A fast, bilingual (English / עברית) todo app built with React 19 and TypeScript.
- English and Hebrew, with full right-to-left layout. User text uses
dir="auto", so mixed-language tasks render correctly. - Themes: System, Light, Dark, Midnight, Paper, Forest, High contrast. There is also a compact density setting.
- Lists and smart views: Today (includes overdue), Upcoming, Overdue and All, with live counts.
- Quick add with inline syntax:
Call Dana tomorrow !high #work. The parsed date, priority and tags show as chips while you type.- Priority:
!high!med!low(or!!!!!!). Hebrew:!גבוה!בינוני!נמוך. - Tags:
#anything. - Due date:
today,tomorrow,friday,@mon,+3d,+2w,2026-10-12. Hebrew:היום,מחר,מחרתיים,@שישי.
- Priority:
- Details panel: title, due date with quick picks, priority, list, tags, subtasks with progress, and notes. It docks beside the list on wide screens and becomes a drawer, or a full-screen sheet on phones.
- Search across titles, notes, tags and subtasks, with match highlighting.
- Filters and sorting:
- filter: All / Active / Completed;
- sort: Manual / Due date / Priority / Newest / A–Z.
- Drag and drop:
- drag to reorder tasks (manual sort);
- drop tasks onto a list in the sidebar to move them;
- drag the selection to move several tasks at once.
- Multi-select: Shift-click or Ctrl-click, then complete, move or delete in bulk.
- Undo for every change: a toast offers Undo, and
Ctrl+Zworks too. - Keyboard-first: press
?in the app for the full list. Highlights:Nnew task,/search;J/Kmove between tasks,Xcomplete;Enteropen details,Erename;Alt+↑/↓reorder,1–9jump to a view.
- Export and import JSON backups, merging or replacing.
- Mobile:
- the sidebar becomes a slide-in drawer and the add bar sits at the bottom of the screen;
- tap targets are large and the layout respects safe areas;
- installable as a PWA, with offline support through a service worker.
- Multi-tab sync: changes in one tab show up in the others.
pnpm install
pnpm dev| Script | What it does |
|---|---|
pnpm dev |
Vite dev server |
pnpm build |
Type-check and build to dist/ |
pnpm build:static |
Build the static site (used by the deploy job) |
pnpm preview |
Serve the production build |
pnpm test |
Vitest (unit and integration) |
pnpm lint |
ESLint |
pnpm typecheck |
tsc -b |
The build has no server requirements and works from any URL path, for example https://example.com/tools/todo/:
vite.config.tssetsbase: './', so every asset URL is relative.- There is no client-side router. The current view lives in the URL hash (
#/today,#/list/<id>), so deep links and the back button work without rewrite rules. - The service worker and manifest are registered relative to the page.
CI (.github/workflows/ci.yml) lints, builds and tests every push. Pushes to main are deployed via Site Manager.
src/
data/ Domain types, the TodoRepository interface, and its localStorage implementation
state/ TodoStore (optimistic updates + undo), view/filter/sort logic
i18n/ Typed dictionaries (en is the source of truth; he must match it)
settings/ Persisted UI preferences (language, theme, density, sort, …)
theme/ Theme ids and swatches; colors live in styles/themes.css
components/ UI, one folder per component with a CSS module
utils/ Dates, quick-add parser, search highlighting
The UI talks only to the async TodoRepository interface in src/data/TodoRepository.ts. The only concrete implementation is LocalStorageTodoRepository, which:
- stores everything under a single versioned key, with migrations;
- recovers from corrupt data by backing up the bad payload under
…:corrupt:<timestamp>and starting fresh; - syncs across tabs through the
storageevent.
To add another backend (REST, IndexedDB, …):
- Implement
TodoRepository.subscribeis optional and lets the backend push external changes. - Construct it in
src/main.tsx. That is the only place that knows which implementation is in use.
Every color is a CSS custom property, defined per theme in src/styles/themes.css. To add a theme:
- Add a
:root[data-theme='…']block there. - Add its id to
THEME_IDSandTHEME_SWATCHESinsrc/theme/themes.ts. - Add a name to both dictionaries.
src/i18n/en.ts defines the keys. he.ts is typed against it, so a missing or extra key is a compile error. Plurals use Intl.PluralRules categories (one / two / other …).
Layout uses CSS logical properties throughout (margin-inline-start, inset-inline-end, …), so RTL needs almost no special casing.