This document describes the current code. Historical course reports and dated review notes are not implementation contracts.
Bullet-AI is a Today-centered personal workspace. The maintained domains are profile preferences, moments, reflections, goals, habits, and habit check-ins. AI has one purpose: strict goal planning. It does not chat over personal records and it never writes domain data directly.
src/domain: explicit camelCase entities and stable identity helpers.src/features/<domain>: domain reads, writes, projections, and reusable domain UI.src/features/today: Today-only aggregation across goals, habits, moments, and reflections.src/features/workspace: route-scoped domain composition, export, logout cleanup, and sync diagnostics.src/features/profile: cloud-backed profile state and preference synchronization.src/data/local: IndexedDB lifecycle, snapshots, mutation state machine, and sync worker.src/data/react: React Query providers, pending overlays, incremental reads, and history pagination.src/data/supabase: snake_case mapping, remote reads, CAS writes, and change-log synchronization.src/lib/profile,src/lib/auth,src/lib/supabase: cross-feature infrastructure.src/app: routes, shell, and page-only interaction state.src/shared: UI and i18n primitives with no product-domain dependency.
Dependencies flow from app -> composition features -> domain features -> data -> domain/lib infrastructure. Domain features cannot import one another or composition features. domain, data, lib, and shared cannot depend upward; ESLint enforces these boundaries and rejects deep relative imports.
AuthSessionProviderowns the browser Supabase session.- A route-scoped feature hook requests its resource through
useSyncedResource. - IndexedDB snapshots and pending mutations produce the first render without waiting for the network.
- A resource without a durable cursor performs one baseline read after first capturing its change-log high-water sequence.
- Later refreshes pull
workspace_change_logpages after that cursor, fetch only changed current rows, and atomically apply upserts, physical deletes, and the new cursor. - Version- and start-time-aware replacement rejects stale reads that would overwrite a newer synced snapshot.
- TanStack Query caches the overlay and BroadcastChannel invalidates it across tabs.
An empty collection has its own durable sync marker; absence of rows is not confused with a resource that has never loaded. Each authenticated browser runtime also has a local generation token, so callbacks started before logout cannot write into a later login for the same user. Durable mutations are scoped only by userId, never by that in-memory token, so a refresh or another tab can take over the queue.
Moments and reflections keep a 90-day durable recent set. Their dedicated pages use stable keyset cursors and load full history one page at a time, retaining loaded old pages only in memory. Goals and check-ins keep complete local projections because retention settings and streak calculations require them, while remote refreshes are incremental after the baseline.
- A feature hook creates the complete optimistic entity.
DataStore.enqueueMutationatomically writes the mutation and optional Blob.- Same-entity never-sent mutations compact (
create+patch,patch+patch,patch+delete);create+deletecancels locally. - A check-in created for an offline-new Habit depends on the Habit create mutation; runnable selection and atomic claim both enforce that dependency.
DataSyncWorkerruns only while online and visible, under a per-user Web Lock.- The Supabase executor performs idempotent create or version-CAS patch/delete.
- Applied writes replace the snapshot only when they are not older than the current cross-tab snapshot. A queued successor rebases on the freshest version.
- Version mismatch stores both the local draft and remote entity as a visible conflict.
- Settings shows a field-level comparison. The user may accept remote, rebase and retry local, or submit merged business fields; remote deletion converts a retained local draft into an idempotent create.
Transient errors use unbounded exponential backoff with jitter. Auth failures pause until the auth revision changes. Permanent failures remain local and visible until the user discards them.
clientIdis the stable cross-device identity; databaseidnever enters feature code.- Every remotely mutable entity has a monotonically increasing
version. - Supabase is the canonical fact source; IndexedDB durably holds pending writes but may be cleared on logout.
- Domain code is camelCase. Snake_case exists only in the Supabase adapter and SQL.
- Deletion is physical. The 5-second undo window happens before enqueueing delete.
- Weekly habit completion and streaks are calculated by grouping check-ins according to the configured week start;
checkedOnpreserves the actual chosen day. - A Habit's immutable
startedOnis business data;createdAtis only a technical audit timestamp. - The database requires
started_onon insert and rejects every later attempt to change it. - Reflections contain only
titleandbody. - Moment images use the private
momentsbucket and mutation-specific object paths. - Moment entities persist only
imagePath; signed URLs and local Blob URLs exist only in the in-memory view layer. - AI rate reservation is a fixed, no-argument database RPC; callers cannot choose the quota window or limit.
- Database initialization uses the unified
000_current_schema.sqldefinition. db/migration-manifest.jsonrecords the initialization entry point and SQL checksums;pnpm migration:checkrejects rewritten or unlisted SQL.- CI runs type, lint, formatting, migration, unit, build, and isolated local Supabase/pgTAP contracts. The local database script disables telemetry and always stops its Docker stack.
- API responses carry a request ID and
Server-Timing; structured logs retain sanitized stack/cause data, and/api/healthexposes configuration readiness without secret values. - Moment image replace, clear, duplicate-create, and hard-delete paths retry mutation-specific Storage cleanup before the mutation completes. The manual audit script handles legacy or otherwise orphaned objects; there is no service-role cleanup route or scheduled cron.
- Full export requires network access so it cannot silently label a partial local cache as a complete backup.