Keep the base class's relative method order (write before coldSnapshot) so
the diff against the base shows the cold-read methods as a pure insertion
instead of a reorder of existing methods.
A session that never talks — a forked child seeded with its ancestor's
title, say — previously got its first cache row only at detach; a crash,
or a fork held live in the store, left the seed-derived values (the
title) unreadable on the cold list. Session creation is now a third
mandatory write point: the creation checkpoint folds the seed and
persists immediately. Write-policy docs (README + catalogs) updated.
Opening a per-record unit splits a legacy `<root>/<name>.json` (the
pre-per-record single-file layout) into per-record documents; an
already-present new record wins, and the legacy file is deleted once
every record migrated. The migration also runs when the new tree is
absent — the fresh-upgrade shape — and foreign, shapeless, or malformed
legacy files are left alone. This preserves previously cached session
titles, list metadata, stats, and subagent identity across the medium
change.
A detached history read still traverses the complete log, but each unit's
fold is now seeded from its cached checkpoint: the registry's restore
slices off the already-folded prefix (events at or below the row's seq)
and applies only the tail. The first cold read writes the refreshed
checkpoint back (fail-soft), so the cache row is created on first read
and kept current afterwards. The recipe lives on the cache
(cachedCheckpoint, coldSnapshot, writeBack); the api-proxy carrier only
supplies the stored header and the full log.
The themed WebKit scrollbar skin keeps a wide table's horizontal bar
permanently painted. Chromium never repaints state-conditioned scrollbar
styles (hover-conditioned ::-webkit-scrollbar* rules and :hover
scrollbar-color changes both compute but never reach the painted bar,
measured headed and headless), so the hover reveal toggles overflow-x
itself: hidden at rest with a padding-bottom matching the themed bar
height, auto on hover or keyboard focus with the padding released — the
appearing bar exactly replaces the padding and nothing below shifts.
Resting hidden overflow drops Chromium's implicit scroller focusability,
so wide wrappers carry an explicit tabindex for arrow-key scrolling.
Cache README (EN/ZH): the medium is the session_projcache domain in
per-record layout (one version-stamped document per session under the
json backend root), reads are synchronous from the domain's in-memory
tables, and the storage stack rides in base. storage-json README
documents both layouts and their contracts; web-app README notes that
storage and the projection cache live in the shared base. Regenerated:
session-projection subsystem catalog (sync cachedSnapshot, domain
medium), config-catalog (Config.root gone), module-graph (cache now
depends on storage-domain, not session-persistence), cli composition
(storage rows in base), and the projection-cache Agent Note — which now
records the file-root revision and its revert as rejected alternatives.
cache.spec now boots the real storage stack (storage, storage-json,
storage-domain) and asserts the per-record medium directly:
<root>/session_projcache/sessions/<id>.json carries a version-stamped
{version, record} document, cachedSnapshot is synchronous (zero-I/O from
the domain's in-memory tables), and the write-policy / fail-soft / listing
coverage is preserved at 100%. json-backend.spec gains a per-record layout
block (per-record documents, overwrite/delete/reopen, unsafe keys and
undeclared tables rejecting, foreign-document discard on open, closed
guard, close drain, unreadable-as-absent); storage-domain domain.spec
covers layout validation and descriptorOf projection. list-children.spec
mounts the storage stack for its projectionCache cases and its
cachedSnapshot mocks and reads go synchronous; the api-proxy specs' cache
mocks go synchronous too. devDeps and tsconfig references updated for the
storage stack.
The storage hub, json backend, and domain form are general infrastructure,
and the projection cache is a session-layer service that depends on them —
both belong in the shared base, not in the web overlay. Base now provides
storage / storage-json / storage-domain / session-projection-cache; the
web-app overlay keeps its surface consumers (workspace, message-feedback),
which inherit storageDomain from base (a child layer sees parent services).
This reverts the storage stack's historical web-app-only placement and the
file-root design's base mount of the cache.
The cache now opens its domain through ctx.storageDomain, which the
web-app overlay provides (storage-json + storage-domain, backend json).
A parent layer cannot see a child layer's services, so the base-layer
mount from the file-root design is reverted: the cache mount moves back
to web-app next to its storage dependencies, and Config.root is gone.
Restore the storage-domain medium the file-root design replaced: the cache
opens the session_projcache domain (per-record layout — one document per
session under the json backend root) and checkpoint writes land through
the domain's write chain. Reads and writes now share ONE coherent state:
cachedSnapshot reads synchronously from the domain's in-memory tables,
and every write is durability-first-then-memory, so a read can never go
around the write chain to the medium. The hand-rolled write chains,
in-flight tracking, per-session file paths, owner-only file modes, and
the sqlite no-path special case are gone; Config.root is removed and the
domain's version stamp makes a checkpointRecord bump discard stale
sessions per record instead of rejecting the whole medium. The async
ripple of the old file read is reverted: api-proxy's listing column and
subagent's cold identity read go back to synchronous cachedSnapshot.
The json backend now serves two layouts. single (the default) keeps the
whole unit as one document at <root>/<name>.json; per-record keeps one
version-stamped document per record at <root>/<name>/<table>/<key>.json
(plus global.json), so one write rewrites one record instead of the whole
unit. The per-record unit is stateless — the directory is the state,
loadAll re-reads the tree, and every write is a single durable file
operation — while single keeps its authoritative in-memory state and
whole-file publish. Records keys must be path-safe ([a-zA-Z0-9_-]+);
an unsafe key rejects. A record document that is malformed or stamped
with another version reads as an absent record: one bad or stale file
never bricks the unit, and a version bump discards stale records instead
of migrating them. DomainSpec and KvUnitDescriptor gain the optional
layout field (defineDomain validates it, descriptorOf projects it).
Review follow-up on #2794: the pin was applied inside nextSessionOrderAccount,
whose output syncSessionOrderAccount persists into sessionOrderByAccount, so a
reused blank stayed first forever after turning non-blank (even across
reloads). Move the pin to the render-derived layer (orderedWorkspaces,
orderedUngroupedSessionIds, ungrouped order, flat rows) so the persisted
account order is never touched, and skip drags the pin would mask (dragging
the pinned blank, or parking another row into the pinned slot) entirely,
including the Host account write.
Adds regression tests for the masked-drag skip and for the pin releasing when
the blank turns real; updates the sidebar-order Agent Note (EN + ZH) and its
pairing hash.
The backdrop keyed each plain-text reference mark by its draft offset, so
any character typed ahead of a reference changed the key and made React
unmount the mark with its nested spans and inline glyph. Every keystroke
before a reference rebuilt every reference after the caret.
A scan derives these ranges fresh on every render, so a range carries no
identity past the render slot it occupies. The mark now keys by its index
in the offset-sorted textRefs list. Structured chips keep occurrenceId,
the identity their occurrence table owns.
The New Session being created renders first in its account while selected,
in both order modes; opening or reusing a blank never advances its
updatedAt, so the pin keeps the reused row at the front.
Clicking New Session reuses the workspace's existing blank session when one
exists; reuse only opens it, so its updatedAt (host: max(createdAt,
lastPromptAt)) never advances and the row kept its old creation-time
position in the sidebar. nextSessionOrderAccount now pins the currently
selected blank session to the front of its account in both Manual and Last
updated modes; non-blank navigation stays untouched.
Fixes#2788
Trim knip.json to 655 lines by removing 15 stale or glob-duplicate workspace entries. Behavior-neutral under knip's specificity-based workspace config selection; CI green, issue policy green, review threads resolved.
ProjectionDefinition lost its persist?: boolean opt-in when every
projection unit became uniformly checkpointed, but the subsystem doc's
type-equiv paste still carried the field, so verify-type-equiv drifted
from packages/session/session-projection/src/index.ts. Remove the field
and its JSDoc from both language sides and re-record the bilingual
pairing record.
- subsystem docs: drop the removed persist flag from the ProjectionDefinition
type block (both languages).
- config-catalog regenerated (cache requires sessionProjections/sessions,
config gains root) and the zh side synced by hand; doc graphs regenerated.
- Agent Notes: the per-session cache note records the owned root tree and
no-persistence design; the storage-root proposal's link to it is corrected
(two levels up).
The web e2e seeds no longer warm the deleted coldSnapshot; assertions do not
depend on the cache path. list-children mounts the cache with a scratch
root instead of a storage-domain backend, and drops the now-unused storage
devDependencies.
The cache now owns its storage root (dshHomePath('projections')), so the
mount moves from the web-app overlay to base with that root declared, next
to the other base session layers. web-app inherits it; its overlay mount is
removed.
Store each session's projection_cache.json under the cache's own root tree
(<root>/<session-id>/projection_cache.json, wired to dshHomePath('projections')
in the base bundle) instead of beside the session log via
sessionPersistence.locate(). The cache owns its directory layout, keys
directories by the code-generated session id, and never consults the
persistence layer; the service now injects only sessionProjections and
sessions.
Drop the coldSnapshot method and its readFrom-tail fold ladder: every cold
consumer refolds from the log itself, so the cache only serves the listing
read (cachedSnapshot, one async file read per session) and the write side.
Fail-soft durability, per-path write serialization, in-flight drain, and
atomic 0600 writes are unchanged; the chain cleanup now observes its own
rejection so a failed write cannot surface as an unhandled error.
dsh-session-persistence leaves peer/dev dependencies and the tsconfig
reference; dsh-atomic-write moves to peerDependencies. Config gains a
required root.