A focused todo app. No account. Autosaves to your browser.
- Frontend: React 19, Tailwind CSS, Plate.js (rich text editor), dnd-kit (drag and drop)
- Backend: Hono (API routes)
- Deployment: Cloudflare Workers with Static Assets
- Build: Vite with
@cloudflare/vite-plugin
Install dependencies:
npm installStart the development server:
npm run devThe app runs at http://localhost:5173 with hot module replacement. The dev server uses Cloudflare's workerd runtime, matching production behavior.
Build for production:
npm run buildOutput is written to dist/:
dist/client/- Static assets (HTML, CSS, JS)dist/todoflare/- Worker code andwrangler.json
Preview the production build locally:
npm run previewThis runs the built app in the Workers runtime before deploying.
Deploy to Cloudflare Workers:
npm run deployThis builds and deploys to https://todoflare.<your-subdomain>.workers.dev.
You'll be prompted to log in to Cloudflare on first deploy.
├── index.html # SPA entry point
├── wrangler.jsonc # Cloudflare Worker configuration
├── vite.config.ts # Vite configuration
├── src/
│ ├── client.tsx # React app entry
│ ├── index.tsx # Hono API worker entry
│ ├── components/ # React components
│ ├── hooks/ # React hooks
│ └── styles/ # CSS
└── dist/ # Build output
The app supports emergency read-only mode and admin backup/restore endpoints.
Local-only users also keep hourly rolling checkpoints in browser storage
(todoflare-backup-YYYYMMDDHH) with automatic compaction.
Authenticated users can self-serve export/import from the account menu
(Export Data and Import Data).
Set READ_ONLY_MODE=true in your deployed Worker vars, then redeploy.
GET /api/clock returns readOnlyMode so you can verify state.
Set an admin token as a Worker secret:
wrangler secret put BACKUP_ADMIN_TOKENThe Worker uses the BACKUPS R2 binding from wrangler.jsonc.
Set these Worker vars and deploy:
AUTO_BACKUP_ENABLED=trueBACKUP_MAX_COLUMNS=1000(or lower for conservative runs)
Cron is configured in wrangler.jsonc as hourly (0 * * * *).
BACKUP_API_ORIGIN="https://todoflare.<subdomain>.workers.dev" \
BACKUP_ADMIN_TOKEN="<token>" \
npm run backup:create -- <columnId>
BACKUP_API_ORIGIN="https://todoflare.<subdomain>.workers.dev" \
BACKUP_ADMIN_TOKEN="<token>" \
npm run backup:list -- <columnId>Restore latest snapshot for a column:
BACKUP_API_ORIGIN="https://todoflare.<subdomain>.workers.dev" \
BACKUP_ADMIN_TOKEN="<token>" \
npm run backup:restore -- <columnId>By default this performs a safe clone restore (creates a new column).
Restore a specific snapshot key:
BACKUP_API_ORIGIN="https://todoflare.<subdomain>.workers.dev" \
BACKUP_ADMIN_TOKEN="<token>" \
npm run backup:restore -- <columnId> "columns/<columnId>/<timestamp>.json"In-place restore (dangerous, takes a pre-restore snapshot first):
BACKUP_API_ORIGIN="https://todoflare.<subdomain>.workers.dev" \
BACKUP_ADMIN_TOKEN="<token>" \
npm run backup:restore -- <columnId> in_placeScheduled backup runs log an event named scheduled_backup_run with counts.
Restore all owned columns for an account from latest backups:
BACKUP_API_ORIGIN="https://todoflare.<subdomain>.workers.dev" \
BACKUP_ADMIN_TOKEN="<token>" \
npm run backup:restore-account -- <accountId>