Architecture
August 20, 2026 · View on GitHub
This document explains how dsh-session-theme works and why it exists.
Why this plugin exists
The DSH Web UI's sidebar lists conversations. Each row shows the session's title — but only after you have opened that session at least once in the current process.
Before you click in, the row shows the workspace folder name instead of the conversation theme. That is confusing when you have many sessions.
Why the title is missing
Sessions are stored as JSONL event logs in the session persistence store.
A summary of each session is produced by projecting events (things like
session/title) into flat rows. The projection system uses two paths:
- Live sessions (attached to the process): the list RPC computes the projection from the in-memory session object directly — this always includes the current title.
- Cold sessions (never opened in this process): the list RPC consults the
projection cache's zero-I/O rows (
cachedSnapshot). If that session'stitleprojection was never checkpointed, the row has no title and the sidebar shows the folder-name fallback.
A title is only checkpointed when the session is actually opened and events flow through the cache write-behind path. Sessions created in an earlier process and never opened since then have no cached title row.
The fix
At startup, this plugin runs the cold-read ladder for every persisted
session: if the cached snapshot has no usable title, it calls
coldSnapshot(id), which reads the session's event log from the persistence
store, refolds the projections (including title) from the log, and durably
writes the result back into the cache.
From then on, session.list returns the title projection for every session —
so the sidebar natively shows each conversation's theme on page load. No click
needed.
Design principles
- Zero client changes. The fix is entirely host-side; the client-side bundle is a no-op stub that keeps the registered manifest valid.
- Fast startup. The warm pass reads logs only for sessions whose cached rows lack a title; sessions already titled are skipped (cache hit).
- Fail-soft. A broken log for one session logs a warning and never blocks the next session or the rest of startup.
- Idempotent. Running the warm pass repeatedly converges to the same checkpointed state; there is no double-publishing of events.
Implementation map
| File | Role |
|---|---|
lib/logic.js | Pure decision helpers: hasUsableTitle, shouldWarm, plan. No I/O, no service access — fully unit-testable. |
lib/index.js | The cordis plugin entry: injects sessionProjectionCache, sessionPersistence, sessions and runs the warm pass at startup. |
lib/client.js | Browser-side no-op stub. |
test/index.test.js | Unit + integration tests (Node's built-in test runner, zero dependencies). |
cordis.patch.yml | Declares the plugin bundle for dsh plugin add. |
Data flow
startup
└─ apply()
├─ list persisted sessions (sessionPersistence.list)
├─ live ids from in-memory store (sessions.list)
├─ plan: cold sessions lacking title (cache.cachedSnapshot)
└─ for each: warm (cache.coldSnapshot → read log → fold → write back)
├─ success → title appears in subsequent session.list
└─ failure → log warning, continue with next session
Related DSH internals
dsh-session-projection-cache— providescachedSnapshot/coldSnapshot.dsh-session-persistence-jsonl— provides the JSONL event-log store.dsh-session-title— registers thetitleprojection (session/title).
All of these ship with the standard DSH distribution; this plugin only wires them together at the right moment.