Time Log#
A time-tracking app on the AT Protocol. Multiple concurrent timers, arbitrary string tags on every entry, mobile-first (PWA later).
- Frontend — React SPA (Vite, Tailwind CSS v4, shadcn/ui).
Records live locally in IndexedDB (
src/store/) and are evaluated client-side; the app is local-first. - Backend — HappyView, a lexicon-driven atproto
AppView. Data is stored as private records in a permissioned space; the client
syncs periodically. See
deploy/for the local Docker stack. - Lexicons —
lexicons/xyz/pardisio/time/*.json(NSID authorityxyz.pardisio.time).
Develop#
npm install
npm run dev # Vite dev server → http://localhost:5173
The full stack (SPA behind Caddy + HappyView) runs via Docker — see
deploy/README.md.
Scripts#
| Script | Description |
|---|---|
npm run dev |
Vite dev server. |
npm run build |
Build the SPA to dist/. |
npm run preview |
Serve the built dist/. |
npm run typecheck |
Type-check the app. |
Layout#
index.html Vite entry
src/main.tsx React mount
src/App.tsx Auth gating + sync boot + store snapshot (context)
src/components/ React UI: board, timer cards, log sheet, filter editor…
src/components/ui/ shadcn/ui primitives
src/index.css Tailwind v4 theme (Momentum palette as shadcn tokens)
src/store/ IndexedDB store: db, entries, filters, ids, types
src/auth/, src/sync/ OAuth session + permissioned-space sync (framework-free)
src/pip/ Document Picture-in-Picture timers puck (framework-free)
shared/ Predicate type + evaluator (used by store + UI)
lexicons/ atproto lexicons (records + XRPC methods)
deploy/ Docker: Caddy + HappyView (mirrors the Fly.io topology)
Data model#
Two record types, mirrored by the local store:
- entry —
name(optional),description,startTime,endTime(absent = running),tags[]. Keyed by AT-URI. - filter —
name, optionalcolor, and a nestable booleanpredicate(and/or/not,tagsInclude,startTime/endTimebefore/after). Filters are evaluated locally (shared/predicate.ts) and their colors mark matching entries.
Console helpers: window.timelog.clearAll() wipes the local cache,
window.timelog.sync() forces a full sync, window.timelog.refresh() reloads
the UI from the store.