Holds the session.history response at the browser's network boundary so
the auto-selected open is observable, then asserts the visible frame and
the recorded phase timeline. Registered host-plane like the other
scaffold-booting e2e files (host include + client project exclude).
The two shipped surfaces offered different tools for no recorded reason:
session checkpoints, tool-result pruning, the goal tools, and Ralph were in
`tui.cordis.yml`; `tool-todo` and web search were in `web.cordis.yml`. Neither
offered session search, a string-replacement editor, or a repeat-tool guard,
though none of the three is surface-specific.
Move the rows that are not surface-specific into `base.cordis.yml` and add
those three. Web search moves there too — the TUI decision the change that
made it a Web default deferred. Both surfaces now assemble the same 27 tools.
This adds only. No row is removed from either surface and no existing row's
configuration is edited: executors, sandbox composition, access defaults,
`tools.mode`, and the workflow tool are exactly what they were. Two rows stay
surface-specific: `tmux-context` (no terminal multiplexer in a browser) and
`session-reference` (its index has one writer owner).
Ship `dsh-mcp-client` as a dependency without a row: the plugin mounts one
server per instance and `command` is required, so a default would name a
third-party server and spawn it outside `ctx.bash` on every launch. The CLI
README carries the YAML for mounting one from a personal config.
Review flagged that "equal by construction" rested on an engine behaviour I
had not measured: `scrollbar-gutter: stable` only equalizes the layers if the
engine applies it to `overflow: hidden` the way it does to `overflow-y: auto`.
Measured it on the running app across the three engines Playwright ships,
and the property does not hold up.
engine .input / .backdrop / .mirror wrap width
chromium 776 / 776 / 776 (768 / 768 / 768 with the declaration)
firefox 776 / 776 / 776 (unchanged by it — overlay scrollbar)
WebKit 768 / 776 / 776 (unchanged by it)
WebKit reserves for `overflow-y: auto` and not for `overflow: hidden`, so the
declaration left .input at 768 against 776 — exactly the gap it was meant to
close — on the one engine where that gap is observable at all, while costing
every chromium user 8px of text column unconditionally. Reverted: the
composer's metrics are now the same as before this PR.
The WebKit gap predates this change and is not closed here. It is recorded in
the Agent Note with the numbers, and the browser scenario asserts the equality
on the lane's engine so a regression into that state fails loudly. The mirror
is unaffected on WebKit for the drafts measured — the extents still agree — but
a draft whose wrapping turns on those 8px would clamp it.
The review's monotonicity concern resolves the same way: the declaration was
never worse than master, because WebKit already measured 768 against 776
without it. It simply was not better.
Also from this round: the wrap-width assertion now covers .mirror as well as
the two glyph layers — it is the height authority, so a mirror alone wrapping
wider would measure the box short and clip content below the 14-line cap with
every other assertion green. Plus `renderGeometry`'s missing `@param
trailingNewline`, and both e2e tsconfig lists restored to alphabetical order.
A composer draft past the 14-line cap could not be scrolled: the caret and
the selection moved, but the words stayed frozen at line 1, so the tail of
anything longer than the cap was unreachable while writing it.
The composer paints its text in two stacked layers. The textarea owns the
value, the selection and the caret but renders its own glyphs transparent;
every visible character is painted by the decoration backdrop beneath it,
which also carries the claim-token highlight, the chips and the ghost hint.
The backdrop is `inset: 0; overflow: hidden` — clipped, not scrolled — and
nothing linked its offset to the textarea's. Below the cap both layers rest
at 0, which is why the defect hid behind every short-draft screenshot and
fixture.
InputBar now mirrors the textarea's scrollTop onto the backdrop, from a
`scroll` listener (every gesture and every caret-driven scroll) and from a
layout effect keyed on the committed draft (an edit reflows both layers
without necessarily firing a scroll event).
Scrolling is layout, so jsdom cannot show this: the unit spec stubs both
offsets and proves the mirroring paths run, while a new browser scenario
measures the user-visible fact against the built client with a DOM Range
over the backdrop's own text — after a wheel gesture over a 40-line draft
the last line is on screen and the first has scrolled out. Confirmed both
directions: with the mirroring reverted and the packages rebuilt, the
golden reads `last draft line is on screen: false` while `textarea moved:
true`.