The chat stats line took its token totals from the loaded conversation nodes,
so paging changed them and compaction erased the billing behind replaced
content. It also had no way to show context occupancy: the numerator and
capacity never reached the browser.
Both now come from token-meter session projections read through the standard
useProjection seat. Window nodes keep supplying turn and step counts plus LLM
and tool wall times, which are correctly window-scoped facts about what is on
screen; accounting no longer comes from there.
`tokenUsage` supplies billing and cache hit. `contextPressure` supplies
occupancy, pairing the newest provider-reported prompt size with the newest
capacity recorded by `request/context`. Deployments without token-meter drop
the token groups; a route whose adapter advertises no capacity drops the
occupancy group rather than rendering a placeholder.
Occupancy is deliberately approximate: the numerator and capacity are
independent last-wins fields, not one atomic request observation, so switching
models pairs a fresh capacity with the prior route's pressure until the next
request reports usage. It is a user-facing reference figure that nothing in the
harness makes decisions from, and it matches how the TUI status line has always
computed occupancy. The Agent Note and token-meter README state this as a
decision, including why the atomic alternative was implemented and rejected, so
it is not re-litigated as a defect.
Snapshot delta is one added `Context N% of 128K` segment across eight web
goldens; the preceding commit absorbed master's pre-existing golden drift.
The seam README states the JSON-shaped write boundary, watch-disposer
quiescence, async listener containment, and the drained teardown; the
provider README rewrites Behavior around the operation chain,
read-modify-write, writer lock, ready reconcile, and leaf-level YAML
diffs, and updates Known Limitations to the residual guarantees.
A new Agent Note records the round's decisions and supersedes the
original note's deferred-lockfile alternative (cross-linked in place).
Chinese counterparts updated pair-by-pair (three briefed minimal
updates, one whole-document translation); type-equiv, config, cordis,
and module-graph catalogs re-recorded.
Move COMPACT_CHECKPOINT_SOURCE and isCompactCheckpointSource into a
cordis-free src/checkpoint.ts leaf, re-exported from the root so every
host-side consumer keeps its import. The client can then type-import the
leaf without reaching dsh-session's root, whose Context merge declares the
host sessions service and collides with the client's -- the dsh-commands/brand
shape. Renaming the plugin id now fails the client typecheck.
Also: keep recoverable summary text when a compact/summary mixes text with
other block types, and capture the seeded-history provenance seqs from the
pushes that produce them instead of deriving them by arithmetic.
- `SessionSummary.updatedAt`'s wire doc still said "Persisted file mtime",
which stopped being true for attached sessions.
- The core invariant let `session/inherited` fall through the merge-extensible
default. It is core-owned, so it gets an explicit case; an unbalanced seed
legally places it inside an open turn, which the relation permits.
- The Agent Note claimed the boundary reaches disk via `live.pending`/
`scheduleDrain`. Verified false: the constructor append precedes `enter()`,
so it never publishes on `session/event` and rides the creation seed instead.
Attaching is therefore a write where none happened before — recorded, since
only `load()` stays a pure read.
- The deferred-index proposal asserted this change documented the cold-mtime
skew on `dsh-host-apiproxy`. It did not; the README entry now exists.
- `firstLiveSeq`'s firehose gap runs through its own seq, not below it.
- The boundary is not always at `firstLiveSeq` (the idempotence guard), so
consumers scan for the last one.
- `lastActivityTime` excludes by type, so a pickup time still leaks onto a
synthetic closer when a boundary ends an open turn. Documented.
- Pin the fork claim end-to-end: a child inherits a still-running parent's
open bracket below its own boundary, while the parent has none. Fails if the
write moves back to the load path.
- Fix the telemetry title that contradicted its own assertions.
The `/status` call site cannot be pinned the way the other two are: the
command appends its own `command/run` before rendering, so the boundary is
never the log tail there. Its fixture now at least renders over a
boundary-bearing log.
Conflict resolution notes:
- StatsLine: master redesigned the row into pipe-separated groups with LLM and
tool wall times. Kept that design and swapped only the token accounting
source, so counts and durations stay window-scoped while billing and context
occupancy read the durable projections.
- Generated artifacts (cordis catalog, module graph, event producer/consumer,
i18n pairing hashes) and web snapshots took master's side; they are
regenerated and re-recorded after this merge.
- Web e2e goldens and details-panel/timeline assertions took master's side:
that evolution is unrelated to this branch.
- ui-conversation package.json: kept master's devDependency ordering, re-adding
only the token-meter entry this branch needs.
- .loadingFloat moved after the .status/.error block (its padding was
losing the same-specificity race) and re-anchored bottom-right: the
truncated/error rows own the bottom left and keep rendering through a
scan, so the pill can never cover them; confirmCreate's relist now
clears the stale failure text like every other scan launch.
- The close edge resets loading, so the slow-scan effect disarms while
hidden and a reopened dialog waits out a fresh silence window
(regression test added).
- The truncated note's survival through a scan is now asserted in the
slow-scan test; the wait-bound test moved to fake timers with the
200ms bound explicit.
- select()'s exemption from the one-frame rule and the constants' local
calibration premise are recorded in JSDoc and the capability-seam
Agent Note; the themed-scrollbars note's rebinding enumeration is
replaced by a pointer to the mechanical gate (it had drifted twice).
Both pairs re-recorded.
`isInheritedSeq` had no production caller — only its own tests — so it was
a public core export shaped by nothing. A bracket owner reads the boundary
positionally; the helper belongs with the compaction seam, where a real
consumer decides its signature.
Also condense the `session/inherited` and `lastActivityTime` docs.
Navigations keep the previous view rendering while scanning: target and
parent legs land as one two-pane frame when the parent leg settles within
a 200ms wait bound (past it the target lands alone and the late leg
upgrades in place; Escape inside the landing window withdraws the
navigation). The loading indicator floats over the content on the card
background and appears only once a scan outlives a 300ms silence window,
so navigation never shifts the columns or flashes an intermediate frame.
The truncated note now describes the on-screen panes instead of hiding
during scans.
A plugin owning a standalone open/close bracket cannot tell a dead marker
from a live one: an unmatched `compact/start` reads identically whether the
previous writer died mid-compaction or a compaction is running now.
`Session.firstLiveSeq` already holds that answer exactly, but only in memory.
Append the log-only `session/inherited` event at that seq from the seeded
constructor — the single waist all six seeded-start paths pass through
(resume, configured startup on a persisted id, `sessions.fork()`, a subagent
fork child, `adopt()`'s live prefix, and a bare seeded `create`). Read it
through the new `isInheritedSeq(events, seq)`.
The constructor placement means persistence needs no changes: the marker is
already in `events` when a backend captures the creation seed, so it rides
the ordinary seed path with no load-time write. It also covers fork, where
the inherited bracket's owner may still be running — the case a
persistence-layer boundary could not reach.
Activity ordering excludes the boundary through `lastActivityTime()`, since
lazy resume makes browsing a pickup and the three call sites would otherwise
float every opened session to the top of a picker or list.
Stats line moves into the InputBar's new footer slot (sharing the card's
width column) and expands to the design's grouped detail row: turns/steps,
LLM and tool wall time, cache hit, and input/output token split, all
derived client-side from the snapshot. The composer stack owns one 8px
rhythm, the seat fades the transcript through a fixed 36px gradient band,
back-to-bottom follows a live --dsh-composer-height, and goal/todo strips
share one 752px tip-fill column.
Replace the surface-ordered fold with a log-ordered human transcript:
append-origin surface events at their own log positions plus one marker per
landed compaction checkpoint. Command folding, the tool-call index, and the
rev-keyed memo carry over unchanged.
Removes foldDegraded, the padding sentinels, baseSeq, and degradedSeqs() --
they existed only to satisfy the core fold's seq === index assertion. That
also closes the pagination hole A1 exposed: a page can carry a checkpoint
whose shadowed range fell outside the window, and nothing resolves
surfaceOp.start anymore.