🔭 spaced is a modern flashcard app that helps you learn faster and remember longer. It uses spaced repetition to help you learn more efficiently.
Note: This is project is still in early development. If you have any suggestions or feedback, please feel free to open an issue.
Spaced repetition is proven to be one of the most effective ways to learn.
Currently, there are many great apps out there that use spaced repetition, such as Anki, SuperMemo, and RemNote.
Personally, I use obsidian-spaced-repetition
, which I love because of the tight integration with Obsidian.
I believe that markdown is the best way to store your flashcards.
However, one aspect that I dislike about both obsidian-spaced-repetition
(and other tools like ObsidianToAnki)
is the separator syntax that is used to create flashcards.
For my own personal workflow, I use Obsidian to write notes
, but writing flashcards adds clutter.
Fundamentally, I believe that flashcards should be treated
as separate entities that are colocated with your notes,
but different in the following ways:
- Flashcards should only be seen during review.
- There is no need to search / index / tag flashcards in the same way that notes are searched. The whole point of flashcards is to only appear when they need to be reviewed.
Thus, I created spaced, which focuses on handling the spaced repetition aspect of learning. This way, I can focus on writing notes that are easy to read and understand (in Obsidian), and create flashcards that are effective for spaced repetition (in spaced).
Here's an example of my existing notes using Obsidian Spaced Repetition:
#### Dynamic and Static Binding
What is dynamic binding (aka late binding)?
?
A mechanism where _method calls_ in code are resolved at **runtime** rather than at compile time.
<!--SR:!2024-01-02,25,249-->
What is static binding?
?
When a _method call_ is resolved at _compile_ time.
<!--SR:!2024-03-24,94,271-->
Overriden and overloaded methods: static or dynamic binding?
?
Overridden methods are resolved using **dynamic binding**, and therefore resolves to the implementation in the actual type of the object.
In contrast, overloaded methods are resolved using **static binding**.
<!--SR:!2024-03-12,85,271-->
Here's my ideal syntax:
#### Dynamic and Static Binding
**Dynamic Binding (Late Binding)**: A mechanism where _method calls_ in code are resolved at **runtime** rather than at compile time.
Overriden methods are resolved using **dynamic binding**.
<!-- id:<some-card-id-here, where flashcards are stored separately> -->
**Static binding**: When a _method call_ is resolved at _compile_ time.
Overloaded methods are resolved using **static binding**.
<!-- id:<some-card-id-here, where flashcards are stored separately> -->
- I don't always have access to my Obsidian vault - I want to be able to review flashcards on the go.
- I want to be able to easily share flashcards with others.
- I want to create a UI/UX that I personally enjoy using.
One of the big criticisms that I have of
obsidian-spaced-repetition
is that it doesn't store the history of your reviews. You also cannot undo a review, which is annoying if you accidentally mark something as "easy".
If you want to self-host this project, you can do so by following these steps:
-
Fork the repository.
-
Generate a new
AUTH_SECRET
with the following commandpnpm dlx auth secret
-
Create a new GitHub OAuth App.
-
Fill in the
.env
file with theAUTH_GITHUB_ID
andAUTH_GITHUB_SECRET
. -
Create a new database in Turso.
-
Fill in the
.env
file with theTURSO_DATABASE_URL
andTURSO_AUTH_TOKEN
. -
(Optional): To support image uploads, I use a custom Cloudflare Worker. If you don't want to, just fill in a dummy value for
CLOUDFLARE_IMAGE_UPLOAD_WORKER_TOKEN
. -
Run the following command to create the database tables:
pnpm db:push
-
Deploy the app to Vercel.
-
Done! You should now be able to access your own instance of spaced.
This project was built using the following tech stack:
- Next.js
- UI: TailwindCSS, shadcn/ui
- Backend: tRPC
- Database: Turso
- ORM: Drizzle
Great Libraries:
- Spaced Repetition: ts-fsrs
- Swipe gestures: react-swipeable
- Markdown editing: Milkdown
- Icons: Lucide
I've also learned a lot from the following resources:
- Lexical: referenced their implementation of undo / redo
- ts-fsrs-demo: structure for a spaced repetition app
- Radix UI: building swipe gestures
- obsidian-spaced-repetition
Finally, a special thanks to @ishiko732 for answering questions regarding ts-fsrs
and spaced repetition.