grep/glob return no presentResult on an error result, so an errored search had
no card and the keyed SearchRow showed only a red dot — the model-facing error
text (bad pattern, missing path, a nested run_code dispatch with no card) was
nowhere on screen. Add an error-text arm mirroring the file-mutation and read
rows. Added tests for the text arm and its name/code fallback. The unknown-kind
fallback in search-card-model is already guarded (returns null → generic path).
Route a `web` result card's raw-content fallback through the TUI's dim
Markdown path (render() only recognized `card: 'generic'` as markdown
content, so web fallback rendered as bare undimmed text).
Memoize renderFetchOutput per (result, maxOutputChars) so the registry's
twin output.render / output.presentationMeta calls on the same frozen
result run one HTML->markdown conversion instead of two.
Remove the trailing `</content>`/`</invoke>` protocol residue from both
sides of the web-result-card Agent Note and re-record the pairing.
- webCardModel returns null for an unknown web `kind` (wire from a newer host)
instead of drawing it as a malformed fetch, matching the unknown-`card` and
terminal-model wire-boundary default.
- Fixture WEB_SEARCH_RESULT/WEB_FETCH_RESULT and the source type derive from the
contract's ToolResultView via Extract, so a new contract field fails at the
type level rather than drifting silently.
- built-boot smoke asserts the web_search/web_fetch turns render their keyed
WebRow cards, giving the registration and wire projection an assembled check.
- DetailsPanel comment no longer claims the card omits content for search.
- ui-primitives README inline-Chinese limitation now lists WebBlock's controls.
Complete the stalled review pass: drop the unused useMemo import (rows flatten
inline), add SearchBlock to the ui-primitives README (both languages) with a
Search results section, and re-record the doc pairings. Search card behavior
and tests unchanged (313 pass).
The per-file coverage gate caught three uncovered paths in the error handling
this round added: the page banner for a failed row removal, the editor card's
transport-rejection catch, and `store.fail` itself.
Two of them are one click each — Remove with a rejecting write, Apply with a
rejecting write — so they are covered through the UI rather than by calling
the helpers directly. The third was a duplicated `error instanceof Error ?
error.message : String(error)` in two files; it becomes one exported
`messageOf`, which removes the branch from both call sites and gives the
fallback arm a home a direct unit test can reach (the lint rule forbids
rejecting a promise with a non-Error, so a rejection cannot exercise it).
A filename whose extension is an Object.prototype key (foo.constructor,
foo.__proto__) resolved to the inherited member through the plain-object index,
so a function reached the read card's lang hint and failed the tool-output JSON
validation, failing an otherwise successful read. Look the extension up as an
own property only. Added rejection tests, converted the zh Note headings to the
all-English sibling convention, and named the parallel-file-reads terminal
golden as the TUI-unchanged evidence in the Testing section (both languages).
The master merge moved the TUI composition into `apps/cli/config/base.cordis.yml`
and I carried the `settings-local` / `credentials-local` rows across without
adding them to the resolver manifest. Bare specifiers in an app config resolve
through that manifest's dependencies, so the whole tree failed to boot:
dsh: plugin(s) failed to load: @deepseek-ai/dsh-settings-local,
@deepseek-ai/dsh-credentials-local
which took every TUI PTY smoke with it. `verify-cordis-config` did not catch
it, so the boot smoke was the first signal.
The workspace browser's two hover-raised popups both died on the way to
them. HoverCard closed on the first pointerleave and rendered its card
pointer-events:none, but the card sits 8px off the anchor, so every path
to it crossed ground belonging to neither. The row action menus put
closeOnPointerLeave's handler on the portaled list, so aiming back at the
... trigger that opened it, or overshooting a list edge, closed it with no
window to come back.
usePointerGrace owns one cancelable delayed close (200ms) shared by both
atoms: leaving arms it, returning cancels it. The hover card becomes
hit-testable so resting on it holds it open, and Menu moves pointer-leave
dismissal to the wrapper span, where React's enter/leave traversal makes
trigger and portaled list one region.
Both gestures are pinned in the real browser lane; each fails without the
corresponding fix.