gen-cordis-catalog/gen-cordis-api pick up the SessionTitleService rename
JSDoc and line shifts; gen-doc-graphs picks up the session-title invariant
companion's session/event edge.
The fixture route shares the requireSession guard, reads the accepted seq off
the appended event, and gains the error-arm spec the workspace.rename case
set as precedent; WorkspaceBrowser drops the unchanged-title block — confirming
the current automatic title IS the pin gesture — and both touched client
READMEs document the new dialog and the unary-settle rule bilingually.
The rename impl narrows on SessionTitleInvalidError: only an
empty-normalizing title maps to title-invalid (its message renders verbatim
in the rename dialog alert), while liveness/disposal races fall to internal;
the absent-service message trims to one presentable sentence. rpc-schemas
gains the title-invalid accept/missing-details lines; cosmetic ordering
(type-only import comment, tsconfig reference, schema import order) restored.
SessionTitleInvalidError narrows the one rename failure that blames the
input; the fallback-unpin append extracts to appendFallback beside
ensureFallback's guarded twin; a deferred-provider test proves rename
supersedes ACTIVE generation; the invariant companion enforces
messageSeqs-empty iff user-source on every appended session/title event
(tsconfig gains the session-title invariant path); SessionTitleEventData
field docs state the third source kind and the empty-seqs rule, mirrored
into the bilingual core-data-structures page; the note qualifies the
refresh unpin as conditional on a derivable replacement.
Close the per-file coverage gaps the new UI behavior introduced: MenuView
pointer-outside dismiss (all guard branches), the GoalBar pause action,
the ui-slash injected onDismiss face, and the non-kebab permission name
passthrough.
Unify the /plan claimed hint with the plan placeholder through a locale
namespace, localize slash menu group titles, replace the PermissionSelect
native select with the Menu primitive, add a goal pause verb chain, clamp
anchored popups to the viewport with scroll-into-view and outside-dismiss,
and fix onPasteUpgrade insertedRange to account for the chip trailing gap.
gen-cordis-catalog embeds source line anchors; the switch extraction and the
ignore annotation moved SessionTitleService, so the services page was stale
against the committed code.
The default arm of copySessionTitleSource is a closed-union exhaustiveness
guard (compile-time protection for the next kind); it is unreachable at
runtime, so the per-file coverage gate ignores it like every other
assertNever arm.
The row menu's Rename item opens the same dialog pattern as workspace rename
(no client-side conflict rule; the host normalizes) and resolves through the
session face via ctx.sessions.binding. Assembled-app snapshot covers the
row-menu -> dialog -> unary-settle flow.
rename is a per-session verb on the outward session face (prompt/cancel
precedent), not a list-service verb: the Session calls session.rename and
applies the response {title, seq} to its projection store under
higher-seq-wins, so the list row updates before the push frame. The fixture
api and the test-runtime double follow the same face.
New unary method in RpcMethodMap with the title-invalid error code; the impl
resolves the agent (cold sessions resume first) and delegates to
ctx.sessionTitle.rename, returning the normalized title plus its event seq so
clients settle the title projection cell ahead of the push frame. session.fork
stays on the reserved-seam list.
sessionTitle.rename appends a user-source session/title event; onUserMessage
skips scheduling while a user title stands, and an explicit refresh is the
deliberate unpin (provider regeneration, or a re-derived fallback when no
provider is registered).
Add @deepseek-ai/dsh-host-directory-picker-auto, a node-half-only chooser
that samples the host situation once at boot (bind host via a new
httpServer.host getter, SSH markers, platform, DISPLAY/WAYLAND_DISPLAY)
and mounts the matching dual-face backend (-native or -browse) as a real
Loader entry in the in-memory root tree; the effect disposer removes it.
Entry-level mounting keeps the seam's one-row-swaps-both-faces invariant:
the client module table discovers the mounted backend's browser half
exactly as a config row's. apps/cli now composes -auto as its
directory-picker row; composing a backend row directly remains the pin.
Marker scale was deferred in a resolved review thread but only the
compaction-progress half was written down. Deferred now names it, says
where the count comes from (`sourceEventSeqs`), and why it belongs with
progress rather than here.
Adds the prerequisite: the terminal's replay and live replacement branches
are textually identical and 600 lines apart, so marker content needs one
home — fold them into a single `renderReplacement(event)` before giving the
row a payload that must stay consistent across both paths.
The `ctx.tui` source line moved when e029ffb88 retired two imports from
`packages/ui/tui/src/index.ts`. Regenerated; `verify-cordis-catalog` is
green again, which is the gate CI caught.
Two comment-only corrections from the final review round.
The `isCompactCheckpoint` redundancy note appealed to the mandatory-marker
invariant to explain something the enclosing branch now states outright:
after e029ffb88 both call sites are literally inside an
`isReplacementSurfaceEvent` branch, so the appeal became retained
reasoning. Say the call sites test it directly.
`appendPreCompactionLog` implied its tool-result content is what survives
compaction, but `bash`'s presenter is static, so the string never reaches
a fixture — the fixtures pin that the shadowed step's card survives.
Neutral text plus a note on where the card body comes from, so a later
reader does not "fix" a fixture to make the sentence true. Verified by the
absence of fixture drift from changing the string.
The replay loop spelled the classification as
`isSurfaceEligibleType(event.type) && !isAppendSurfaceEvent(event)` while
the live listener used `isReplacementSurfaceEvent(event)`. Under the
mandatory-marker invariant these select the same set: the only divergence
is a surface-eligible event carrying no marker, which no active Session
can hold — `Session.append` and the seed construction loop both reject it
through the same `planSurfaceEvent`.
Using the same predicate on both paths makes "both paths classify a
surface event by the same marker" structural rather than argued, and
retires the TUI's last `isSurfaceEligibleType` use.
No behavior change: the TUI suite and all 113 snapshot fixtures pass
unchanged.
Review follow-ups on b9b2e593f, all documentation precision.
The redundancy note on `isCompactCheckpoint` leaned on a reading its call
site does not state: `index.ts` reaches it for surface-eligible non-append
events, which is the same set as replacements only because the marker is
mandatory. Say that instead.
The Agent Note now owns three facts it was leaving to a future reader.
`rebuildTranscript` materializes a component per append-origin event and
runs on mount, color-scheme change, and every reasoning toggle — work
compaction used to bound for exactly the long sessions it serves, so the
cost now tracks session length rather than the surface. `Consequences`
names `surface-replayed-compaction` as the durable evidence for the
live/replay equivalence claim, so the two fixtures that must move together
are findable from the Note rather than the PR thread. `Deferred` records
that a page can now carry a checkpoint whose `surfaceOp.start` fell out of
the window: pagination no longer cuts on the checkpoint's provenance
group, `FoldAdapter` pads with a non-surface sentinel, and `nodes()`
degrades to `degradedSeqs()` — which is already close to the transcript
projection A2 should build deliberately.
Also corrects the definite-assignment comment in the snapshot scenario:
the assertion rests on the awaited setup invoking `beforeMount`, not on
that call being synchronous.
Review follow-ups on the append-origin transcript projection.
The live/replay equivalence claim was stated unconditionally but does not
cover `tool/call`: only replay re-derives call pairing, because a call
event carries no `surfaceOp` of its own and inherits transcript
membership from the `assistant/message` that advertised it — which the
live listener has necessarily just rendered. Narrow the claim in the TUI
README and Agent Note, and record at `rebuildTranscript` why the filter
is replay-only rather than a missing live branch.
Add `surface-replayed-compaction`: the three existing fixtures all come
from the live path, leaving the resume case the bug report leads with
pinned only by a unit test. The new checkpoint mounts with the
replacement already stored and records byte-identical to
`surface-after-compaction-wide`, so the two fixtures now pin the
equivalence they assert. The shared fixture appends move into
`appendPreCompactionLog` / `appendCompactionCheckpoint`.
`MESSAGE_TYPES` is not "human message event types" — it includes
`assistant/message`. Say what the code distinguishes (append-origin
conversation messages vs. model-only replacement copies) at the const,
the `paginate` and `session.history` JSDoc, the apiproxy README, and the
Agent Note.
Also: spell the replace shape as `Extract<SurfaceOp, { op: 'replace' }>`
for symmetry with the module's two other uses; document why
`isCompactCheckpoint` keeps a replacement check that is redundant at both
call sites; say that Ctrl+R toggles reasoning, which rebuilds the
transcript; and qualify "the sole source of derived history" as derived
*model* history now that the transcript is the other projection.
describe() now carries each namespace's detached composition base and raw
user section beside the resolved value — presence in the user layer is how
a form marks a field user-overridden — and describe({redactSecrets:true})
strips role('secret') fields from every layer while enumerating their
{path,set} slots, so a wire surface has no slot that can carry a secret.
The pure redactSecrets(schema,value) walker (object/dict/array containers,
secret-role subtree as opaque leaf, inputs never mutated) is exported for
any other wire; the README's no-redaction Known Limitation is discharged.
CI's coverage gate rejected `ansi.ts` after the state normalization: the new
fold carries shapes the existing cases never reached.
Added, each expectation checked against a real terminal where the terminal is
the authority — three of my first guesses were wrong and the implementation
was right:
- `48;2;R;G;B` (the extended-background arm and its 4-parameter span), a bright
foreground and background (`91`/`101`, which resolve to `--dsw-*` tokens
rather than literal rgb), a `38` with no kind byte, re-opening an attribute
already in force, and the bare `\x1b[m` reset.
- Clearing a wide pair from the spacer side: `中x\r\bA` shows `A x`, since the
backspace clamps at column 0 and writing the lead blanks its spacer instead
of letting the `x` slide left. An erase reaching the lead does the same, so
`中x\x1b[1K|` shows three blanks before the bar.
ctx.llm gains 'llm/adapters-updated' — a payload-free registry notification
emitted at every topology commit point (adapter routes registering or
disposing, directory entries appearing or withdrawing) with contained
observers and INVARIANT rethrow — plus registerConfigurableProviders/
listConfigurableProviders, the directory of routes an adapter plugin can
activate through configuration. llm-deepseek declares deepseek-official
(whole llm-deepseek section as profile); llm-pi-ai declares the full
installed catalog under providers.<provider> even while dormant, so the
web settings surface can offer every provider before any route exists.
The invariant companion asserts the registry stays readable at each
notification.
Nine findings, one critical. Terminal cases verified in a real terminal first.
CRITICAL: cells held the accumulated SGR history, so every state boundary
re-emitted the whole chain — output switching color without a full reset
emitted O(n^2) characters. Measured: 3200 such cells produced 25 MB, and the
reviewer's ~90 KB alternating-color case is well under bash's own output cap.
State is now a normalized record (foreground, background, attribute set) with
one canonical sequence per boundary, so the emitted text is linear in cells;
the 90 KB case parses in 36 ms. That also makes the attribute closers every
chalk-based tool writes actually close: `\x1b[1mbold\x1b[22mplain` leaves the
following write PLAIN, which a real terminal confirms.
Width follows emoji presentation, not the U+2600-U+27BF block: `A✓B` redrawn
with `XY` shows `XYB`, so the check every progress line writes is ONE column.
Taking the block as wide misaligned exactly the output this card exists for.
Writing over either half of a wide pair blanks the other, since a terminal
cannot leave one cell of a two-cell glyph standing.
`line\n\x1b[0m` does not end in a newline as a string yet its last parsed line
holds nothing visible, so the terminator check now reads the parsed lines — it
had added a blank row and inflated the collapse count.
A line with no cursor movement no longer builds a column buffer at all; only
its SGR is folded, so an `ls -R` or a 5k-line log allocates nothing per
character.
The `.terminalDescription` rule had been inserted into an existing grouped
selector, silently giving `.codeBody` description typography and changing its
bottom margin from 4px to 0 — a pre-existing surface this PR does not own.
Split out, `.codeBody`'s margin restored.
Three comments contradicted their code: the fixture's exit-marker line (still
claiming recovery from a marker deliberately removed), `bash-sample`'s header
(still routing a click to the details panel, and calling the consumer's cap the
block's own), and a DetailsPanel comment stacked above the wrong rule. The
ui-primitives README documented only the CR/BS half of the replay, so a reader
would expect `OK0%` where `100%\r\x1b[KOK` renders `OK`.
The native adapter's route was named deepseek, colliding with pi-ai's
catalog provider of the same name, so the two DeepSeek paths could never
be mounted side by side. The web settings page needs both configurable at
once. Compositions, fixtures, goldens, scaffolding defaults, and docs all
move together (pre-release, no shim); TUI/session-query-spill/
missing-credential goldens re-recorded through their keyless refresh
modes because provider-name length shifts box padding and spill
truncation points.
CI's coverage gate caught `ansi.ts` at 95.31% branches. My local check had
scoped coverage to that one file, which measures a different test set than
the gate does — the gate is the authority and it was right.
Three branches. Two are now pinned, each verified in a real terminal first:
`ab\r` + U+0301 + `x` shows `xb` — the redraw leaves the cursor at column 0,
so a combining mark has no cell to attach to and the terminal shows nothing
for it. It also revealed that the mark was kept when `cursor` was 0, which is
fixed.
`ab\rX\x1b[31m` then a plain line shows `Xb` unstyled and the next line RED:
the mirror of the reset case, where the scan ends styled while the last cell
is not, so the convergence has to OPEN the run at the line end for it to
reach the following line.
The third was `?? ''` on a `String.split` result, which always yields at
least one element — removed rather than tested, since no input can reach it.
The terminal and history pagination both treated the model-visible surface as
the human transcript. A landed compaction replacement therefore erased the
conversation it summarized — messages the reader had already seen — and a
model-only replacement copy consumed a page's `maxMessages` quota, which could
also split a compaction's provenance from the replacement citing it.
`dsh-session` now exports the marker split `isAppendSurfaceEvent` /
`isReplacementSurfaceEvent`. The terminal replays append-origin surface events,
keeps a shadowed step's tool cards paired through its append-origin assistant
message, and renders one dim marker where a compaction landed; the checkpoint is
recognized through the compaction seam's `isCompactCheckpointSource` contract,
not the shape of the replacement. `session.history` counts only append-origin
human messages. Everything model-facing keeps reading `session.surface`.
Eight findings, each terminal case verified in a real terminal first:
`\x1b[32mdone\rok\x1b[0m` then `plain` shows `okne` green and `plain` in the
DEFAULT color. The replay returned the last written cell's state, so a reset
landing after the final write vanished from both the text and the returned
state — and every build tool writes exactly that shape, so the color leaked
onto all later output. The replay now converges to the state the scan ended
in, which is also what it hands to the next line.
`abcd\b\x1b[1K|` shows ` |`: CSI 1K erases THROUGH the cursor column, and
the loop stopped before it. The erase mode also reads only the first
parameter now, since a terminal treats `1;2K` exactly as `1K`.
`éx\rYZ` shows `YZ`: a combining mark takes no column, so it attaches
to the cell already written instead of advancing the cursor and leaving the
`x` standing.
`中x\rA` shows `A x`: overwriting a wide character's lead cell leaves its
spacer as a blank rather than closing the gap, which would shift everything
after it one column left.
The banner lost its span when the gutter became padding — a plain block child
only reaches the content box, so the reserved column was painted in the body
color and the card's top-left radius drawn in it. Invisible in the light
theme, where banner and body share a token; visible in the dark one. The
header now pulls back across the gutter and re-insets by the same amount.
The replay trigger matches the same CSI shape the parser accepts, so a form
like `\x1b[1;2K` can no longer skip its own erase, and `replayLine`'s JSDoc
documents its new parameter and returned pair.
Docs: four places still described the dot as sitting left of the card surface,
which stopped being true when the gutter became the card's own padding, and
two fixture comments still referenced the exit marker that was deliberately
removed.