Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c2b1034
feat(chat): preserve exact local continuity and reviewed side notes
BunsDev Sep 11, 2026
c9e4534
Merge origin/main with viewport containment into local continuity
BunsDev Sep 11, 2026
8bcb389
test(chat): cover retained continuity across constrained viewports
BunsDev Sep 11, 2026
cc7db60
fix(chat): represent remembered familiars outside the loaded roster
BunsDev Sep 11, 2026
a789b3d
fix(chat): recover reviewed notes and bound durable admission
BunsDev Sep 11, 2026
a89029c
Merge origin/main gitignore update into continuity branch
BunsDev Sep 11, 2026
476279a
fix(chat): close side-note replay and retry guidance gaps
BunsDev Sep 11, 2026
5062fd4
fix(chat): retain creation retries across session navigation
BunsDev Sep 11, 2026
71d8b23
fix(chat): reconcile writes before notifying local observers
BunsDev Sep 11, 2026
782e273
fix: own local mutation refresh and preserve exact review selections
BunsDev Sep 11, 2026
142513b
Merge approved main quota diagnostics into continuity branch
BunsDev Sep 11, 2026
d0d58f1
fix: fence side-note reads and familiar metadata
BunsDev Sep 11, 2026
e386817
fix: reconcile root saves and enforce scoped reviewed writes
BunsDev Sep 11, 2026
a873bf3
fix: verify exact root recovery receipt identity
BunsDev Sep 11, 2026
932b66c
fix: preserve commit truth and bind initial hydration freshness
BunsDev Sep 11, 2026
a060b19
fix: surface local storage startup failures with safe retry
BunsDev Sep 11, 2026
1a22dcd
merge: integrate current main authority into local continuity
BunsDev Sep 11, 2026
d28df3d
fix: retain uncertain review identity and stabilize local refresh
BunsDev Sep 11, 2026
e9b7f45
Merge accepted Windows quota reproduction into local continuity
BunsDev Sep 11, 2026
1061c31
fix: retain unresolved recovery and reject failed IndexedDB opens
BunsDev Sep 11, 2026
1fbdf23
test: wait for rendered review controls before clicking
BunsDev Sep 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,82 @@ The desktop app can also connect or pair with Cave through a least-privilege
native adapter and render bounded, read-only canonical chat data. Explicit demo
routes remain available for design exploration.

## Conversation chapters

Open **Ongoing** in a conversation to navigate UTC-day chapters without
changing its original transcript. This is an exact-conversation view, not a
merge of conversations that share a familiar name. **This device** notes stay
separate, and the Cave source stays read-only.

Switching familiars restores the exact conversation you last selected, including
an available message anchor. References and unsent drafts stay in memory, scoped
to the live source, writer, familiar ID, and conversation ID. Changing sources
never copies a draft. Reloading clears drafts and navigation preferences, not
durably saved local notes. A missing remembered conversation or message shows an
unavailable notice rather than silently choosing a newer conversation.

The installed frozen SDK does not yet expose `listConversationChapters`.
Production Chat says so and offers navigation over already-loaded messages
only, marking partial history rather than prefetching bodies. The additive
DEVELOPMENT read port can consume typed producer headers after a separately
qualified SDK/native capability is admitted. It keeps the eight-page ceiling,
bounded memory-only query cache, revision checks, and source-switch isolation.
Malformed or cross-conversation chapter headers and repeated anchors are rejected.
It does not replace `vendor/opencoven-sdk` bytes or either conformance lock.

## Retained local side notes

You can explore a separate note without changing your parent conversation:

1. Select **This device**, open a conversation, and choose **New retained side note**.
2. Save messages in the empty side note. Your parent draft stays separate.
3. Select individual messages, choose **Review Bring back**, and edit the excerpt.
4. Choose **Bring back reviewed excerpt** to save exactly that text to the original
local parent as an inert user note.

**Close note** retains its messages. **Reopen note** permits more writing.
**Discard note** requires confirmation and removes the note's local messages;
previously reviewed imports remain in the parent. A minimal creation tombstone
prevents a retried create operation from recreating a discarded note.

