Skip to content
ella-toPublic

About

an app for writing your thoughts

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Focus

A fast, offline-first outliner for hierarchical notes. Everything stays in your browser: there is no account and no server. Live at focus.ella.to.

  • Outline: nest bullets, add multi-line notes, check items off, and zoom into any bullet. Undo and redo cover text and structure.
  • Workspaces: keep separate outlines, for example personal and work. Lock a workspace with a password to encrypt it with AES-GCM; the key is derived with PBKDF2-SHA-256 at 600,000 iterations.
  • Offline and installable: a service worker caches the app, and the web manifest lets you install it.
  • Multiple tabs: edits made in one tab show up in the others.
  • Your data: export a workspace, back up every workspace into one file, and restore it later.

Press ? in the app to see every keyboard shortcut.

Development

Requires Bun.

bun install
bun run dev          # start the dev server
bun run build        # type-check and build into dist/
bun run preview      # serve the production build

Quality checks (CI runs all of them):

bun run typecheck
bun run lint         # ESLint, zero warnings allowed
bun run format:check # Prettier (bun run format to fix)
bun run test         # unit tests (Vitest + fake-indexeddb)
bun run test:e2e     # end-to-end tests (Playwright)

The end-to-end tests need a browser. Run bunx playwright install chromium once, or reuse an installed Chrome with PLAYWRIGHT_CHANNEL=chrome bun run test:e2e. The pwa project runs against a production preview build to cover the service worker.

How it works

Notes are stored as an append-only event log in IndexedDB, with one log per workspace. Loading a workspace replays its events into a tree, and the tree is held in a MobX-State-Tree store that the React UI observes.

Path Responsibility
src/lib/event-store.ts IndexedDB schema and migrations, atomic writes, change broadcast to other tabs
src/lib/event-replayer.ts Pure function that turns events into a bullet tree
src/lib/store/ Root store (root-store.ts), models, persistence strategy, encryption, export formats, cross-tab sync
src/components/outline/ The outline editor: bullets, keyboard handling, focus and caret management
src/components/workspace/ Workspace switcher and its dialogs (create, rename, lock, unlock, delete)
src/components/layout/ Page chrome: header, search, menu, footer, banners, shortcuts
src/components/ui/ Shared UI primitives (shadcn/ui, confirmation dialog, toasts)
src/hooks/ Route sync, media queries, delayed focus
e2e/ Playwright specs

A few design rules keep the data safe:

  • Every edit is written immediately. A burst of typing to one field updates its event in place, so the log stays small without delaying writes.
  • Whole-log rewrites (import, lock, unlock, compaction) happen in a single IndexedDB transaction. They either complete fully or change nothing.
  • A failed read never leads to a write, and a locked workspace never receives plaintext events, even from a stale tab.
  • The URL is the source of truth for the open workspace and the zoomed bullet, so browser back and forward work as expected.

Deployment

Pushing to main runs CI and deploys dist/ to GitHub Pages through .github/workflows/deploy.yml. The build uses relative asset URLs, so it works at a domain root or under a sub-path.

About

an app for writing your thoughts

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages