File Activity only folded top-level tool-result nodes, so PTC sessions
(run_code as the only tool) showed an empty card and the Minimal preset
went unseen. The fold now covers every surface that executes a file op:
- Nested Code-Mode calls: each settled sub-call in the conversation
node's subCalls tree folds as one op, attributed to the nested tool
and located on its parent run_code result (FileOp.parent; the op's
own seq names the dispatch event, which has no surface row). The op
log shows the program's description as the "why". Narrowing is fully
defensive: malformed blocks drop out, and the walk is bounded by a
depth cap plus a visited-set cycle guard.
- str_replace_editor (Minimal preset): view reads; create,
str_replace, and insert write with their own line deltas.
- Searches resolve to real files when the result's presentation meta
carries the complete matched-file list (FileOp.hits shows the count,
include filters join the detail); a capped or malformed meta falls
back to the searched path/pattern as before, and meta attribution
survives an aged-out call head.
Closes#24
Hovering around the trend chart used to change the detail panel's height: the brief section only rendered rows that resolved (the turn opener vanishes outside retention, usage-only replies carry no reply node), and collapsed entirely - margins included - when nothing did.
- the brief lane ALWAYS renders: a fixed three-row min-height, rows appear inside it, unknown steps leave blanks instead of collapsing
- the In row is one of those constants - an empty step shows a muted "(nothing new)" placeholder in place of its chips, and stays inert (a row with no node has nothing to locate)
The 26px gradient overlays at the chart's left/right edges (shown when the history overflows) read as washed-out white patches; drop the overlays, their edges state machine, and the .lc-chart-fade styles. Scroll anchoring, turn-label centering, and the hover tip are untouched.
The absolutely positioned tooltip contributed scrollable overflow inside
.lc-chart-scroll: a wide reply preview hovering a right-edge bar inflated
scrollWidth and toggled the horizontal scrollbar per hover, jumping the
whole trend card. A new positioned .lc-chart-wrap now anchors the edge
fades and the tip beside the scrollable content; syncTip glues the tip to
its bar visible slice via transform (edge-clamped, centered when wider
than the viewport), which also pins the left fade that used to drift
off-screen with the scrolled content.
A new Agent Network card at the foot of the Context tab: the whole agent family (current agent, ancestors, subagents at any depth) as a live, fully responsive node map — fused context rings per session, family-colored lineage, halo status language, hover inspector, and click-to-jump into any agent's session. 693 tests, 100% coverage.
Order registries as official npm then npmmirror; each source failure
(non-ok response, missing/non-string version, network error) advances to
the next source, and only an all-source failure resolves to null so the
plugin info card keeps its baked-in version. TTL cache and module-level
promise reuse are unchanged.
Two compounding defects made every incoming event flash the whole card:
- Rows were keyed by seq PLUS reverse-index; the list renders newest-
first, so an appended event lands at index 0 and shifted every
existing row's key, forcing React to destroy and rebuild the entire
list on each push. Rows now key on the durable seq alone, so a push
prepends one row and keeps every other row's DOM node.
- The truncation-title layout effect had no deps: every render (hover
included) re-read scrollWidth/clientWidth for every label — one
forced synchronous layout per row — and rebound the resize listener.
It now resyncs only when the events list changes, with the resize
listener bound once (root resolved inside the callback so the
empty-to-non-empty transition keeps it live).
The chart's layout effect ran after every render (no deps), so any
hover/select re-render near the right edge re-ran the stick-to-end
check — and by the time it read el.scrollWidth the value was already
the post-append width, which could misfire the anchor. The effect now
runs only when granularity, focusTurn, or the request list changes,
and the near-end check compares against the PREVIOUS pass's scrollWidth
(kept in a ref), so a reader near the right edge still follows an
appended bar while a mid-chart reader is never yanked back, and a
hover/select render never touches the scroll at all.
React 18 dev replays a failed render through a fake DOM event; jsdom
reports it as an uncaught window error and vitest forwards it to an
uncaughtException, which no console.error spy can catch. A window error
listener that prevents the default keeps the deliberate throw inside the
test, alongside the spy silencing React's own boundary message.
- Remove the MAX_OPS=8 cap: an expanded file renders its whole operation
log instead of folding older ops behind a static "+n earlier ops" line.
- Remove the unresolved counter and footnote: a file-kind call whose
arguments aged out of the retained window names no target and is now
skipped outright, with totals[kind].ops incremented only after path
resolution — every op the card counts, it also rows.
- Drop the files.unresolved/files.moreOps strings and the dead
.lc-fa-more/.lc-fa-note rules.
The file list now defaults to most-operations-first (ties broken by
recency) and a lc-gran toggle on the meta strip switches to
most-recently-touched order; the sort applies to the filtered copy, so
the fold keeps serving entries latest-first.
The bottom-right card no longer lists model-visible messages; it answers
what the agent DID to the user's files up to the picked step. Tool-result
nodes (live + archive) join the conversation snapshot for call arguments
and fold into one row per file with per-purpose counts (read/written/
searched), estimated line deltas from edit/write arguments, multimodal
and directory form glyphs, and failure dots. Header chips double as
purpose/form filters next to a path search box; a row expands into the
file's operation log, and each operation reveals the exact tool result
in the Context browser. The card scrubs live with the trend chart pick
(exclusive next-request bound); ops whose paths aged out of retention
are honestly counted in a footnote.
The chip row lived in CurrentComposition, was fed the fold's per-tool
toolList summary, and the chips focused the Context browser on a tool
via a one-shot toolFocus callback. The browser already exposes the
tool schemas through its headers section, so the chip row was a
duplicate entry point that no longer pulled its weight.
Removes the lc-tools DOM block, the toolFocus/onToolFocusHandled
prop wiring across ContextView/ContextModal/Browser, the
'tools.top'/'tools.more' translations, the .lc-tools* CSS, and the
now-unused toolList summary across the wire + state schemas
(stateVersion 9 -> 10). estimateToolSchema stays because headers.ts
still prices individual tools for the browser's tool-schema cards.
The Context browser joined full content against the chat conversation
window; when a surface node's seq sat outside it, the fallback paged
blindly backwards through session.loadOlder() (up to 20 pages of 50
messages prepended into the shared chat window) and finally dead-ended
on a static note.
sessions.history already supports seq-anchored pages cut on whole
append-origin message boundaries, so ONE call with beforeSeq: seq + 1
returns exactly the page holding the event whenever the durable log
still has it. The browser now:
- fetches on miss with that single anchored read and maps the raw page
into the node shapes it already renders (new client/historyPage.ts:
user messages, assistant blocks, tool results paired with their
in-page call head, compaction checkpoints with their summary);
- merges fetched nodes under the zero-cost window join (join wins);
- reports honest states instead of one dead end: loading, absent from
the session log (compacted or cleared), failed with a retry button;
- drops the sessions.provide contribution and the MAX_AUTO_PAGES
machinery entirely - the chat scrollback is never mutated again.
Hosts without the connection api keep the static preview-plus-hint
degradation.
The old suite exercised the built lib/ bundle, so v8 remapped almost
nothing (8% src coverage) and every run needed a build first. The new
suite imports src/ directly under two vitest projects (node for
host/shared, jsdom for client), rendering real React 18 trees against
the real dictionaries and ui-primitives, with faithful in-memory
harness faces (cordis inject/effect, locale chain, slots, sessions
provide) instead of plugin mocks.
- 43 fine-grained, stateless, single-module spec files (606 tests)
forked across workers: ~2.5s wall, no build step
- v8 thresholds at 100/100/100/100 per file over src/**
- host specs drive the real projection definitions (init/apply/view)
plus one full cordis + SessionStore + SessionProjectionRegistry
integration; the framework's snapshotJsonValue pins the
projection-cache precondition on every intermediate fold state
- oxlint's src ruleset now covers tests/** as well
- typecheck covers the tests via tsconfig.tests.json