feat(chat): make v0.0.1 standalone with Cave optional - #61
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR makes the Chat app usable as a standalone product by introducing a local storage-backed chat source (IndexedDB with in-memory fallback) and making Cave pairing optional/opt-in, while keeping the existing read-path QueryAdapter contract intact.
Changes:
- Added a local chat persistence layer (
ChatStore+ backends) and a localQueryAdapterimplementation. - Split read/write concerns via a
ChatWriterso local is writable while Cave remains read-only. - Updated app and shell wiring/UI to mount local immediately, add a composer + “New conversation”, and gate Cave behind an explicit “Connect to Cave” flow (tests updated accordingly).
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/local/chat-records.ts | Defines local record shapes, backend port, and sanitization utilities. |
| src/lib/local/indexeddb-backend.ts | Implements durable IndexedDB backend for local records. |
| src/lib/local/memory-backend.ts | Adds non-durable in-memory backend fallback when IndexedDB is unavailable. |
| src/lib/local/chat-store.ts | In-memory index + keyset pagination + atomic commit semantics over a backend. |
| src/lib/local/local-query-adapter.ts | Implements existing QueryAdapter interface over the local store. |
| src/lib/local/chat-writer.ts | Introduces a write port with explicit unsupported outcomes for Cave. |
| src/lib/local/chat-source.ts | Packages adapter + writer into switchable sources (local vs cave). |
| src/lib/local/local-chat.test.ts | Adds comprehensive unit coverage for local cursors, paging, writes, adapter contract, and fallback. |
| src/lib/app-metadata.ts | Updates app metadata strings to reflect standalone/local-first positioning. |
| src/chat-shell.tsx | Adds optional writer/revision props, composer rendering, and “New” conversation button. |
| src/chat-composer.tsx | New message composer for local writes with durability disclosure and error display. |
| src/chat-composer.css | Styling for source bar, Cave connect surface, composer, and “New conversation” button. |
| src/app.tsx | Rewires startup: local mounts immediately; Cave becomes opt-in and source-switchable. |
| src/app.test.tsx | Updates app tests for opt-in Cave, local-first behavior, and composer behavior. |
| e2e/connection-gate.spec.ts | Adjusts browser expectations to local-first and composer behavior without Cave. |
| e2e/app.tauri-mock.spec.ts | Updates mocked Tauri e2e flow to opt in to Cave before asserting Cave UI. |
| e2e/app.spec.ts | Updates baseline e2e test to assert shell mounts without a blocking gate. |
| docs/standalone-query-adapter-design.md | Adds design doc describing the standalone/local-first approach and constraints. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
BunsDev
force-pushed
the
feat/standalone-chat
branch
from
September 1, 2026 07:23
c2c1e54 to
780aab1
Compare
BunsDev
force-pushed
the
feat/standalone-chat
branch
from
September 1, 2026 08:11
73923ab to
513faad
Compare
This was referenced Sep 1, 2026
BunsDev
force-pushed
the
feat/standalone-chat
branch
2 times, most recently
from
September 2, 2026 04:35
8a52bb3 to
a4ae210
Compare
Design-only. No implementation code. Records the constraints that shape the standalone chat lane, each verified against the tree rather than assumed: - Cave Client v1 exposes no write operation, so writes can never be routed to Cave and must not share the read port. - installationId feeds Cave pairing only, so it must not gate app start. - ChatShell couples to the QueryAdapter type, not to Cave, so a local implementation of the existing interface renders local data unchanged. - manual-page-walk enforces exact cursor echo and no cursor reuse, which forces keyset rather than offset cursors. - The conformance lock pins src-tauri and scripts, not the web layer. Proposes three narrow ports (ChatStore, ChatWriter, ChatSource) with the existing QueryAdapter interface kept byte-identical, IndexedDB persistence with an explicitly non-durable memory fallback, and demotion of ConnectionGate from entry gate to opt-in connect surface. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The app blocked every user behind a Cave connection gate, so a first public release could not be used at all without a paired Cave instance. Cave Client v1 also exposes no write operation, so no amount of gate tuning would have made the app writable. Adds a local chat source and demotes Cave from an entry gate to an opt-in, read-only second source. - src/lib/local/chat-records.ts: record shapes and the ChatBackend port. commit() takes whole records because appending a message also bumps its conversation's updatedAt, and a crash between those two writes would order a conversation by a timestamp no message justifies. - src/lib/local/indexeddb-backend.ts: durable backend. Both stores are written in one readwrite transaction. Chosen over localStorage for the atomicity and for the absence of a ~5 MB ceiling. - src/lib/local/memory-backend.ts: fallback that reports isDurable() as false, so the UI states plainly that nothing is being saved instead of implying persistence it cannot deliver. - src/lib/local/chat-store.ts: in-memory index with keyset cursors. createManualPageWalk aborts a walk when a cursor repeats, and offset cursors repeat as soon as a row is inserted mid-walk, so cursors name the last row served rather than a position. - src/lib/local/local-query-adapter.ts: the existing QueryAdapter interface over local storage. It reimplements rather than wraps createQueryAdapter because every reason that adapter exists (latency, dedup, aborts, TTL over a socket) is absent here; a cache in front of synchronous map reads would only add a window showing replaced data. - src/lib/local/chat-writer.ts: WriteResult with 'unsupported' as a first-class outcome, so a Cave-backed composer disables itself rather than failing per keystroke. - src/app.tsx: local source mounts immediately; Cave connects only after the user opts in. The installation ID gate now disables Cave pairing only, since it feeds pairingIdentity and nothing else. A dropped Cave connection falls back to local rather than showing an empty view. - src/chat-shell.tsx: two optional props (revision, writer) plus a composer. The read path is unchanged. No assistant reply is fabricated. There is no model backend in this release, so the composer states that no reply will arrive rather than inventing canned text. APP_PHASE is deliberately unchanged: it mirrors a value in src-tauri/src/metadata.rs that the conformance lock pins, so it moves only alongside a Rust change and a repin. Verified: typecheck, lint (7 pre-existing CSS warnings), 409 unit tests, vite build, and a real Chromium run confirming a sent message survives a reload through IndexedDB with no console errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
…tion Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
Document the implementation details that evolved from the original proposal so the design record does not describe the shipped PR as an unimplemented draft. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
BunsDev
force-pushed
the
feat/standalone-chat
branch
from
September 2, 2026 05:08
e5ffe80 to
0f5156f
Compare
Co-authored-by: BunsDev <68980965+BunsDev@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The app blocked every user behind a Cave connection gate. A first public
release could not be opened at all without a paired Cave instance, which
contradicts shipping v0.0.1 as a standalone product.
Cave Client v1 also exposes no write operation —
CAVE_OPERATIONSin@opencoven/sdk-corelists 13 ops and none of them create a message. So noamount of gate tuning would have made the app writable; reads and writes had to
become separate ports.
Design approved in
docs/standalone-query-adapter-design.md(commit2ed75f9).What changed
New local layer (
src/lib/local/)chat-records.tsChatBackendport.commit()takes whole records so a message append and its conversation'supdatedAtbump land atomically.indexeddb-backend.tsreadwritetransaction.memory-backend.tsisDurable() === false.chat-store.tslocal-query-adapter.tsQueryAdapterinterface over local storage.chat-writer.tsWriteResultwith'unsupported'as a first-class outcome.chat-source.tsWiring
src/app.tsx— local source mounts immediately; Cave connects only after theuser clicks Connect to Cave. The installation-ID gate now disables Cave
pairing only (it feeds
pairingIdentityand nothing else). A dropped Caveconnection falls back to local rather than showing an empty view.
src/chat-shell.tsx— exactly two new optional props (revision,writer)plus a composer. The read path is unchanged.
src/chat-composer.tsx/.css— the composer.Decisions worth reviewing
Keyset cursors, not offset.
createManualPageWalkaborts a walk the momenta cursor value repeats, and offset cursors repeat as soon as a row is inserted
mid-walk. Cursors therefore name the last row served (
{v,t,i}, base64url).The local adapter reimplements
QueryAdapterrather than wrappingcreateQueryAdapter. Every reason that adapter exists — network latency,request dedup, abort handling, TTL caching over a socket — is absent here. Reads
are synchronous map lookups; a cache in front of them would only add a window in
which the UI shows data the store has already replaced.
No assistant reply is fabricated. There is no model backend in this release.
The composer states that no reply will arrive rather than inventing canned text.
The memory fallback is never silent. When IndexedDB is unavailable the
composer says the messages are kept in memory only and will be lost.
APP_PHASEdeliberately unchanged. It mirrors a value insrc-tauri/src/metadata.rsthat the conformance lock pins, so it moves onlyalongside a Rust change and a repin. Flagged for a follow-up, not smuggled in
here.
Verification
corepack pnpm typecheck— cleancorepack pnpm lint— clean except the 7 pre-existingnoDescendingSpecificitywarnings in
src/demo/chat-demo.css(owned by the warnings lane)corepack pnpm vitest run— 409 passed, 61 skipped, 0 failedcorepack pnpm build— succeedsmessage, reloaded the page and the message survived via IndexedDB, with
zero console errors and the Cave source correctly disabled.
27 new tests cover cursor round-tripping, a full page walk driven through the
real
createManualPageWalk, cursor-echo and no-repeat guarantees, commitatomicity, rollback on backend failure, corrupt-row sanitizing, orphan-message
dropping, validation codes, and a shared adapter-contract suite.
src/app.test.tsxwas rewritten for the opt-in Cave flow; the three e2e specs were updated to match.
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com