ChatKcal Next Native Rewrite
- Date: 2026-08-08
- Status: Ready for Review
- Scope: L3 architecture and platform rewrite
- Tracking: Beads issue pending; the bd executable is unavailable in the current environment
- Latest confirmed direction: Self-hosted Supabase hub
- Confirmed deployment direction: Docker Engine inside the owner's existing WSL2 Linux distribution; keep Tailscale on Windows
- Open technical gate: Adopt PowerSync only if its Rust/Tauri spike passes
1. Context
ChatKcal Next is a planned personal, local-first rewrite of ChatKcal as a Tauri application. The immediate delivery order is Windows first, followed by Android, while keeping Android compilable from the start. Both clients will share Rust domain, persistence, query, and synchronization logic.
Every installed application will remain usable offline with its own SQLite database. A production self-hosted Supabase stack will provide the central PostgreSQL system of record, PostgREST/RPC, Realtime, Auth, and administration. A thin Rust MCP service will expose authenticated meal tools through Tailscale Funnel. The first deployment will run on Docker Engine inside the owner's WSL2 Linux distribution, with Windows providing host networking and Tailscale.
PowerSync receives a bounded Milestone 0 spike because its Tauri and Rust SDKs are currently alpha. If it proves that Rust can continue to own SQLite queries, mutations, and sorting on both Windows and Android, it replaces most custom sync plumbing. Otherwise ChatKcal uses its planned Rust outbox and cursor protocol over Supabase RPC and Realtime.
This is a fresh-start system. Migrating records from the legacy AWS/DynamoDB deployment or browser IndexedDB is explicitly outside the initial scope.
1.1 Confirmed product direction
- ChatKcal is strictly personal and single-owner for the foreseeable future.
- The native app is the primary product surface.
- Windows is the first fully delivered client.
- Android follows the Windows vertical slice and shares the same Rust core.
- Every client writes locally first and syncs later.
- Multiple offline writers are supported.
- Record conflicts use deterministic last-write-wins resolution.
- Self-hosted Supabase provides the central convergence, auth, API, realtime, administration, and backup boundary.
- A successful remote MCP write becomes visible in connected apps near-real-time.
- MCP is exposed through a public Tailscale Funnel URL.
- Supabase Auth sessions and OAuth credentials must be rotatable and revocable.
- Supabase Auth OAuth 2.1 is the chosen ChatGPT/MCP identity provider, subject to a Milestone 0 beta-compatibility test.
- Docker Engine runs inside the owner's existing WSL2 Linux distribution; the project does not install or depend on Docker Desktop.
- Tailscale stays on Windows rather than running a second node inside WSL.
- A clean database is expected; there is no legacy data import requirement.
1.2 Success statement
A successful first production release allows the owner to:
- install ChatKcal on Windows;
- create, edit, delete, and browse meals without a network connection;
- see locally committed changes immediately;
- reconnect and synchronize changes with the hub without duplication;
- see a meal logged on another device or through MCP appear within seconds while the app is online;
- rotate credentials without rebuilding the applications; and
- restore the central database from a verified backup.
2. Goals and non-goals
2.1 Goals
- Native Windows packaging and installation.
- An Android-compatible codebase from the first milestone.
- SQLite as the durable authority for each installed client.
- Rust-owned validation, calculations, sorting, lookup, persistence, and sync.
- A thin React/TypeScript presentation layer based on the current visual design.
- A production self-hosted Supabase stack backed by PostgreSQL.
- PostgREST and PostgreSQL RPC functions instead of a custom general-purpose sync HTTP server.
- Supabase Realtime as a wake-up path, never the sole durable copy of a change.
- Supabase Auth for native sessions and OAuth 2.1 MCP authentication.
- A bounded PowerSync Tauri/Rust evaluation with an explicit fallback.
- Durable, idempotent, offline-capable synchronization.
- Near-real-time change notification with polling/reconnect fallback.
- A thin Rust Streamable HTTP MCP endpoint with focused meal tools.
- A public HTTPS ingress provided by Tailscale Funnel.
- A private Supabase REST/Realtime surface provided through Tailscale Serve.
- A schema and client contract portable to managed Supabase later.
- Docker Compose deployment with verified backup and restore procedures.
- Structured operational logs that avoid meal content by default.
2.2 Non-goals for the first release
- Multiple users, households, sharing, roles, or tenant isolation.
- Multi-user signup, social login, or a general public account system.
- Migrating legacy AWS, DynamoDB, Cognito, or IndexedDB data.
- Direct client access to PostgreSQL.
- Treating Supabase Realtime notifications as durable offline replication.
- Public marketplace distribution of the ChatGPT connector.
- iOS, macOS, or Linux packaging.
- HealthKit, Google Fit, widgets, background reminders, or wearable integration.
- Arbitrary natural-language food parsing inside Supabase or the MCP service.
- Building a custom replacement for Supabase Auth, PostgREST, Realtime, Studio, or the API gateway.
- Exposing the Supabase CLI development stack, Studio, PostgreSQL, or the unrestricted REST API to the public internet.
- Automatic conflict review UI. Deterministic last-write-wins is accepted.
3. Architecture decisions
| ID | Decision | Status | Consequence |
|---|---|---|---|
| CK-NEXT-001 | Build ChatKcal Next in a separate repository | Proposed | The legacy AWS application remains stable and can be archived independently |
| CK-NEXT-002 | Use one monorepo for native apps, Rust crates, MCP, Supabase migrations, and deployment | Proposed | Contracts, SQL functions, and domain rules can change atomically |
| CK-NEXT-003 | Use Tauri v2 with the existing React/Vite visual language | Confirmed direction | UI assets can be ported without retaining AWS runtime dependencies |
| CK-NEXT-004 | Use SQLite on each device and PostgreSQL in self-hosted Supabase | Confirmed direction | Devices remain offline-capable while Supabase provides central durability |
| CK-NEXT-005 | Put durable application rules behind Rust commands | Confirmed direction | React owns presentation and transient interaction state, not persisted truth |
| CK-NEXT-006 | Use PowerSync only if a Windows/Android Rust-ownership spike passes | Proposed gate | Alpha dependencies cannot silently redefine the UI/Rust/SQLite boundary |
| CK-NEXT-007 | Retain a Rust outbox plus cursor fallback if the PowerSync spike fails | Proposed fallback | Delivery does not depend on an immature SDK |
| CK-NEXT-008 | Use a hybrid logical clock and actor ID for deterministic last-write-wins | Proposed | Offline writes converge without relying solely on server receipt order |
| CK-NEXT-009 | Use Supabase Realtime only as a wake-up signal | Confirmed direction | Missed WebSocket events cannot cause data loss |
| CK-NEXT-010 | Use Supabase Auth sessions for native clients and OAuth 2.1 for MCP | Confirmed direction | Auth, PKCE, refresh, revoke, discovery, DCR, and JWKS are consistent |
| CK-NEXT-011 | Split private Supabase ingress from public Auth/MCP ingress | Confirmed direction | Funnel exposes only OAuth-required routes, consent UI, and MCP |
| CK-NEXT-012 | Use the official production self-hosted Supabase stack, not supabase start |
Confirmed direction | The deployed stack can be hardened and operated |
| CK-NEXT-013 | Run Docker Engine inside the existing WSL2 distro and keep Tailscale on Windows | Confirmed direction | Uses the lightweight Linux environment the owner already operates |
| CK-NEXT-014 | Preserve schema and API portability to managed Supabase | Confirmed direction | Later migration changes hosting and credentials, not application architecture |
4. Repository strategy
Create a sibling repository named chatkcal-next. Do not place the rewrite inside this repository and do not make either project a runtime dependency of the other.
The legacy repository remains:
- the source of truth for the visual design and interaction language;
- a reference for existing domain fields and validation behavior;
- a source of selected React components, CSS, icons, and tests; and
- the historical record of the AWS architecture.
ChatKcal Next becomes the successor. At cutover, this repository can be marked legacy or archived after any useful UI history has been preserved.
4.1 Proposed layout
chatkcal-next/
apps/
chatkcal/
src/ # React/TypeScript presentation
src-tauri/ # Tauri shell and command adapters
crates/
domain/ # entities, validation, calculations, clocks
local-storage/ # SQLite repositories and migrations
sync-client/ # PowerSync adapter or outbox/RPC fallback
application/ # use cases shared by Tauri and MCP
services/
mcp-server/ # thin Rust Streamable HTTP MCP adapter
supabase/
migrations/ # tables, RLS, triggers, and RPC functions
tests/ # pgTAP/integration and merge fixtures
infra/
supabase/ # production compose overrides and secrets template
reverse-proxy/ # private/public route allowlists
backup/ # dump, verify, retention, and restore
tailscale/ # Serve/Funnel runbooks
experiments/
powersync/ # bounded Tauri/Rust/Android spike, then archived
docs/
architecture/ # accepted state after implementation
project_management/
Taskfile.yml
Cargo.toml # Rust workspace
package.json # Bun workspace
The Rust application crate owns local policy. PostgreSQL constraints and RPC functions independently guard central writes and implement the same revision ordering against shared conformance fixtures. Tauri commands and MCP tools are adapters; neither React nor transport code owns meal rules.
5. System context
flowchart LR
owner["Owner"]
chatgpt["ChatGPT or external MCP client"]
subgraph windows["Windows device"]
uiw["React UI"]
rustw["Tauri + Rust application"]
sqlitew[("SQLite")]
uiw <--> rustw
rustw <--> sqlitew
end
subgraph android["Android device"]
uia["React UI"]
rusta["Tauri + Rust application"]
sqlitea[("SQLite")]
uia <--> rusta
rusta <--> sqlitea
end
subgraph host["Windows host + WSL2 Docker Engine"]
serve["Tailscale Serve: private API ingress"]
funnel["Tailscale Funnel: public Auth + MCP ingress"]
proxy["Deny-by-default reverse proxy"]
mcp["Thin Rust MCP server"]
kong["Supabase API gateway"]
auth["Supabase Auth + OAuth 2.1"]
rest["PostgREST + RPC"]
realtime["Supabase Realtime"]
postgres[("Supabase PostgreSQL")]
serve --> proxy
funnel --> proxy
proxy --> kong
proxy --> mcp
kong --> auth
kong --> rest
kong --> realtime
mcp --> auth
mcp --> rest
auth --> postgres
rest --> postgres
realtime --> postgres
end
owner --> uiw
owner --> uia
rustw <-->|"Auth + REST/RPC + Realtime"| serve
rusta <-->|"Auth + REST/RPC + Realtime"| serve
chatgpt <-->|"HTTPS Streamable MCP"| funnel
5.1 Trust boundaries
- UI to Rust: local Tauri IPC. Only an explicit command allowlist is exposed. The webview cannot access databases or credentials directly.
- Device to Supabase: private tailnet HTTPS ingress. Supabase Auth and RLS still apply; tailnet membership alone is insufficient.
- Internet to Auth/MCP: Funnel exposes OAuth discovery/token routes, the consent UI, and MCP only. REST, Realtime, Studio, health, and PostgreSQL are denied from the public route table.
- Containers to PostgreSQL: private Compose network and named volume only. PostgreSQL is never published through Serve or Funnel.
- MCP to Supabase: the MCP server validates Supabase JWTs through JWKS and forwards user context to RPC/RLS rather than using unrestricted SQL.
- Windows to WSL2: Docker publishes the allowlisted proxy port inside WSL; WSL localhost forwarding makes it reachable from Windows. Tailscale runs only on Windows to avoid nested tailnets.
6. Component responsibilities
| Component | Owns | Deliberately does not own |
|---|---|---|
| React UI | rendering, navigation, form state, animation, accessibility, optimistic feedback | durable records, sorting policy, totals, validation, sync conflict decisions |
| Tauri command adapter | IPC serialization, command authorization, error mapping | meal rules or SQL |
| Rust application crate | create/update/delete/query use cases and local transaction boundaries | HTTP, MCP, or UI details |
| Rust domain crate | entities, validation, nutrient rounding, logical dates, ordering, and HLC generation | database engines and network transport |
| SQLite repository | local durable records and migrations; outbox/cursor only in fallback mode | cross-device authority |
| PowerSync adapter | SQLite replication and upload queue only if the spike passes | product validation or LWW policy |
| Fallback sync client | outbox, RPC push/pull, Realtime reconnect, retries, and sync status | central merge policy |
| PostgreSQL RPC/functions | validation backstop, mutation dedupe, HLC comparison, canonical merge, durable changes | UI presentation or local queries |
| Supabase Auth | native sessions, OAuth 2.1, PKCE, DCR, refresh, revoke, and JWKS | application authorization policy beyond claims |
| PostgREST | REST/RPC transport derived from PostgreSQL | offline replication or domain policy |
| Supabase Realtime | low-latency wake-up notifications | durable delivery |
| MCP adapter | tool schemas, annotations, JWT context, idempotency, and structured results | direct unrestricted SQL writes |
| Reverse proxy | explicit private/public route allowlists, limits, and headers | identity or application policy |
| WSL2 Docker Engine | Linux-container runtime, named volumes, Compose, and published proxy port | Tailscale identity or backup policy |
| Tailscale Serve/Funnel | private/public HTTPS reachability | identity or authorization |
7. Local application design
7.1 UI boundary
“Display only” is interpreted as a thin presentation layer rather than a stateless interface. React may own:
- text currently being edited;
- open dialogs and selected tabs;
- navigation and selected date;
- loading, success, and error presentation;
- animation state; and
- an optimistic projection returned by Rust.
React must not own:
- the canonical meal list;
- daily totals;
- persisted settings;
- date normalization;
- sorting or filtering policy;
- validation;
- identifier generation;
- merge decisions; or
- direct network synchronization.
The current application already separates meal services from most UI code in frontend/src/providers/ServiceProvider.tsx and frontend/src/services/IMealService.ts. That seam should guide the port, while AWS Amplify, Cognito, GraphQL, promotion, and Dexie implementations are left behind.
The current frontend/src/hooks/useMeals.ts calculates totals and performs domain normalization in React. ChatKcal Next moves those responsibilities into Rust and returns a ready-to-render day view.
7.2 Initial Tauri commands
| Command | Input | Output |
|---|---|---|
| get_day | logical date | sorted meals, totals, targets, sync status |
| create_meal | meal draft and occurrence time | committed meal and refreshed totals |
| update_meal | meal ID and changed fields | committed meal and refreshed totals |
| delete_meal | meal ID | tombstone result and refreshed totals |
| get_settings | none | owner settings |
| update_settings | changed settings | committed settings |
| request_sync | reason | latest sync outcome |
| get_sync_status | none | pending count, last success, connection, last error |
| sign_in | owner credentials | sanitized Supabase session status |
| sign_out | none | cleared local session state |
| get_auth_status | none | signed-in, refresh-needed, or rejected state |
Commands return structured domain errors. Human-readable strings are mapped in the UI; storage or network error text is not exposed directly.
7.3 Local database
SQLite migrations initially create:
- meals — current materialized meal rows, including tombstones;
- settings — the single owner settings row;
- sync_outbox — unsent mutations in fallback mode only;
- sync_state — device ID, pull cursor, and last observed logical clock in fallback mode, or PowerSync connection/checkpoint state when adopted;
- applied_mutations — bounded fallback deduplication history if required; and
- schema_migrations — migration version history.
With PowerSync, each user action commits through its SQLite extension and is automatically represented in the upload queue. With the fallback client, the domain row and outbox mutation commit in one SQLite transaction. Either path must prove that a process stop after local commit cannot lose synchronization work.
The SQLite file is stored in the Tauri application data directory. Sync credentials are stored in an encrypted secure store such as the official Tauri Stronghold plugin, subject to a Windows and Android validation during the scaffold milestone. Supabase refresh tokens and any recovery credentials are not stored in application tables or frontend storage.
8. Domain and data model
8.1 Meal
The new meal record contains:
| Field | Purpose |
|---|---|
| id | client-generated UUID; stable across every replica |
| occurred_at | UTC instant representing when the meal occurred |
| logical_date | owner-local YYYY-MM-DD used for daily views |
| timezone | IANA timezone used to derive logical_date |
| summary | human-readable meal description |
| emoji | optional display emoji |
| calories | non-negative value rounded to two decimals |
| protein | non-negative grams rounded to two decimals |
| carbs | non-negative grams rounded to two decimals |
| fat | non-negative grams rounded to two decimals |
| extended_nutrients | normalized key/value map rounded to two decimals |
| notes | optional owner notes |
| revision | hybrid logical clock plus actor ID |
| created_at | first creation time |
| updated_at | most recent mutation time |
| deleted_at | tombstone timestamp, null for active records |
The current domain shape in frontend/src/types/index.ts and validation behavior in shared/src/schemas/mealSchema.ts are the starting reference, not schemas to copy unchanged.
8.2 Settings
Settings are a single replicated entity containing:
- calorie and macro targets;
- display units;
- extended nutrient definitions and targets;
- default IANA timezone;
- UI preferences that must follow the owner between devices; and
- its own revision and tombstone metadata.
Device-specific presentation preferences, such as window size, stay local and do not sync.
8.3 Sync metadata
Every mutation contains:
- mutation_id — a globally unique idempotency key;
- actor_id — stable device ID or server MCP actor;
- entity_kind and entity_id;
- operation — upsert or delete;
- revision — hybrid logical clock value;
- complete entity snapshot for v1;
- created_at; and
- source — windows, android, or MCP.
Complete snapshots keep the first protocol simple and avoid field-level merge ambiguity. Payload size is negligible for personal meal data.
9. Conflict and ordering policy
Last-write-wins is evaluated using the tuple:
The actor ID is the deterministic tie-breaker. The same comparator is used by SQLite clients and PostgreSQL.
Rules:
- A mutation with a newer revision replaces the existing row.
- A mutation with the same revision and higher actor ID wins deterministically.
- An older mutation is recorded as processed but does not replace the row.
- Delete is represented as a tombstone and participates in the same ordering.
- A later update can intentionally restore a tombstoned record.
- Tombstones are retained indefinitely in v1. Compaction requires a future protocol proving every active device has advanced beyond the deletion.
- Replaying a mutation_id returns the original outcome and never creates a duplicate.
Hybrid logical clocks are preferred over wall-clock timestamps because Windows, Android, and the server can be offline or have clock skew. Physical time remains available for display and diagnostics, while revision ordering is monotonic per actor.
10. Synchronization strategy
10.1 PowerSync decision gate
PowerSync is the preferred reduction in custom sync work only if its alpha Tauri and Rust integrations pass all of these Milestone 0 gates:
- one code path builds and launches on Windows and Android;
- Rust owns SQLite reads, writes, sorting, totals, and subscriptions;
- React receives rendered view models only and does not import a PowerSync SDK;
- an offline Rust write enters the upload queue atomically;
- Supabase Auth JWTs work with the self-hosted PowerSync service;
- uploads can call one Supabase RPC that applies mutation IDs and HLC LWW;
- two clients converge after simultaneous offline update/delete cases;
- the Open Edition licence and deployment model remain acceptable; and
- removing PowerSync remains possible without changing domain entities or UI command contracts.
If any mandatory gate fails, archive the spike and use the fallback below. PowerSync is server-authoritative: PostgreSQL RPC still owns validation, idempotency, and LWW acceptance. PowerSync supplies replication, checkpoints, the SQLite upload queue, and reactive change delivery.
10.2 Fallback durable flow
sequenceDiagram
participant UI as React UI
participant App as Rust application
participant DB as SQLite
participant Sync as Sync worker
participant API as Supabase PostgREST/RPC
participant PG as Supabase PostgreSQL
UI->>App: create_meal(draft)
App->>DB: transaction: meal + outbox mutation
DB-->>App: committed local view
App-->>UI: render immediately
Sync->>DB: read pending outbox batch
Sync->>API: POST /rest/v1/rpc/apply_mutations
API->>PG: transaction: dedupe + merge + append change
PG-->>API: outcome + latest cursor
API-->>Sync: acknowledged mutation IDs
Sync->>DB: remove acknowledged outbox rows
Sync->>API: POST /rest/v1/rpc/pull_changes
API->>PG: read ordered change batch
PG-->>API: changes + next cursor
API-->>Sync: changes + next cursor
Sync->>DB: transaction: merge changes + advance cursor
Sync-->>UI: Tauri event: day invalidated
UI->>App: get_day(date)
10.3 Private and public API outline
| Endpoint | Exposure | Purpose |
|---|---|---|
| POST /rest/v1/rpc/apply_mutations | Serve only | fallback or PowerSync upload with idempotent HLC LWW |
| POST /rest/v1/rpc/pull_changes | Serve only | durable fallback cursor pull |
| WebSocket /realtime/v1 | Serve only | wake connected clients after PostgreSQL changes |
| /auth/v1 token/session routes | Serve only | native client authentication |
| /auth/v1/oauth/* and required discovery | Funnel + Serve | ChatGPT OAuth 2.1, PKCE, DCR, refresh, and revoke |
| authorization consent UI | Funnel + Serve | explicit owner approval |
| POST/GET /mcp | Funnel + Serve | Streamable HTTP MCP transport |
| Studio, pg-meta, unrestricted REST, health | localhost/admin | never public |
RPC parameter and result shapes are versioned in Supabase migrations and represented by Rust transport types. Shared fixtures verify SQL and Rust revision ordering. Generated TypeScript bindings exist only for UI-visible Tauri commands.
10.4 Upload semantics
- Push accepts a bounded ordered batch, initially at most 100 mutations.
- Supabase Auth authenticates before PostgREST invokes the function.
- Each mutation is independently idempotent.
- Deduplication, merged-row writes, and durable change append occur in one PostgreSQL RPC transaction.
- The response identifies applied, ignored-as-older, and already-processed mutations.
- A partial transport failure is retried with the same mutation IDs.
- Invalid mutations are rejected explicitly and remain visible in client sync diagnostics rather than being silently dropped.
10.5 Fallback pull semantics
- PostgreSQL assigns an increasing change cursor after every accepted merged state transition.
- Pull returns changes strictly ordered by cursor.
- Applying a pull batch and advancing the local cursor is one SQLite transaction.
- Pagination resumes from the last committed cursor.
- A full snapshot/resync endpoint is added only if testing demonstrates a need.
10.6 Near-real-time behavior
Supabase Realtime carries only a wake-up that relevant rows changed. It is not the durable source of truth and does not replace a checkpointed sync pass.
The sync worker pulls when:
- a Realtime database-change notification arrives;
- the application starts;
- the application regains focus;
- the network becomes available;
- a local write commits;
- a retry backoff expires; or
- a low-frequency active-app poll detects missed events.
Target behavior while both ends are connected is remote-write-to-visible-UI in under two seconds on the local network. This is a service objective, not a correctness dependency. Android background suspension is expected; it catches up when resumed.
11. Self-hosted Supabase hub design
11.1 Production stack and service selection
The hub is the official production self-hosted Supabase Docker stack, trimmed to the capabilities ChatKcal needs:
- PostgreSQL as the canonical database;
- GoTrue/Supabase Auth for native sessions and OAuth 2.1;
- PostgREST for authenticated table reads and RPC calls;
- Realtime for low-latency wake-ups;
- Kong as the internal Supabase API gateway; and
- Studio plus postgres-meta for local-only administration.
Storage, imgproxy, Edge Runtime, and analytics are omitted initially. The Supabase CLI development stack may be used for disposable local testing, but it is not the deployed hub. A separate thin Rust MCP service exposes Streamable HTTP, validates Supabase-issued access tokens, and invokes the same PostgreSQL RPC functions used by native sync. A deny-by-default reverse proxy is the only published container entrypoint and keeps private REST, Realtime, administration, health, and database routes away from Funnel.
11.2 PostgreSQL tables
The first schema contains:
- meals — canonical merged materialized rows;
- settings — canonical owner settings;
- changes — durable cursor-ordered accepted changes;
- processed_mutations — mutation idempotency records;
- mcp_idempotency — remote tool request deduplication; and
- audit_events — security-sensitive external actions without sensitive meal text.
Supabase owns its Auth schemas and token lifecycle. Application rows include an owner UUID referencing the Auth user, and row-level security permits only that owner. PostgreSQL constraints repeat core invariants. Versioned RPC functions such as apply_mutations, pull_changes, and the MCP write functions default to security-invoker behavior so RLS continues to apply, then enforce validation, idempotency, and deterministic LWW inside transactions. Any exceptional security-definer helper must explicitly verify auth.uid(), revoke broad execute grants, and receive dedicated bypass tests. Rust remains the local domain-policy owner and tests its rules against the server functions.
11.3 Supabase portability
The portability target is:
The schema, RLS policies, PostgREST RPC contract, and Auth model can move from self-hosted Supabase to managed Supabase without changing the app's domain or UI contracts.
The initial design therefore:
- treats Supabase Auth, PostgREST/RPC, Realtime, and RLS as intentional platform dependencies;
- keeps all application migrations, functions, triggers, grants, and RLS policies in source control;
- confines Supabase-specific integration to the sync/auth adapters;
- uses stable REST/RPC and JWT contracts rather than internal service tables;
- treats Realtime as an optimization, never the only copy of a change; and
- continuously tests migrations against a clean self-hosted Supabase stack.
Moving to managed Supabase is a deployment migration, not a drop-in Docker configuration change: restore the database, recreate secrets and Auth settings, verify RLS and OAuth redirect URLs, then repoint client configuration.
12. Authentication and security
12.1 Supabase Auth model
- Bootstrap exactly one owner account and disable public sign-up.
- Native apps use Supabase Auth sessions over the private Serve route.
- Access tokens are short-lived JWTs; refresh tokens remain in Tauri Stronghold or the platform credential store, never React persistence.
- PostgreSQL RLS keys all application data to auth.uid().
- Device sign-out and administrator session revocation provide credential rotation without a bespoke token table.
- Service-role credentials remain container-only and are never shipped to an app or accepted by the public MCP endpoint.
12.2 OAuth 2.1 for ChatGPT MCP
Supabase Auth includes an OAuth 2.1 server with PKCE, dynamic client registration, discovery metadata, and JWKS support, explicitly including MCP authorization. ChatGPT therefore uses the same single-owner Supabase identity as the native apps. The public surface contains only the required Auth OAuth routes, the owner consent UI, and /mcp; it does not expose the Supabase REST API.
A static personal bearer key remains an optional later convenience for generic clients that support custom headers. It is not the primary ChatGPT connector design because ChatGPT's authenticated MCP connection expects OAuth.
12.3 OAuth compatibility spike
Supabase OAuth server support is beta, so Milestone 0 must prove the exact self-hosted flow before product code depends on it:
- configure the official self-hosted Auth image with OAuth server mode, dynamic registration, the authorization path, issuer, and redirect URLs;
- expose only required discovery, authorization, token, registration, JWKS, consent, and MCP routes through Funnel;
- connect ChatGPT developer mode and invoke a read-only MCP test tool;
- verify discovery, dynamic client registration, PKCE, access-token refresh, revoke/relink, audience, scopes, and expiry behavior;
- confirm the MCP service can validate tokens from cached JWKS without a service-role secret; and
- record configuration, browser consent, recovery, and upgrade behavior.
If the beta implementation fails a required compatibility gate, keep Auth and MCP behind adapters and evaluate managed Supabase Auth or another standards-compliant OAuth provider. Do not implement a home-grown OAuth server inside the app milestone.
12.4 Public ingress controls
- Funnel exposes only /mcp, the owner consent page, and required OAuth routes.
- The public listener has strict body, concurrency, and timeout limits.
- Authentication is required before tool execution.
- Repeated authentication failure is rate-limited.
- CORS is not treated as an authentication control.
- Destructive MCP tools are annotated accurately and designed for client confirmation.
- Every external mutation records actor, tool, mutation ID, outcome, and timing.
- Meal descriptions and notes are excluded from logs by default.
- PostgREST data routes, Realtime, PostgreSQL, Studio, metrics, backup, and administration are never routed through Funnel.
13. MCP contract
13.1 Initial tools
| Tool | Purpose | Safety |
|---|---|---|
| log_meal | create one meal with structured nutrition fields | write, idempotency key required |
| get_day | return meals and totals for one logical date | read-only |
| update_meal | update an identified meal | write |
| delete_meal | tombstone an identified meal | destructive |
| get_targets | return active nutrition targets | read-only |
Updating targets can be added after the basic connector is reliable.
13.2 Tool rules
- Tools use narrow action-oriented names and explicit JSON schemas.
- The server validates every value through the domain crate.
- log_meal accepts an occurrence time and timezone; missing values use the owner default timezone and current time.
- All write tools require a client request ID used as an idempotency key.
- Structured results return the canonical meal, revision, and mutation ID.
- Errors distinguish validation, authentication, conflict-ignored, not-found, rate-limited, and internal failures.
- Read-only and destructive annotations are accurate.
- Tool descriptions do not instruct the model to infer nutrients when the caller has not supplied them.
- The MCP layer does not call an LLM. ChatGPT or the external caller is responsible for converting natural language into structured tool arguments.
13.3 Remote write journey
- The owner asks ChatGPT to log a meal.
- ChatGPT selects log_meal and produces structured arguments.
- Funnel forwards the HTTPS MCP request through the public reverse-proxy route.
- The MCP service validates the Supabase token, audience, owner, and scopes.
- The adapter validates its schema and calls a versioned PostgreSQL RPC.
- PostgreSQL commits the canonical meal, mutation dedupe row, change row, and audit event in one transaction.
- Supabase Realtime wakes connected sync clients.
- PowerSync, if accepted, replicates the authoritative row into SQLite; otherwise the Rust worker performs a durable cursor pull.
- SQLite applies the state transactionally.
- Rust invalidates the affected day view and React re-renders it.
14. Deployment topology
14.1 Initial server deployment
Use Docker Engine inside the owner's existing WSL2 Linux distribution on the initial Windows tailnet machine. Deploy the official production self-hosted Supabase Compose stack, trimmed as described above, plus the Rust MCP service, deny-by-default reverse proxy, backup job, and health checks. Add a self-hosted PowerSync service only if its Milestone 0 gate passes.
Operational rules:
- use Docker-managed Linux named volumes for PostgreSQL data; never bind the database directory to /mnt/c or another NTFS path;
- run the Docker daemon as a Linux systemd service inside WSL and document explicit start, stop, status, and upgrade commands;
- do not install Docker Desktop alongside the WSL-managed Engine;
- keep Tailscale installed once on Windows, not nested inside WSL;
- publish only the reverse-proxy port to Windows;
- route private native Auth, REST/RPC, and Realtime through Tailscale Serve;
- route only OAuth-required paths, consent, and /mcp through Funnel;
- enable WSL autoMemoryReclaim and apply measured WSL resource limits where appropriate;
- do not require WSL mirrored networking, because Docker-published ports have documented edge cases in that mode; and
- verify startup after sign-in, machine reboot, container restart, and WSL shutdown before treating the host as always-on.
On the current development machine, WSL 2.6.1 and Tailscale 1.98.8 are already present. Docker runtime installation remains an owner-operated WSL step; the project supplies configuration and verification commands but must not install Docker Desktop or alter the WSL distro without explicit instruction.
14.2 Windows runtime decision and capacity
Docker Engine inside the existing WSL2 distribution is the confirmed runtime.
It avoids Docker Desktop's management processes and uses the Linux environment
the owner already keeps active. The tradeoff is explicit ownership of daemon
startup, systemd behavior, updates, Linux permissions, Windows localhost
forwarding, and recovery after wsl --shutdown.
The official full Supabase stack lists 4 GB RAM, two CPU cores, and 40 GB SSD as minimum resources, with 8 GB RAM, four cores, and 80 GB SSD recommended. The trimmed deployment should use less, but no lower budget is promised before measurement. Milestone 0 records idle, active-sync, backup, and restore CPU, memory, disk, and wake-up behavior before applying WSL or container limits.
WSL systemd services do not by themselves keep a WSL instance alive. Milestone 0 therefore verifies the actual user-session startup and shutdown behavior. If the service becomes truly unattended or must survive user sign-out, moving the same Compose deployment to a small Linux host or VM is preferable to adding Windows-specific lifecycle workarounds.
14.3 Availability behavior
If the hub, PostgreSQL, Tailscale, or the internet is unavailable:
- native CRUD continues against SQLite;
- mutations accumulate in PowerSync's durable upload queue or the Rust outbox;
- the UI shows pending work and the last successful sync;
- retries use bounded exponential backoff with jitter;
- no local record is discarded; and
- remote MCP logging is unavailable and must return a clear failure.
14.4 Backups
- Run a daily PostgreSQL logical backup including application and Supabase Auth schemas required for recovery.
- Encrypt backups at rest.
- Keep an initial 30-day rolling retention.
- Copy backups to a second tailnet device when available.
- Record backup time, size, checksum, and outcome.
- Perform and document a restore test before declaring the hub production-ready.
- SQLite remains an offline replica, not the central backup strategy.
15. UI migration strategy
Port, do not rewrite blindly.
The formal preservation baseline is captured in docs/project_management/logs/2026-08-08_chatkcal_next_design_preservation.md, with machine-readable values in docs/design/chatkcal-legacy-design-tokens.json. That artifact is normative for the initial visual port: it inventories the six canonical screenshots, extracts the current token system, classifies components as Keep/Adapt/Retire, and defines the side-by-side native parity gate.
Reuse candidates:
- color, spacing, typography, and responsive design tokens;
- favicon and icon assets from frontend/public;
- meal cards, daily totals, date controls, settings visuals, and empty states;
- accessibility labels and proven interaction patterns; and
- visual regression scenarios.
Replace or redesign:
- AWS Amplify and Cognito composition;
- GraphQL services and API access screens;
- Dexie and IndexedDB;
- guest promotion and registration flows;
- browser-PWA installation behavior;
- React-owned totals and data normalization; and
- assumptions that a browser refresh is the application lifecycle.
The 1,074-line frontend/src/pages/Dashboard.tsx should not be copied as one unit. Port behavior into cohesive feature components backed by the Rust command contract.
16. Execution plan
The user explicitly approved this Proposed Approach and Execution Plan on 2026-08-08. Execution begins with Milestone 0 and the end-to-end first slice.
Milestone 0 — Repository, platform, and risk spikes
Purpose: establish a clean successor repository and resolve the riskiest platform assumptions before product implementation.
Work:
- Create the separate chatkcal-next repository and protect its main branch.
- Add Bun, Cargo, and Taskfile workspaces.
- Scaffold Tauri v2 with React, TypeScript, Vite, and Windows/Android targets.
- Establish Rust crate dependency direction and generated IPC contract checks.
- Produce empty Windows and Android builds.
- Validate SQLite and secure session storage on both targets.
- Supply WSL Docker Engine prerequisites and operator commands, then deploy the trimmed official self-hosted Supabase stack once the owner-provided Engine is available.
- Prove named-volume persistence, Windows-host Tailscale routing, resource recovery, reboot startup, health checks, and a clean database restore.
- Run the Supabase Auth/ChatGPT/Funnel OAuth compatibility spike described above.
- Run the bounded PowerSync spike and record pass/fail evidence for every gate in Section 10.1. Select PowerSync or the Rust outbox/cursor fallback.
- Record accepted decisions as ADRs.
- Add initial CI for formatting, linting, tests, Windows build, Android compile smoke, migrations, RLS, and RPC contract tests.
- Copy the legacy token artifact and six canonical screenshots into the new repository as read-only migration inputs.
- Harden the preservation harness: deterministic FTUE state, awaited storage reset, fixed clock/timezone, bundled font decision, disabled snapshot animations, serial visual execution until isolation is proven, and actual Playwright screenshot assertions.
- Capture empty-day, expanded-meal, settings, and compact-metric baselines on desktop and mobile in addition to the existing normal/overshoot scenarios.
Exit criteria:
- the Windows shell installs and launches;
- the Android shell compiles and launches in an emulator;
- Rust can write/read SQLite on both;
- a secret can be stored and retrieved securely on both;
- the trimmed Supabase stack survives restart and restores from a clean backup;
- ChatGPT can call an authenticated test MCP tool through the chosen auth path;
- PowerSync is either accepted with documented evidence or rejected in favor of the already-specified fallback;
- the legacy visual suite runs deterministically and compares against an explicitly versioned canonical baseline;
- the token artifact is consumable by the new UI without importing legacy runtime code;
- no product feature code depends on the legacy repository; and
- the auth choice, Docker/WSL operating envelope, and rollback paths are documented.
Milestone 1 — Windows local-first vertical slice
Purpose: deliver a useful offline Windows application before adding distributed-system complexity.
Work:
- Implement domain entities, validation, rounding, logical dates, and totals.
- Implement SQLite migrations and repositories.
- Implement atomic local writes through the selected PowerSync adapter or the fallback meal-plus-outbox transaction.
- Implement Tauri commands for day queries and meal CRUD.
- Port the current visual language and core dashboard interactions.
- Add settings and owner timezone.
- Add local sync-status presentation, initially showing offline/not configured.
- Package an installable Windows artifact.
Exit criteria:
- a fresh Windows install supports meal CRUD entirely offline;
- restart preserves records;
- Rust returns sorted day views and totals;
- React contains no SQL or authoritative calculation policy;
- invalid and negative nutrition values are rejected consistently;
- deletes use tombstones locally;
- every mutation produces exactly one durable queued upload through the selected sync implementation;
- the Windows visual regression suite passes;
- normal, overshoot, empty, expanded-meal, settings, and compact-metric states have been reviewed side-by-side against the preservation baseline; and
- every intentional visual deviation is classified and explicitly approved by the user.
Milestone 2 — Self-hosted Supabase and cross-device sync
Purpose: converge multiple offline replicas safely.
Work:
- Implement Supabase migrations, constraints, indexes, ownership columns, RLS, grants, and clean-stack migration tests.
- Bootstrap the single owner and implement native Supabase Auth session flows.
- Implement versioned PostgREST RPC functions for mutation apply, fallback cursor pull, reads, and MCP use cases.
- Implement HLC merge behavior, mutation deduplication, and audit writes inside PostgreSQL transactions.
- Implement the selected PowerSync adapter or fallback Rust sync worker and retry policy.
- Configure Supabase Realtime wake-ups and missed-notification recovery.
- Add sync and authentication state to the UI.
- Finalize trimmed Supabase Compose, reverse-proxy allowlists, Tailscale Serve, health, secret-management, backup, restore, upgrade, and rollback runbooks.
Exit criteria:
- two independent SQLite clients converge through PostgreSQL;
- clients safely replay the same mutation batch;
- out-of-order updates converge identically;
- delete and later restore behave according to the revision comparator;
- interruption during push or pull cannot lose a committed local mutation;
- a remote change appears on a connected Windows app within two seconds in the normal case;
- missed Realtime events are recovered by PowerSync checkpointing or fallback pull;
- RLS prevents any token from reading or writing outside the owner scope;
- Studio, PostgREST, Realtime, health, and PostgreSQL are unreachable through Funnel;
- PostgreSQL is not reachable through the public ingress; and
- a backup can be restored into a clean hub.
Milestone 3 — MCP and public Funnel ingress
Purpose: make safe remote logging available to ChatGPT and other clients.
Work:
- Implement the MCP adapter with the official Rust SDK and Streamable HTTP.
- Implement the initial tool schemas and safety annotations.
- Implement MCP idempotency and structured results through versioned Supabase RPC functions.
- Integrate the validated Supabase Auth OAuth 2.1 flow from Milestone 0.
- Route only MCP and OAuth-required endpoints through Funnel.
- Add rate limits, timeouts, audit events, and secret-safe logging.
- Connect the private ChatGPT plugin in developer mode.
- Build and retain an MCP evaluation prompt suite.
Exit criteria:
- unauthenticated public requests are rejected;
- revoked or expired sessions stop working;
- ChatGPT can authenticate, log, read, update, and delete a meal;
- repeat tool delivery with the same idempotency key does not duplicate data;
- an MCP-created meal reaches the Windows UI through ordinary sync;
- destructive operations are correctly annotated and confirmed by the client;
- MCP Inspector and protocol conformance checks pass; and
- public logs contain no secrets or raw meal notes.
Milestone 4 — Android delivery
Purpose: ship the second native client using the proven shared core.
Work:
- Complete Android-responsive UI adaptation.
- Validate SQLite migrations and Supabase session storage on physical Android.
- Integrate Android Tailscale connectivity to private Auth, REST/RPC, and Realtime endpoints.
- Handle application suspension, resume, and network transitions.
- Add Android packaging, signing configuration, and release runbook.
- Run cross-device Windows/Android/MCP convergence tests.
Exit criteria:
- the Android app installs on the target device;
- offline CRUD and restart persistence work;
- background suspension does not corrupt SQLite or its durable upload queue;
- resume catches up with the hub;
- Windows and Android converge under simultaneous offline edits;
- an MCP log appears on both connected clients; and
- no Android-only business rule duplicates the Rust domain crate.
Milestone 5 — Hardening and cutover
Purpose: make the personal service dependable enough for daily use.
Work:
- Run recovery, clock-skew, duplicate, corruption, and outage exercises.
- Finalize structured logging, retention, health checks, and operator commands.
- Validate credential rotation and emergency revoke.
- Verify nightly backups and repeat the restore test.
- Document upgrade and rollback for apps, Supabase containers, MCP, and schema migrations.
- Confirm legacy data migration remains unnecessary.
- Mark this repository as legacy only after user review.
Exit criteria:
- all quality and recovery gates pass;
- documented install, upgrade, backup, restore, token rotation, and incident procedures are reproducible;
- both target apps are using the self-hosted Supabase hub;
- the owner explicitly approves cutover; and
- architecture state documentation reflects the implemented system.
17. Verification strategy
17.1 Domain tests
- validation boundaries and two-decimal normalization;
- logical date behavior around midnight and daylight-saving transitions;
- daily totals and extended nutrient aggregation;
- HLC monotonicity and deterministic actor tie-break;
- update, delete, and restore ordering;
- serialization round trips; and
- property tests proving merge commutativity, associativity where applicable, and eventual convergence for a generated mutation set.
17.2 Storage tests
- migrations from every released SQLite and Supabase PostgreSQL schema version;
- transactional local row plus durable upload-queue commit;
- rollback on injected failure;
- mutation deduplication;
- PowerSync checkpoint or fallback cursor advancement atomicity;
- tombstone persistence;
- clean database bootstrap; and
- migration, function, grant, and RLS compatibility against a clean self-hosted Supabase stack and a managed Supabase preview project when available.
17.3 Sync tests
- two and three replica convergence;
- simultaneous offline edit of the same meal;
- simultaneous delete and update;
- duplicate, missing, delayed, and reordered batches;
- network failure before and after server commit;
- process restart with pending queued work;
- lost Realtime connection and checkpoint/poll recovery;
- full catch-up after an extended offline period; and
- server clock and client clock skew.
17.4 Application tests
- Rust command contract tests;
- React component and accessibility tests;
- Windows Playwright visual and functional tests where supported;
- Windows install/upgrade/uninstall smoke;
- Android emulator compile and launch smoke from Milestone 0 onward;
- physical Android offline/resume tests before release; and
- UI assertions that domain totals exactly match Rust results;
- Supabase Auth sign-in, refresh, sign-out, revoke, and offline-expiry behavior; and
- PowerSync dependency-isolation tests if that adapter is selected.
17.5 MCP and security tests
- MCP Inspector discovery and tool calls;
- current MCP conformance suite;
- ChatGPT developer-mode evaluation prompts;
- OAuth discovery, PKCE, renew, revoke, and reconnect;
- dynamic client registration, issuer, audience, consent, and JWKS rotation;
- missing, malformed, expired, and wrong-scope credentials;
- RLS denial tests using anonymous, expired, wrong-owner, and service-role-free client contexts;
- rate limiting and request-size limits;
- idempotent write retries;
- accurate read-only/destructive annotations;
- secrets absent from logs and error messages; and
- Funnel routes cannot reach PostgREST data, Realtime, Studio, health, metrics, backup, or PostgreSQL.
17.6 Required task runner gates
The new repository should expose at least:
task format
task lint
task test
task test:sync
task test:mcp
task build:windows
task build:android
task verify
Bun and bunx are used for JavaScript tooling. Rust tooling is invoked through Taskfile targets. No release is considered ready until the appropriate target build and integration gates pass.
18. Observability and operator experience
18.1 Client-visible state
The app displays:
- online/offline state;
- number of pending local mutations;
- syncing state;
- last successful synchronization time;
- last non-sensitive error and retry action; and
- whether the configured credential has been rejected or revoked.
18.2 Structured server telemetry
Every request receives a correlation ID. Relevant fields include:
- endpoint or MCP tool;
- authenticated owner subject and OAuth client ID, never the token;
- actor and device ID;
- mutation ID;
- entity ID;
- incoming and resulting revision;
- PowerSync checkpoint or fallback cursor;
- merge outcome;
- latency;
- response class; and
- retry or rate-limit status.
Meal summaries, notes, raw authorization headers, and full MCP arguments are excluded by default.
18.3 Health
- Liveness proves the process event loop is responsive.
- Readiness proves required Supabase services are reachable and migrations are current.
- Backup health is tracked independently.
- A single command reports Serve, Funnel, proxy, Auth, REST/RPC, Realtime, PostgreSQL, MCP, backup, and recent sync status without printing secrets.
19. Impact analysis
19.1 Legacy repository
- No production code is changed during planning.
- Selected frontend code and assets will be copied into the successor repository.
- AWS infrastructure remains historical and is not revived.
- Existing PWA, Cognito, AppSync, DynamoDB, Lambda, and promotion behavior are not carried forward.
- This plan is stored here because this repository owns the transition history.
19.2 Data
- Fresh SQLite and PostgreSQL schemas are created.
- No legacy data is imported.
- Client data becomes replicated durable state rather than a browser guest cache.
- Deletion becomes a tombstone to support offline convergence.
- Self-hosted Supabase PostgreSQL plus verified off-host dumps provides the backup boundary.
19.3 Security
- Funnel intentionally creates a public internet endpoint.
- Application-layer authentication is mandatory because Funnel supplies reachability, not authorization.
- ChatGPT compatibility adds public Supabase OAuth routes even though the product is single-user.
- Supabase sessions, signing keys, OAuth clients, and service-role secrets require rotation and incident runbooks.
- The deny-by-default proxy and route tests reduce accidental public exposure.
19.4 Operations
- The Windows host, WSL2 Docker Engine, Supabase containers, and Tailscale must remain healthy for remote MCP and cross-device sync.
- Native local usage survives hub outages.
- Supabase upgrades, secrets, PostgreSQL backup, and restore are owner-operated responsibilities.
- The WSL distro and Docker systemd service must be started for the hub; a future small Linux host is the cleaner migration if this becomes unattended.
20. Risks and mitigations
| Risk | Impact | Mitigation |
|---|---|---|
| Supabase OAuth beta fails ChatGPT DCR/PKCE compatibility | Connector cannot authenticate reliably | Run the compatibility spike first; keep an auth adapter; fall back to managed Supabase or another standards-compliant provider |
| PowerSync alpha Rust/Tauri SDKs fail a required gate | Native ownership or Android delivery is blocked | Make the spike pass every explicit gate; retain the Rust outbox/cursor implementation path |
| PowerSync server licensing becomes unacceptable | Self-hosting or future changes are constrained | Review FSL/Open Edition terms before adoption and keep domain/UI code independent of the adapter |
| Funnel accidentally exposes private Supabase routes | Data or administrative surface becomes public | Publish one deny-by-default proxy port and test every public allowlist route |
| RLS or RPC grants are misconfigured | Authenticated data access exceeds intent | Default-deny policies, role-specific negative tests, migrations in source control |
| WSL or its Docker service does not start reliably | Sync and MCP remain offline after host restart | Reboot drills, systemd health checks, startup runbook, and migrate to a small Linux host if needed |
| Trimmed Supabase stack still uses too many resources | Windows development performance degrades | Measure first, omit unused services, use WSL memory reclaim, and set tested limits |
| Supabase container upgrade breaks configuration | Hub outage or auth failure | Pin image versions, stage upgrades, back up first, and retain rollback configuration |
| Device clocks diverge | Incorrect LWW winner | Use HLC plus actor tie-break and test clock skew |
| Missed Realtime events | UI appears stale | Realtime only wakes checkpointed PowerSync or durable fallback pull |
| Duplicate sync or MCP requests | Duplicate meals | Mutation IDs, processed-mutation table, and MCP idempotency |
| Tombstone cleanup resurrects deleted data | Deleted meals return | Retain tombstones in v1 |
| Android background restrictions delay sync | Near-real-time expectation fails while suspended | Document foreground SLA and sync immediately on resume |
| React and Rust contracts drift | Runtime IPC errors | Generate bindings and fail CI on uncommitted generation changes |
| Hub loss removes the central backup | Recovery risk | Encrypted off-host daily dumps and tested restore |
| Legacy UI port carries AWS assumptions | Coupling and dead code | Port feature-by-feature through new Rust contracts |
21. Architectural fitness rules
The implementation should enforce these rules in tests or CI:
- UI packages cannot import SQL, sync transport, token, or database modules.
- Database adapters depend on domain/application contracts, never the reverse.
- MCP handlers cannot issue meal SQL directly; they call versioned RPC functions.
- Every durable local mutation creates a queued upload in the same transaction, whether PowerSync or the fallback adapter is selected.
- Every accepted server mutation updates canonical state and writes dedupe, change, and required audit records in one PostgreSQL transaction.
- Every replicated entity has one revision comparator implementation shared by client and server.
- Every external write is authenticated, scoped, validated, idempotent, and audited.
- Funnel route tests prove private endpoints are unreachable publicly.
- Generated TypeScript bindings must match Rust command contracts.
- Realtime notifications may trigger pull but may never be the only copy of a change.
- Secrets are denied from structured logs by tests.
- Migrations, functions, grants, and RLS pass against a clean self-hosted Supabase stack.
- No native or public service contains a Supabase service-role secret.
- PowerSync, if selected, remains server-authoritative and replaceable behind the sync adapter.
- Production configuration cannot invoke
supabase startor expose Studio.
22. Reference basis
Primary implementation references:
- Tauri platform overview
- Tauri Rust command boundary
- Tauri official plugin platform support
- Tauri Stronghold
- Tailscale Funnel
- Tailscale on Windows with WSL2
- OpenAI MCP authentication
- OpenAI plugin connection testing
- Official MCP Rust SDK
- Supabase local development
- Supabase self-hosting
- Supabase Docker self-hosting
- Supabase self-hosted Docker configuration
- Supabase OAuth 2.1 server
- Supabase OAuth for MCP
- Supabase Realtime self-hosting and delivery limitations
- PowerSync organization and SDK maturity
- PowerSync client writes
- Docker Engine installation
- Docker Engine on Ubuntu
- WSL systemd services
- WSL networking
23. Approval record
Plan approved
On 2026-08-08 the user explicitly approved creating the successor repository and building the first end-to-end pass, including parallel implementation agents with primary-agent quality oversight.
The completed status transition is:
Execution begins with Milestone 0 and may implement the thinnest connected path through later milestones where required for the requested end-to-end first pass. Exit criteria remain authoritative and deviations must be recorded here.
24. Execution notes
- Implementation was authorized on 2026-08-08; the first action is creating the separate chatkcal-next repository and its Milestone 0 work log.
- The document-software-architecture workflow was used to separate process, storage, trust, mutation, and deployment boundaries.
- The current repository contains 137 source-like files and approximately 14,823 source-like lines. The largest file is the 1,074-line Dashboard, supporting a feature-by-feature UI port rather than wholesale copying.
- The legacy design-preservation artifact now records six refreshed canonical screenshots, extracted semantic tokens, component port decisions, test-suite gaps, and native parity gates. The default parallel visual run is not yet deterministic: five tests passed in parallel, while the complete six-test suite passed in a final one-worker run.
- The confirmed platform direction now uses the official production self-hosted Supabase stack on Docker Engine inside the owner's existing WSL2 distro, with Tailscale remaining on Windows. PowerSync remains conditional on its Rust/Tauri/Android spike.
- The required project-manager skill is unavailable in the current session; the checked-in Project Management Protocol v2.0 was followed directly.
- The bd executable is unavailable, so the required Beads issue could not be created. This is a documented process deviation; issue state must be reconciled as soon as bd becomes available.
- The private successor repository was created at
https://github.com/001ben/chatkcal-nextwithdevas the implementation branch. - The first Windows-first slice now includes the Tauri/React dashboard, Rust domain and SQLite storage, durable outbox primitives, the self-hosted Supabase schema/runtime boundary, and the authenticated Rust MCP service.
- The original semantic design tokens and six canonical visual captures were preserved in the successor repository, with deterministic new desktop and mobile snapshot tests.
- Verification passed for 19 Rust tests, frontend unit and visual tests, strict lint/formatting, the Windows MSI and NSIS installers, a WSL-only MCP container build, and a disposable fresh-volume Supabase runtime smoke with 13 pgTAP RLS/RPC assertions.
- No Docker package was installed. All container work used the existing Docker
Engine inside
Ubuntu-22.04WSL as directed. - Native auth/sync-loop integration, two-device convergence, Android generation and compilation, the OAuth/ChatGPT connector spike, production Tailscale routes, and a live backup/restore drill remain open production gates.
The execution transition is:
25. Context memory
Summary for future context
ChatKcal Next is a separate-repository, single-owner Tauri v2 rewrite. Windows ships first; Android remains compiling from Milestone 0 and becomes the second client. React is presentation-only except transient UI state. Rust owns domain logic, SQLite, queries, totals, and the client sync adapter. A bounded spike selects PowerSync or the specified Rust outbox/cursor fallback. The official production self-hosted Supabase stack owns central PostgreSQL, Auth, OAuth 2.1, PostgREST/RPC, RLS, and Realtime wake-ups. A thin Rust MCP service calls versioned RPC functions. Docker Engine inside the owner's WSL2 distro hosts the trimmed stack initially; Docker-managed Linux volumes hold database data, while Tailscale runs once on Windows. Serve exposes private native API routes. Funnel exposes only required OAuth routes, owner consent, and Streamable HTTP MCP through a deny-by-default proxy. A Milestone 0 spike must prove Supabase's beta OAuth flow with ChatGPT and the PowerSync alpha SDKs. The contracts can later move to managed Supabase. Fresh start; no legacy import. The verified first implementation pass is Ready for Review; it is not Completed pending user confirmation and the remaining production gates.