An attempted review keeps its operation key, selected message IDs, edited text,
and local branch preconditions in source- and writer-scoped memory. Returning
to the parent or switching sources does not cancel it. After an uncertain save,
retry the unchanged review to reconcile the result before editing again, or
explicitly cancel. Cancellation cannot undo an import that already committed.
If a stale branch is definitively rejected before committing, **Review again**
captures current branch preconditions and a new operation key while retaining
your edited excerpt. An uncertain operation never becomes editable under its
old key. Failed side-note metadata reads offer **Retry local side notes** without
enabling mutations against unknown metadata.

**Pending reviews do not survive reload or restart.** Saved imports do. If you
reload after an uncertain save, inspect the parent before starting another import;
the app cannot recover that pending review's key across restarts.

Side notes, lineage, and import receipts use the existing `ChatStore` and
IndexedDB transactions. Repeated operation keys reconcile to the same result;
changed payloads with reused keys are rejected. Competing windows are checked
at commit, and failed imports leave no partial parent record. Imports neither
merge the transcript nor execute instructions, generate replies, copy attachments,
or write to memory services.

The version-2 IndexedDB upgrade preserves existing records and adds operation-key
indexes plus an atomic shared mutation revision. Warm writes read only the exact
conversation preconditions and indexed operation receipts; unchanged history is
not scanned. Initial hydration and refresh after another window's writes still
load history. Close older app windows if they block the database upgrade.

These are local-only notes with **no connected familiar**, not Cave-backed side
chats. If durable storage is unavailable, the UI discloses memory-only custody.
There is no Temporary or provider-deletion guarantee. The frozen Cave source
has no side-chat writer, so it shows an unavailable notice rather than an enabled
no-op or a local fallback for canonical content.

The installed Cave SDK omits import provenance. The source-level notice states
that limitation; canonical messages remain read-only text without inferred
import markers or changed roles.

## Security boundaries

- The main window can invoke only the reviewed `app_identity`,
Expand Down
10 changes: 5 additions & 5 deletions e2e/app.smoke.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { expect, test } from '@playwright/test';

test('preserves the local demo routes alongside the default app', async ({ page }) => {
test('preserves the local demo routes alongside the default app', async ({ page, baseURL }) => {
await page.goto('/?demo=chat');

await expect(page).toHaveURL('http://127.0.0.1:4174/?demo=chat');
await expect(page).toHaveURL(new URL('/?demo=chat', baseURL).href);
await expect(page.getByRole('complementary', { name: 'Conversations sidebar' })).toBeVisible();
await expect(page.getByRole('region', { name: 'Held action' })).toBeVisible();
// Exact: the composer also has a "Send options" caret beside Send.
Expand All @@ -12,19 +12,19 @@ test('preserves the local demo routes alongside the default app', async ({ page

await page.goto('/?demo=messages');

await expect(page).toHaveURL('http://127.0.0.1:4174/?demo=messages');
await expect(page).toHaveURL(new URL('/?demo=messages', baseURL).href);
await expect(page.getByRole('complementary', { name: 'Conversations' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Send' })).toBeVisible();

await page.goto('/?demo=minimal');

await expect(page).toHaveURL('http://127.0.0.1:4174/?demo=minimal');
await expect(page).toHaveURL(new URL('/?demo=minimal', baseURL).href);
await expect(page.getByText('Chats', { exact: true })).toBeVisible();
await expect(page.getByText('Familiars', { exact: true })).toBeVisible();

await page.goto('/?demo=familiars-reads');

await expect(page).toHaveURL('http://127.0.0.1:4174/?demo=familiars-reads');
await expect(page).toHaveURL(new URL('/?demo=familiars-reads', baseURL).href);
await expect(page.getByRole('complementary', { name: 'Conversations sidebar' })).toBeVisible();
await expect(page.getByRole('button', { name: /Q3 pricing evidence map/ })).toBeVisible();
// Stage 1 has no send capability yet; the composer notice says so rather
Expand Down
Loading
Loading