Skip to content

Data Agency

neodigm edited this page Oct 4, 2026 · 1 revision

Data Agency

The conversation belongs to the person having it. This page is what that means concretely.

Nothing is stored until a page asks

persist defaults to off.

<!-- nothing is written anywhere -->
<machvive-chat-syncopation-services>

<!-- conversations go to IndexedDB, in this browser -->
<machvive-chat-syncopation-services persist>

Storing someone's conversation is a decision a page has to make deliberately, not a default it inherits by installing a component. Two tests pin this from both directions — that persist writes through, and that nothing is stored without it — because a default-on cache would quietly retain everything anyone typed, which is the opposite of the stated promise.

Where it goes

IndexedDB, database machvive-chat-syncopation, store conversations, keyed by conversation id. In the user's own browser and nowhere else. No network request is involved, because no module makes network requests.

Why not localStorage

A transcript grows without bound and localStorage is a synchronous ~5 MB cliff — it fails at exactly the point the conversation got interesting, and it blocks the main thread while it works.

The cache degrades to memory when IndexedDB is unavailable (a private window, storage disabled by policy, a plain Node process) rather than throwing. History is lost; the surface keeps working. Losing history must never break the conversation in progress.

Writes are fire-and-forget. Storage failing is never allowed to interrupt a turn.

Giving it back

<machvive-chat-syncopation-history></machvive-chat-syncopation-history>

Three operations, which together are what "agency" has to mean if it means anything:

Control Does
Open Replays a stored conversation into the live one
Export Hands over a JSON file. No upload, no endpoint
Delete Removes it — the stored row and the in-memory copy

Plus Export all and Delete all in the header.

Deletion actually deletes

Cache#remove and Cache#clear drop the IndexedDB row and the memory copy. A "clear history" that only hides rows is a lie, and this is the one place in the collection where that distinction carries real weight. A test asserts that a removed conversation is unreadable afterwards — including from memory, which is the half that is easy to forget.

Export is a file, not a request

history.addEventListener('history-export', ({ detail }) => {
  // detail.json is the same string the user is downloading
});

const json = await history.export();        // all of them
const one = await history.export('c-abc');  // just one

The method returns the JSON and dispatches the event, then offers a download. There is no endpoint and no telemetry hook — the user's data goes to the user.

Building your own controls

const { cache } = services;

await cache.list();                 // [{ id, records }]
await cache.get(id);
await cache.remove(id);
await cache.clear();
await cache.put(conversation.toJSON());
cache.available;                    // false when IndexedDB is unusable

If you build a settings screen instead of using the history component, keep all three operations. Export without delete is a photocopier; delete without export loses something the user may have wanted.

Things worth telling your users

Dictation is usually not local. SpeechRecognition in most browsers sends audio to a vendor service. The voice component cannot change that, but you can say so where you put the microphone button — particularly if the rest of your surface runs a local model and is otherwise private.

A local model means the conversation never leaves the device. With transport="local", there is no server-side copy to subpoena, breach or retain. That is a stronger privacy claim than any hosted provider can make, and it is worth stating plainly rather than burying.

Clearing site data clears this. IndexedDB goes when the user clears site data, and may be evicted under storage pressure. Persistence here is a convenience, not a system of record. If a conversation matters to your business, your server should have its own copy — with its own consent, its own retention policy, and its own deletion path.

What this package never does

  • No network requests. A test fails if any module gains one.
  • No runtime dependencies, so no transitive supply chain. A test asserts it, in the manifest and the lock file.
  • No telemetry. Importing a module emits nothing.
  • No window.dataLayer, no analytics globals, no beacons.
  • No credential of any kind.

Next: Design Decisions.

Clone this wiki locally