Focus Study is a local-first workspace for learning any topic. Bring your own versioned Question & Answer collection in a strict Markdown format, study it in the browser, and keep attempts, notes, highlights, favourites, priorities, and progress on your device.
The app begins with an empty library and a Getting Started guide.
Docker is the supported developer workflow. From the repository root:
make upOpen http://localhost:5174. The Compose bind mount keeps Vite hot reload enabled while the named focus-study-node-modules volume keeps container dependencies out of the host checkout.
Stop the server with:
make downCommon checks run inside Docker:
make test # Vitest unit and interaction tests
make typecheck # TypeScript project references
make build # Production Vite bundle
make check # typecheck + test + buildmake clean removes the Compose container, named dependency volume, and orphan containers. It does not remove learner data stored by your browser.
Use the included skills/focus-study-collection/ Codex skill to generate a collection that matches the public focus-study Markdown contract. The skill validates the result before returning it. You can also author the format directly.
Each collection has YAML frontmatter with format_version: 1.0, a stable collection ID, title, and description. Each question is an immutable-ID focus-study-question fenced YAML record with its topic, section, question number, question, Markdown answer, tags, priority, difficulty, and initial favourited value.
Import a .md file from the Library / Getting Started screen. Focus Study validates the whole file before changing the local library and presents errors without partially importing content. Re-importing a collection with the same stable IDs refreshes its content while retaining progress and learner preferences. Imported priority, difficulty, and favourite values are defaults for newly seen questions only.
The app does not read a local study-guides/ directory at runtime. A deployed browser app cannot read files on your computer; collections are added only through an explicit import or restore action.
Export any selected collection as a .focus-study.zip backup. It contains the original collection.md, collection-qualified learner state, and a versioned manifest. Restoring that archive resumes the collection with its attempts, active session, notes, highlights, filters, answer revisions, and preferences intact.
Focus Study works completely offline by default. Set VITE_ONLINE_HOST=true and provide VITE_GOOGLE_CLIENT_ID only for a hosted deployment that offers optional Google Drive sync.
When enabled, the app asks the learner to connect Google Drive and requests the narrow drive.appdata permission. Each browser writes a separate hidden device snapshot in Drive's private appDataFolder; it does not create a visible .focus-study folder or require an application backend. On sync, the app reads the legacy snapshot and all device snapshots, unions collections by stable collection_id, chooses the newest content for duplicate collections, and merges learner state. A collection missing from one device is never treated as deleted. Access tokens stay in browser memory, so syncing works only while the site remains open and connected. The sync UI shows status, last sync time, and a manual Sync now action.
Do not commit a Google client ID configuration intended for a private deployment. Keep local Vite values in .env.local.
- Library / Getting Started — generation prompt and template, strict Markdown import preview, validation errors, collection management, backups, and optional Drive sync.
- Study — responsive collection/topic/section/question navigation, search, tag/attempt/favourite/priority filters, masked answers, Markdown notes (including fenced code blocks), timer recovery, and completed-attempt feedback.
- Analytics — collection/topic/date/tag/favourite/priority/attempt filters, reconciled KPI cards, section progress, a text-equivalent time trend, and a sortable question table.
Learner state is persisted in browser IndexedDB, with localStorage as a last-resort fallback when necessary. The header exposes which path is active. All state is qualified by collection ID so different collections can reuse question IDs safely.
Only a completed Done action creates an immutable attempt. An active session stores its ISO start timestamp and note draft, so reload recovery calculates elapsed time from the original timestamp instead of resetting to zero.
The UI uses semantic HTML, accessible names, visible focus rings, a skip link, keyboard search suggestions, live status announcements, text labels alongside attempt colours, SVG icons, and a reduced-motion media query. Responsive layouts are defined for 375px, 768px, 1024px, and 1440px checkpoints; visually verify those widths after make up.
The automated suite covers parsing and validation, import/re-import, archive round-trips, study metrics, active-session recovery, and browser-only Drive sync behavior. Use make check before handing off changes.
src/
components/ # accessible library, study, sidebar, analytics UI
lib/ # Markdown contract, storage, archive, and sync logic
skills/focus-study-collection/# Codex collection-generation skill and template
design-system/ # persisted UI/UX design decisions
