Purpose: Guidelines and context for AI agents working on this codebase
Last Updated: July 12, 2026
This project has users in production. Therefore:
- Backwards compatibility is mandatory — Existing local and synced Yjs data must keep working
- No destructive schema changes without migration — Add optional fields first, migrate safely, and preserve old data
- No user-data reset assumptions — Users must not be expected to clear browser data or Drive sync state
- No clean breaks in persisted contracts — Entity shapes, document names, sync metadata, and URL routes need compatibility handling
- No automatic destructive sync actions — Resets, claim states, archive moves, and billing mutations must be explicit and reversible where practical
- Legacy code can be removed only after migration — Delete old implementations after the replacement safely handles existing data
- Per-file test coverage ≥ 70% — For
src/hooks/**andsrc/utils/**, each file must meet at least 75% coverage - Repository writes require explicit user approval — Leave changes uncommitted for review. Do not commit, push, tag, create GitHub releases, publish packages, or invoke production deployment workflows unless the user explicitly asks for that specific action.
- Assess release scope before every requested commit or push — Determine whether the change warrants a core app SemVer release/tag and whether it changed a published agent artifact (bridge, MCP Registry manifest, OpenClaw bundle, Claude plugin, or ClawHub skill). Report the required release train and publish only the artifacts whose shipped contents or metadata actually changed.
Before changing code, read the sources that govern the area being changed:
- This file, then
status/_status.mdand the relevant layer status file SYSTEM_OVERVIEW.mdandARCHITECTURE_MAP.mdspec/requirements.md,spec/acceptance.md, and the relevant feature/design specifications- Relevant files under
contracts/andrules/, especiallyrules/domain-invariants.md - Relevant sections of
README.md,CONTRIBUTING.md, and operational documentation underdocs/ - Existing tests and behavior comments beside the code being changed
- A matching workflow under
.agents/skills/when one applies
AGENTS.md and the project-specific rules are authoritative. The reusable skills and prompts support those rules; they do not override TaskTime Pro's production compatibility requirements.
For behavior changes, use red/green discipline: first add or update a test that demonstrates the required behavior, verify that it fails for the intended reason, implement the smallest compatible change, then run the focused test and the relevant broader Docker-backed gate. Update docs and comments when a contract, workflow, or non-obvious invariant changes.
Ongoing agent workflows are available in .github/prompts/. status/ is the execution work register; TODO.md is the broader backlog and ideas list. Update the relevant status file when a tracked slice materially changes state.
Authority rule: spec/, contracts/, and rules/ are project source of truth. SYSTEM_OVERVIEW.md and ARCHITECTURE_MAP.md compress that context and must be reconciled when architecture or workflows change. If source, tests, and specifications disagree, investigate and reconcile the drift rather than silently choosing the most convenient version.
TaskTime Pro is a local-first task time tracking and invoicing app.
- Framework: React 19 + Vite
- Styling: Tailwind CSS
- Storage: Yjs CRDT with IndexedDB persistence (via
y-indexeddb) - State: Yjs-backed React hooks in App.jsx
- Routing: Path-based via
useUrlStatehook (e.g.,/projects,/clients/123) - Sync: Yjs + Google Drive (delta-based, conflict-free)
- Path-based routing:
/,/projects,/projects/{id},/clients,/clients/{id},/invoices,/reports,/expenses,/account - Query params only for secondary state:
?section=,?tab=,?create= - Custom
useUrlStatehook (not React Router) - Supports browser back/forward navigation
- Engine: Yjs for conflict-free sync
- Persistence: y-indexeddb for local storage
- Multi-doc architecture: Data split by type/time period
- Sync provider: Google Drive (delta uploads)
- Schema changes must be additive or include an explicit migration path.
- Existing IndexedDB and Google Drive state must be considered live customer data.
- Old cloud state can reintroduce incompatible records after local changes; compatibility must be handled in validation, migrations, and sync code.
- Test schema changes against realistic existing local and Drive-backed data before release.
- Never auto-sync destructive resets across devices.
Document structure:
| Document | Contents | Loading |
|---|---|---|
core |
projects, tasks, clients, businessInfos, templates | Always |
entries-active |
Last 90 days of time entries | Always |
entries-{year} |
Historical entries by year | On-demand |
tasks-archived |
Archived tasks | On-demand |
invoices-archived |
Paid invoices from past years | On-demand |
- All app state powered by Yjs hooks
- Entity hooks:
useProjects(),useTasks(),useTimeEntries(),useTimers(), etc. - All components now use hooks directly - no prop drilling for mutations
- Timer/task components fully migrated to hooks (TimerControls, GlobalTimer, TaskTree, TaskItem, etc.)
- Invoice components fully migrated (InvoiceGenerator, InvoicesList use Yjs hooks)
- Account/Settings fully migrated (usePreferences, useYjs().clearAllData)
- Toast notifications via
ToastContext
- Functional components only
- Hooks for all logic
- PropTypes for validation (TypeScript migration planned)
- Tailwind for styling (no CSS modules)
- 4-space indentation (per project preference in
_implan.md) - Empty line after opening braces
{ - Empty line after semicolons in logical sections
- JSDoc comments on functions
- Components: PascalCase (
TaskItem.jsx) - Hooks: camelCase with
useprefix (useIndexedDB.js) - Utilities: camelCase (
dateUtils.js) - Constants: SCREAMING_SNAKE_CASE
src/
├── components/ # React components
│ ├── modals/ # Modal components
│ ├── invoice/ # Invoice-specific components
│ └── sync/ # Sync status/settings (Yjs)
├── hooks/ # Custom React hooks (Yjs entity hooks)
├── contexts/ # React contexts (Toast, Yjs)
├── stores/yjs/ # Yjs store, doc manager, providers
├── utils/ # Pure utility functions
├── types/ # TypeScript declarations
└── styles/ # CSS files
- Multiple active timers across projects (one per project)
- Timer state managed by
useTimers()hook - Pause preserves elapsed time, doesn't create entry
- Stop creates the time entry automatically
- Projects have
invoiceIds[](references, not embedded) - Tasks have
projectIdand optionalparentTaskId - Time entries have
taskId - Invoices stored separately, referenced by ID
ModalManager.jsxorchestrates all form modals- Supports modal stacking (nested modals)
- Use
openXxxModal()functions from App.jsx
TaskTime Pro uses Yjs for conflict-free sync. The system is in src/stores/yjs/:
- CRDT-based sync - Conflicts resolved automatically by Yjs
- Multi-document architecture - Data split for scaling
- Delta-based uploads - Only changes sync, not full state
- Automatic archival - Old entries archived by year
Hooks (in src/hooks/):
const { projects, createProject, updateProject, deleteProject } = useProjects();
const { tasks, createTask, updateTask, archiveTask } = useTasks();
const { entries, createEntry, loadYear } = useTimeEntries();
const { timers, startTimer, stopTimer, pauseTimer } = useTimers();
const { clients, createClient } = useClients();
const { invoices, createInvoice } = useInvoices();
const { preferences, updatePreferences } = usePreferences();Key files:
src/stores/yjs/YjsStore.ts- Main store facadesrc/stores/yjs/YjsDocManager.ts- Multi-doc managementsrc/contexts/YjsContext.tsx- React context providersrc/hooks/use*.ts- Entity-specific hookssrc/components/sync/YjsSyncStatus.tsx- Status indicatorsrc/components/sync/YjsSyncSettings.tsx- Settings panel
Sync Behavior Rules (definitive):
Three auto-sync modes exist: manual, backup, sync. Each has distinct trigger behavior:
| Trigger | Manual | Backup | Sync |
|---|---|---|---|
| Local edit | No auto-sync | Push-only (debounced 100ms; project-note edits after 1.5s quiet) | Manifest check, reconcile if changed, then push (debounced 100ms; project-note edits after 1.5s quiet) |
| Tab focus | No auto-sync | Push pending local changes only | Full pull+push (60s cooldown) |
| Network online | No auto-sync | Push pending local changes only | Full pull+push (60s cooldown) |
| Periodic interval | None | None | Every 5 minutes while visible (manifest check; pull+push if changed) |
| Page reload | Connect only, except a pristine first device may do one bootstrap pull | Full pull+push on connect | Full pull+push on connect |
| "Sync Now" button | Full pull+push (force) | Full pull+push (force) | Full pull+push (force) |
| Reconnect after disconnect | Connect only (no sync) | Push dirty docs on connect | Push dirty docs on connect |
Key rules:
- Backup = push-only by default. No automatic pulling of remote changes. Users must click "Sync Now" or reload to get remote changes.
- Sync = full bidirectional. Pulls + pushes on all triggers with cooldowns.
- Manual = user-controlled. Only "Sync Now" triggers sync after setup. Page reload and reconnect normally only establish the Drive connection without pulling or pushing, except a pristine first device may do one bootstrap pull so existing Drive data appears immediately.
- Sync Now = full-state verification. The user-facing action pulls and verifies every loaded document by uploading its full current state; refresh-only internal callers explicitly disable full-state verification.
- Pull efficiency: Before downloading, a lightweight
modifiedTimemetadata check determines if the manifest changed. No download if unchanged. - Pull throttle: 30 seconds — skips manifest reload if no local changes and last pull was recent.
- Foreground request budget: A clean focus/online event inside the 60-second cooldown makes zero Worker/Drive requests. Once stale, an unchanged clean check makes one manifest-metadata request, advances the local cooldown, and performs no document transfer, manifest save, backup listing, or full app-data listing.
- Cross-tab lock: Web Locks API prevents duplicate syncs across tabs.
- Pending local retry: If an automatic upload meets an active sync or occupied Web Lock, genuine pending local work retries with bounded exponential backoff after the current pass can release the lock. Clean checks do not retry, and failed network/conflict passes wait for the normal recovery triggers.
- Page-exit serialization: Hiding or exiting during an active sync does not enqueue a second forced pass.
- Reconnect push: Dirty docs are tracked by document name in localStorage and only those docs are pushed as full-state on next connect regardless of mode. Pull/consistency retries remain separate from local-dirty evidence; legacy boolean-only markers are conservatively supported.
- Idempotent reconciliation: Archive and persisted-record reconciliation emits no Yjs update after records are already settled.
- Never auto-sync destructive resets across devices — e.g.,
resetExpiredSkipsmust not undo a valid skip from another device.
Token persistence is handled by a Cloudflare Worker to solve OAuth token expiry:
- Worker URL:
https://sync.tasktime.pro - Source: private operational Worker source. The public repository mirror intentionally excludes this implementation.
- Features: Secure refresh-token storage, auto-refresh, and short-lived direct-token issuance
How it works:
- OAuth popup → Worker exchanges code for tokens
- Worker encrypts and stores refresh token in KV
- Worker returns session ID to app (stored in localStorage)
- Worker status selects direct Google Drive for the next connection
- The browser receives a short-lived access token kept only in tab memory and sends routine Drive file requests directly to Google
- Worker auto-refreshes access tokens as needed; it never returns a refresh token to the browser
Worker operations: Deployment, logs, D1/KV commands, and secret management live in the private infrastructure repository, not in the public app Makefile.
Local development: Set VITE_SYNC_WORKER_URL in .env.local
All npm/node commands run through Docker, NOT locally.
make dev # Start dev server (http://localhost:3101)
make stop # Stop dev server
make build # Production build
make install # Install dependencies
make add PKG=idb # Add a package
make lint # Run ESLint
make typecheck # Run the repository-wide TypeScript check
make logs # View container logs
make shell # Open shell in container
make clean # Full rebuild (after package.json changes)
make npm CMD="run test" # Run arbitrary npm command# Install a package
docker compose run --rm app npm install <package>
# Run dev server
docker compose up
# Run any npm script
docker compose run --rm app npm run <script>Do NOT run npm directly — it won't work (npm not installed on host).
- Don't use localStorage — We use Yjs + IndexedDB
- Don't add console.log — Remove debug statements
- Don't skip migration code — Production data must remain readable
- Don't keep old code "just in case" — Delete it
- Don't break persisted data contracts — Add compatibility handling or migrations
- Don't use class components — Functional only
- Don't add new dependencies without justification — Keep it lean
- Don't run npm directly — Use
docker compose run --rm app npm ... - Use Yjs hooks —
useProjects(),useTasks(), etc. for all data access - Don't create new useIndexedDB calls — All new state should use Yjs
- File deletions must be triggered via CLI — Use a terminal delete command so you can approve it
- Yjs sync system (Phases 1-6: core, Drive provider, React hooks, App migration, sync reliability, component migration)
- Phase 7: Token persistence with Cloudflare Workers
- Phase 8: Sync optimizations (60s interval, pull throttle, manifest change check, fallback file lookup)
- App.jsx fully migrated to Yjs hooks
- Old SyncEngine removed
- YjsSyncStatus and YjsSyncSettings components
- Timer components migrated (TimerControls, TaskTimer, GlobalTimer)
- Task components migrated (TaskTree, TaskItem, TaskActions, SubtaskSection, SubtaskItem)
- TimeEntriesModal migrated to Yjs hooks
- InvoiceGenerator migrated to Yjs hooks
- InvoicesList migrated to Yjs hooks
- ProjectList/ClientList cascade deletes migrated to Yjs hooks
- Account clear data migrated to use Yjs store clearAllData()
- Preferences migrated to usePreferences() hook
- ExportImport migrated to useTimers() hook
- PaymentMethods, BusinessInfo, InvoiceTemplates migrated to Yjs hooks
- Dashboard and RecentTasks migrated to Yjs hooks
- useTaskState migrated to Yjs hooks
-
syncableEntity.tsdeleted (no longer needed) - Cloudflare Worker deployed (private operational source; excluded from the public mirror)
- Worker-based auth flow (OAuth popup → Worker OAuth/token control plane)
- Encrypted refresh token storage in Cloudflare KV
- TypeScript migration (gradual)
- Testing infrastructure improvements
| Document | Purpose |
|---|---|
docs/agent-release-runbook.md |
Local MCP bridge, ClawHub skill, OpenClaw bundle, and Claude plugin publishing workflow |
_implan.md |
Original project plan and preferences |
README.md |
User-facing documentation |
rules/ |
Detailed engineering, testing, design, Docker, hardening, and domain constraints |
.agents/skills/ |
Reusable workflows for planning, implementation, review, and handoff |
SYSTEM_OVERVIEW.md |
Compressed runtime, data, workflow, reliability, and security model |
ARCHITECTURE_MAP.md |
Module boundaries, dependency direction, and change hotspots |
spec/ |
Product intent, requirements, acceptance, architecture, UX, features, roadmap, and ambiguities |
contracts/ |
Durable public interfaces and persisted data schemas |
status/ |
Current cross-layer execution state and handoff detail |
- Check this file for rules — Especially the "no legacy code" rule
- App.jsx uses Yjs hooks — All state is managed by Yjs
- Keep tests updated — Add or adjust tests whenever behavior changes
- Use Yjs hooks directly —
useProjects(),useTasks(), etc. fromsrc/hooks/ - Subtasks cannot be recurring — The project UI disallows recurring subtasks; avoid adding recurring-specific logic to subtask components.
- Keep the context layer current — Update specifications, contracts, overview/map, and status when their governed behavior changes.
This file should be updated when major architectural decisions are made.
- Use DebugBundle for runtime failures, production/customer-facing incidents, endpoint downtime, notification/webhook delivery failures, health-check failures, specific incident reports, or symptoms likely to have generated captured events.
- For deterministic local code, UI, layout, copy, calculation, refactor, or test-only issues, inspect source and tests first; do not check DebugBundle incidents unless runtime evidence is needed or the user asks.
- Read
.agents/skills/debugbundle/SKILL.mdfor the full DebugBundle workflow.