TaskTime Pro is a production, local-first task management, time tracking, expense, reporting, and invoicing application for freelancers and solo professionals. The browser owns user data and business mutations. Optional services add provider-neutral Google Drive or Dropbox cloud synchronization, push notifications, diagnostics, public documentation, and same-device agent access.
This is a context-compression document. Detailed requirements live in spec/, durable interfaces in contracts/, and mandatory constraints in rules/.
-
Temporary root recovery:
src/recovery/builds an independently pinned readonly Yjs/IndexedDB reader for the old root origin. It uses the normal portable backup validator without initializing the app store or a provider. The public site owns its dismissible UI; removal follows the recovery window. It remains available after launch cleanup for returning local-only users. -
Browser app: React 19/Vite PWA under
src/. It provides all product screens and owns Yjs-backed mutations. -
React context identity: Yjs and billing context objects live in UI-independent shared modules so lazy Reports imports after development hot updates retain their mounted providers. Store lifecycle and entitlement policy are unchanged.
-
Explicit deletion: Browser hooks and agent commands share archive-aware deletion in
workspaceDeletion.ts. Complete-history preflight and local persistence barriers keep dependent cleanup ahead of parent deletion; known unapplied cloud history requires Sync Now. Existing or offline-concurrent orphan records are retained for explicit recovery, never automatically erased. -
Local persistence: Yjs documents persisted to IndexedDB through
y-indexeddb. Explicit durability barriers merge persisted/current updates and await the atomic data transaction's commit, preserving unseen cross-tab work; incomplete hydration and aborted writes cannot acknowledge a successful mutation. -
First load: Workspace collections start empty. Onboarding and category reads never seed records, so a fresh browser remains eligible for pristine cloud restore until actual user work exists. Existing starter records remain saved data; preference defaults are read-time fallbacks.
-
Cloud sync: Production supports direct browser-to-Google Drive and direct browser-to-Dropbox App Folder sync with short-lived memory-only access tokens. The provider-neutral lifecycle shares sync, manifest, backup, hosted-service identity, agent behavior, and explicit user-initiated transfer while Worker controls fail closed independently for endpoints, new Dropbox connections, and transfers. Connections and transfers are deployed/enabled for approved/current accounts; no transfer starts automatically. Broad Dropbox availability to new public users remains gated on Dropbox App Console production access followed by the non-destructive post-approval sign-in/token/direct-file canary. Routine file bodies bypass the Worker. Dropbox's verified connected-account email is read browser-to-provider and retained in the origin-local auth record; the Worker keeps its pseudonymous subject for identity and entitlement. Only when the user explicitly starts paid Checkout may the browser submit that verified email as a separate billing contact for the mapped Stripe Customer. A verified moved-source marker stops automatic reconnects, primarily directs the user to the recorded destination, and permits source reuse only through an explicit source-only wipe followed by a push-only seed from the complete local workspace.
-
Shared Google auth UI: Storage re-reads clear signed-in identity when the shared session is absent, so local disconnect updates every mounted account and sync consumer without a page reload. This does not reset workspace data or revoke the remote grant. A transient Worker status failure retains that session but leaves Drive transport unresolved; bounded visible-tab checks or a later online/visible signal can select direct transport without a refresh.
-
Cloud connection recovery: Both providers bound status/token requests at ten seconds and automatically retry temporary startup failures while visible and online. Retries coalesce, honor Retry-After and stop after three attempts until a later wake. Healthy wake checks add no auth traffic; stale completions cannot restore disconnected sessions. Existing sync modes and persisted data remain authoritative.
-
Agent command layer:
src/agent/commands/exposes validated business actions over the browser bridge context. -
Local MCP bridge:
src/agent/bridge/and the built@tasktimepro/agent-bridgepackage provide loopback-only, explicitly paired agent access. -
Managed OpenClaw plugin: the official native plugin registers generated TaskTime tools and owns one packaged bridge child for the supervised Gateway/profile lifetime; it does not own product data or duplicate command behavior.
-
Public site and build outputs: The independent private, locally nested and ignored
tasktime-site/repository owns Astro homepage/pricing/blog/legal/agent pages and public discovery. Core builds onlydist-app; site builds its owndist. A reviewed JSON snapshot carries core public tool/discovery metadata into site without parent-source imports or coupled release cycles. Seecontracts/site-distribution.md. Production uses one root Pages project for site and one permanent app project, sharing the existing Worker/services. The launch rollback deployment is retired after owner acceptance. Site source is retained privately undertasktimepro; each component deploys independently from its tested, approved artifact. -
Publication isolation: Only the app owns PWA installation and offline caching. Site ships a static non-indexable 404 to disable implicit host SPA fallback, plus complete public metadata/sitemap checks. App noindex metadata remains crawlable. Each repository's release gate independently blocks on high/critical dependency findings; functional green is not security approval.
-
Origin roles:
src/config/origins.tsexplicitly distinguishes the marketing/documentation, application, optional Worker, and agent-documentation origins. Production values are exact HTTPS origins and local exceptions are explicit loopback HTTP origins. The app and Worker sources accept the exact old and new application origins during the cutover overlap. The supervised two-user move reuses complete portable backup import or the selected provider's existing pristine-device bootstrap; it does not add a permanent migration subsystem or change live configuration locally. Final cutover cleanup removes obsolete old-origin/callback/temporary authority but never clones or deletes the shared Worker, D1/KV, provider, email, or Push services. -
Operational evidence: App and site use independent DebugBundle browser configuration and services (
tasktime-app,tasktime-site). The site owns a small optional bundled diagnostics and privacy-strict aggregate analytics module, without app storage, persistent visitor identity or cross-site tracking. The app opts the shared Worker/auth/origin/path into browser SDK trace propagation; the Worker permits the optional trace header on app-origin CORS requests. Provider file requests are outside that target. Deploy Worker CORS support before the app artifact that sends the header. The app also attaches its exact build version as diagnostic deploy context. Local tests remain the first tool for deterministic failures. New subscription acquisition requires VAT-inclusive Stripe Prices and matching catalog copy; automatic tax stays enabled and server controls independently authorize acquisition. -
Subscription control plane: Private Worker/D1/Stripe modules and the browser client now implement a sanitized catalog, canonical provider-bound billing status, short-lived signed local assertions, recovery, and action policy. Production acquisition is enabled after staged verification; the status register and private operational record own rollout evidence. Independent client/report/email switches permit staged enforcement and rollback. Billing state never becomes Yjs/product/provider data.
-
Production-like local stack: In an operator checkout, the default
make devcommand applies an explicit Vite-development flag on a loopback hostname and runs the app, local Worker/D1, scheduled recovery runner, and Dockerized Stripe test webhook listener, plus the optional site on port 3102, as one detachedtasktimeCompose group. The app stays on port 3101. Stop preserves the prepared containers for Docker Desktop Play; validation commands usetasktime-toolsand cannot join its lifecycle or stop it. The local overlay cannot turn off a Worker control enabled in tracked production configuration, while guarded unreleased billing controls may be enabled against Stripe test mode. Product screens remain visually production-like without sandbox-only banners or developer-facing notices. Hosted Send and email delivery-status checks exercise the normal Pro entitlement, quota, idempotency, and recovery path against local D1 and the configured Resend account; startup fails before opening the stack if that ignored local credential is absent, and delivery still requires an explicit Send.make dev-billing-sandboxremains a compatible alias, while a public checkout without private infrastructure retains an explicit core-app fallback. Production builds ignore the sandbox flag, and product data remains in the ordinary real local Yjs workspace with its configured sync mode. The Vite development server does not install the production service worker; PWA caching and Web Push use the production-preview validation path. Startup read-only attests the applied local email ledger against the canonical schema after migrations and fails closed on drift; it never repairs or deletes preserved local data automatically. Before the scheduled sidecar starts, preparation also applies the existing idempotent Web Push schema to its isolated local database so an empty developer checkout does not make the otherwise-independent recovery invocation fail. -
Hosted-email recovery: The browser persists a privacy-minimized, lifecycle-bound attempt before Send. A single byte-identical retry reuses the same attempt and provider idempotency key; all later browser reconciliation is D1-only and provider-free. Status may use bounded, privacy-minimized coordination evidence to make a definitive missing-attempt result safe, but it never changes an attempt, quota, or delivery outcome. Signed provider events and the scheduled Worker reconciler may confirm an already-contacted part but never resend it. An authenticated
ATTEMPT_NOT_FOUNDreleases only the exact local marker because no durable Worker reservation exists. Reconciliation runs automatically on discovery and after an entitled-send5xx; the modal stops loading after a short bounded check while the list continues background recovery and suppresses duplicate Send. Accepted metadata is written once only if the current document still matches its send-time snapshot. If the provider result was marked applied but the matching Yjs sent timestamp did not survive a crash or reload, the missing timestamp opts that retained terminal marker back into owned, no-send status proof and idempotent metadata application. This also covers a terminal partial result where the customer copy was accepted and an optional forward copy was rejected. An invoice that already has sent metadata does not poll again. There is no manual status button. -
Local pricing review fallback: Vite development on a loopback hostname can render the bundled
/pricing/review catalog immediately inside Plan & Billing while the Worker catalog is unavailable. The Worker response replaces it when available; production builds never use the fallback, and no fallback value can authorize trial, Checkout, entitlement, or hosted service work.
The Yjs store is split into documents so current work stays loaded and historical data can load on demand:
| Document | Responsibility |
|---|---|
core |
Projects, active tasks, clients, settings/templates, current invoices, and the internal replay-safe invoice billing-operation journal |
entries-active |
Recent time entries |
entries-{year} |
Historical time entries by year |
tasks-archived |
Archived tasks |
expenses-archived |
Archived expenses |
invoices-archived |
Archived/older invoices |
src/stores/yjs/types.ts defines current TypeScript shapes and src/stores/yjs/validation.ts validates current and supported historical data. Existing IndexedDB, Drive, and backup data are live customer contracts.
-
Create clients and projects, organize tasks/subtasks, and plan work by week.
-
Start, pause, resume, and stop one timer per project; stopping creates one time entry.
-
Record expenses and recurrences, organize tax-return periods, and track paid/claimed states.
-
Generate invoice drafts or quotes from unbilled work and expenses, finalize them, record payments, cancel finalized unpaid invoices as retained audit records, export/send valid documents, and undo supported billing operations.
Invoice preparation offers optional Save Draft and direct Finalize Invoice. Saved drafts have their own list, Continue Draft, explicit Refresh Work and confirmed Delete Draft actions. UI and agent commands use the same guarded operations and source selections. Reopening preserves captured prices and work; refresh deliberately replaces linked selections within the saved project/client scope and period, including client-only expenses. Drafts neither claim work nor advance invoice numbers; saved and unsaved previews/PDFs are visibly unissued. Finalization checks current complete history, pricing consistency and number availability before the replay-safe billing operation; sending/payment follow finalization.
-
Review dashboard metrics and reports, then export CSV, PDF, ZIP, backup, or accountant artifacts.
-
Optionally connect Google Drive or Dropbox using manual, backup, or bidirectional sync modes.
-
Optionally pair a same-device agent bridge and grant scoped business-action access.
-
The deployed Pro release boundary gives Free one active client and a useful Reports Overview for the current local calendar month. An optional no-card trial, Pro subscription, or owner-issued complimentary grant unlocks unlimited active clients, advanced report tabs/outputs, and TaskTime-hosted sending. Existing/imported/synced records, manual email delivery, PDFs, tax bookkeeping, sync/backups, portability, and core agents remain available. Launch packaging is only Free and Pro; the founding Pro offer is
EUR 39/yearfor the first 250 paid canonical principals, after which new acquisition automatically uses theEUR 59/yearstandard offer. Existing continuous/recoverable founding subscriptions remain on their founding Price. Permanent complimentary grants are owner-administered through a private audited operation, consume no founding place or trial eligibility, and create no Stripe state. Explicit billing-profile deletion transactionally revokes an active grant while retaining its audit history. An isolated rehearsal validates the same lifecycle without production effects.
The dashboard separates Today/Upcoming actions from fixed-timeframe summaries
and preset-period reports with preceding-period trends. Its stacked chart uses
actual saved time plus a read-only active-timer projection sampled each minute
while visible. Tracked cards share that projection, with pause and stop identity
handling; unbilled estimates and payment totals retain saved-record billing and
currency semantics. Live ticks never write or sync product data. While a task
timer is running, the Dashboard keeps other task rows in that project visibly
and natively disabled across Today, Upcoming, and Tasks, including every route
into task details; the timer-owning task and paused project timers remain usable.
Recurring-task
Disable recurrence/Enable recurrence controls use calendar-off/calendar-check
actions in task menus and remain separate from timers: optional persisted pause
state suppresses due work until Enable,
which establishes a local date boundary without catch-up. Disabled state appears
as a neutral calendar-off Disabled schedule tag inline with the repeat description
in task details and in place of the normal recurrence tag in task lists; task titles
remain unchanged. Shared current task/project/client
classification excludes personal and unassigned work from billable dashboard and
report hours, exports, and unbilled summaries without rewriting saved flags or
invoice evidence. Automatic billable marking requires a non-personal project
with a client assignment. Task moves preserve finalized invoice attribution and
source claims; stale drafts must refresh if their source project/client or
billability changes before finalization.
Validated history loads through existing Yjs store APIs, with explicit loading
and retry. The chart bundle is lazy and precached for offline navigation; the
service worker tolerates Origin-header variation only for manifest-listed public
build assets. No persisted contract or /reports entitlement changes are involved.
The Expenses overview derives paid spending, recurring estimates, upcoming
occurrences, category shares, and recorded activity from existing active/archive
expense hooks. It preserves the original expense tabs/list and mutation paths,
uses canonical payment snapshots, and shares the lazy offline chart bundle.
Expense categories have optional original-color tags and separate add/edit
modals; historical references retain archived category identity. Expenses keep
category IDs, so later category name/group/color edits render immediately.
Planner expenses resolve the same live category identity for their left accent,
with a neutral fallback and no project/client color inheritance. A
recurrence category change can explicitly update only linked instances that
still carry its prior category; other recurrence changes remain future-only.
The expense form opens category management through the shared modal stack and
restores its unsaved draft on return. Account offers provider sign-in directly
in its header through the existing authentication flow. Its Sign in/Sign out
action follows the lifecycle-bound retained session independently of sync
transport; sign-out still requires a verified final sync before local deletion.
Retained Dropbox sessions recover through status retry; auth results reach the
sync runtime through the existing auth-change channel without replacing the session.
See spec/designs/billing-and-finance.md for metric scopes and phone ordering.
- Local data remains usable offline; cloud features are optional.
- Hosted billing actions wait for the lifecycle-selected cloud connection to finish its foreground reconnection. Portal-return reconciliation retries after canonical status recovery, while browser-offline billing UI derives from the browser network state rather than a generic retryable service/session failure. The exact origin-local lifecycle continues selecting its bounded signed plan during startup, reconnect, and offline use; transport readiness gates network work without downgrading or deleting that verified device state. Open tabs enforce signed expiry/clock rollback on a bounded timer and wake; delayed responses cannot extend access. Offline keys remain usable within the signed lifetime independently of HTTP freshness. Online action/quota data is discarded on transport failure, and delayed billing side effects are fenced to the initiating account. Browser/agent client writes re-read the plan at lock acquisition rather than retaining an earlier Pro decision.
- Cloud connection queues concurrent edits before I/O and defers automatic retry until setup completes. Manual bootstrap is strictly pull-only. Semantic remote validation runs after complete passes, using manifest revision readiness for loaded documents and deferring references into unloaded archives.
- Schema changes are additive or explicitly migrated and tested against historical data.
- UI badges, invoice composition, and agent invoice commands share the same read-only eligibility operation. Current billing ranges include the complete selected end date and assign cross-midnight entries by their local start date; finalized legacy invoices with markerless source entries retain conservative historical period matching.
- Invoice composition normalizes finite browser numeric values before preview and persistence; finalization reuses those semantics, reconciles only compatible duplicate task/project-breakdown copies, preserves supported merged-task pricing inheritance, and fails before claiming source records when financial evidence or merged topology conflicts.
- Browser and agent cancellation adapters share one journaled source-release operation. Cancellation revalidates current eligibility before the first journal write; retains the invoice number, original snapshots, and project links; releases only sources still owned by that invoice across active/historical/archive documents; never rewinds numbering; and conditionally converges late-arriving same-invoice claims after partial failure or stale Drive/archive replay without overwriting later billing.
- Canceled invoices remain read-only audit records in
core, are unmistakably marked in retained PDFs, and contribute zero to payment, revenue, output-tax, profit, outstanding, aging, statement, and project-allocation calculations. Portable backup1.5preserves the record while continuing to import every previously supported backup version. - Mark-as-unpaid is a paid-invoice correction only: it clears payment evidence while retaining billing-source claims and cannot reopen a sent, overdue, draft, or canceled invoice.
- UI hooks and agent commands share domain operations for timer lifecycle/recovered stops, protected manual time-entry mutations, task completion/recurrence state, duplicate-safe entity identity, protected expense deletion, and relationship-safe project/client/task writes.
- Timer start edits also share complete-history overlap checks and a fresh timer check before their Yjs write; paused edits keep the existing endpoint, including across calendar boundaries. Note-only edits leave timing untouched. The quick editor offers Today/Yesterday, preserves older timer dates, and previews the resulting interval and duration before saving.
- The entitlement policy is shared across browser and agent paths, with independent production rollout switches.
It gates only a net-increasing active-client create/restore transition,
advanced Reports/exports, and hosted Send.
/reports, its current-month Overview, and every tab remain visible; a locked advanced tab branches to a static section-specific preview before mounting protected modules, history, calculations, rows, or export builders. Import/restore/sync never discards or auto-archives an over-limit client. The universally Free first-client slot is safe even before plan status resolves. Billing context publishes one derived plan-plus-connection state so Reports, Plan & Billing, and hosted-email UI keep verified access separate from temporary transport readiness. Reports Overview and locked advanced previews consume the same Get Pro decision; preview copy distinguishes fresh acquisition, browser-offline, automatic-reconnect, and explicit-reconnect states instead of flattening them into account confirmation. Hosted email applies the same state distinction instead of presenting temporary recovery as a new upgrade. - The guarded loopback development stack exercises the normal provider-bound browser policy against local Worker/D1 state, Stripe test mode, and the configured email provider. It has no synthetic billing-state selector and the product UI is the same as production. Its local assertions, test-mode Stripe state, and local D1 rows cannot authorize production or establish deployment evidence.
- Automatic recurring-task status reads never clear persisted skip evidence; paid cross-currency expense mutations prepare snapshots before committing; canonical agent unbilled queries load complete local history.
- Sync mode trigger semantics in
AGENTS.mdare durable behavior. - Sync mode performs a lightweight manifest check every five minutes only while visible, coalesces tab-visible/browser-online signals within one second into one foreground pass, and lets genuine pending local work blocked by an active pass or cross-tab lock retry with bounded backoff after the lock can be released. Connection, full-sync and lazy-document writers serialize against each other, including concurrent archive requests and browsers without Web Locks. Callback-owned loads serialize under the existing lock and drain before the pass resumes. Lazy loads track edits before I/O or waiting.
- Provider-grant revocation is confirmed before the browser clears its Worker session; transient refresh, rate-limit, provider-status, and revocation failures preserve retryable credentials and runtime state. Google Drive and Dropbox expose the same Disconnect and Wipe data & disconnect flows, and Account sign-out/deletion reuse the active-provider lifecycle rather than assuming Google.
- Direct transport keeps Google access tokens in one per-tab module instance only, clears them on expiry/session generation/cross-tab invalidation, removes any retired persisted-token record, deduplicates concurrent same-tab session validation, and keeps all Worker/Google API traffic outside service-worker Cache Storage. Direct reads/writes use retry-safe Google operations and the Worker does not receive routine Drive file bodies.
- In the provider-neutral path, the active cloud session also authenticates hosted email, privacy-safe synced metrics, and future Pro state. Provider subjects are domain-separated hashes; transfer links them only after target readback verification and before activation. This control-plane identity does not expose product records or turn the Worker into a file proxy.
- Destructive data, billing, deletion, and sync actions require explicit intent and safe preview/confirmation where available.
- Agent access is loopback-only with short-lived pairing, scoped permissions, approvals, rate limits, and revocation. App-session bearer tokens are bounded to memory/current-tab resume state. Same-profile browser reopen uses a non-exportable origin-local P-256 key and replay-safe challenge to obtain a fresh token; the matching public authorization remains only in the live bridge, so Gateway restart requires pairing.
- Private Worker source, secrets, provider identifiers, and internal operational material do not enter the public repository.
All Node/npm work runs through Docker-backed Make targets. Vitest tests are colocated throughout src/; integration tests live in src/test/integration/; Playwright browser flows live in e2e/. Repository-wide TypeScript checking is a required release gate, and CI runs make release-gate.
See ARCHITECTURE_MAP.md for module navigation, spec/roadmap.md and status/_status.md for current work, and spec/ambiguities.md for unresolved decisions.