The publishable-terminal filter and the order-loop guard are two call sites, but
deleting the order-loop one left all 1150 runtime tests green. With no host
groups the projection returns the legacy nav order, which is built from every
terminal tab rather than the filtered set, so that guard is the only thing
holding a web mirror back on this path.
The fallback that rescues editor tabs the group projection missed takes each
tab's `groupId` on faith. A groupId naming no host group still gets a published
`tabGroups` entry — and since `tabGroupLayout` is the host's layout pruned to the
published set, pruning can never add a leaf for it, so the tab arrives in a group
no client can give a pane. Resolve such a tab into a group that did publish.
Reported by the same audit as the two above, and unlike them I could not reach it:
hydration re-homes groupless tabs (`adoptGrouplessTabs`) and makes the layout span
every group (`layoutSpanningGroups`), and group close collapses the layout with
it. The guard is here because the projection must not be the thing that invents
the id, not because a host state was found that hands it one.
`unplacedPublishedGroupIds` states the placement half of the invariant, and pins
the guard on its own: deleting it turns that assertion red with the by-name
expectations removed.
A terminal publishes one row per pane, and `getRuntimeLeafIdsForTerminal` returns
none when there is no live pane and no persisted layout — which is exactly what
`createTerminalTab` seeds (`emptyLayoutSnapshot()`) until TerminalPane mounts. The
group still named it, so the snapshot published `tabGroups`, a `tabGroupLayout`
leaf and an `activeGroupId` for a tab the client is sent no row for. Off the
active worktree, where nothing mounts, that state lasts until the user goes there.
Hold such a terminal out of the group projection. The existing machinery does the
rest: an emptied group leaves `tabGroups`, its layout leaf is pruned with it, and
`resolvePublishedActiveGroupId` moves the pointer.
Old clients are unaffected in outcome: `buildHostToLocalTabIdMap` never had a key
for the tab, so `buildMirroredHostGroups` already dropped it from the group's
order and skipped a group left empty. The host now sends what the client was
already computing.
The published group->tab invariant read "no group may name a tab absent from
`tabs`". That is not the contract. A terminal occupies its group under the parent
tab id but publishes one row per pane, so `tabOrder` names `term-a` while `tabs`
carries `term-a::<leafId>`. The rule passed only because both fixtures were
browser/editor-only; adding an ordinary mounted terminal to one fails it.
The real rule is resolvability, not equality: a group may only name a tab the
client can resolve, and the client's `buildHostToLocalTabIdMap` keys terminals by
surface id *and* `parentTabId`. Move the invariants into a named module that says
so, and put a terminal in the fixture so the terminal case is covered rather than
avoided.
Also pins a guard that was silently unpinned: the mirrored-tab check runs twice —
once choosing which terminals the group projection may name, once emitting rows —
and deleting the projection copy left the whole runtime suite green, because no
fixture had a web-only mirrored terminal inside a group.
Review follow-up. `resolvePublishedActiveGroupId` was a second copy of the rule
already in `projectLocalStructuredSessionTabs`; extract it once and call it from
both, so the structured projection cannot drift from the publisher.
Ablating the shared rule left the structured projection's own tests green, so
pin it there too: a snapshot whose active group holds no visible structured tab
must not keep pointing at it, and must fall to null when nothing survives.
Also repairs a cross-file comment reference that dangled after the relocation.
When every tab in the active group is unpublishable — a combined-diff tab,
a workspace-document browser tab — the group is held back from `tabGroups`,
but the snapshot still published `activeGroupId` naming it. The client was
told the active group is one it never received.
Resolve the published pointer against the published group set: keep it when
the group survived, else the first published group, else null. Both fallbacks
are values three of the three read sites already handle today.
The published invariants covered group->tab dangling references and the
snapshot's own `activeTabId`, but nothing checked `activeGroupId` against the
published groups. The extended invariant now checks every published group
reference — `activeGroupId` and the `tabGroupLayout` leaves — and moves to its
own file so the claim has a name.
STA-5724
* Improve markdown rich mode: distinguish HTML tags from placeholders
- Consolidate size limit and unsupported content checks into single function
- Refine HTML/JSX detection to validate against rehype-sanitize's known tag names
- Allow bare placeholders like `<id>` and `<project-id>` in rich mode rendering
* Add decision explainer for markdown rich-mode rendering fix
* rm html explainer
* fix(browser): close guest-owned split tab
* fix: check sourceId before toggling floating panel on close
The empty-panel toggle is the ambient fallback only. Guest-initiated
closes (with sourceId) target the main workspace and should not toggle
the panel.
* test(browser-split-shortcuts): remove terminal-mirrors close test and un
Removes test case that verified Cmd+W closes guest-owned browser splits when
active-tab mirrors point to a terminal, along with the helper function and
unused fixture properties that only that test required.
* Clarify automation history unavailability and improve recovery UX
- Improve error message to explain run history is unavailable due to host
version requirements, not automation failure
- Hide misleading "0 runs" count badge when history is unavailable
- Deep-link "update server" recovery actions to specific runtime environment
in settings instead of pane root
- Add test coverage for run count hiding and recovery targeting
* Watch for deep-linked settings targets that render asynchronously
Some settings panes (such as Remote Orca Servers) fetch and render their
rows asynchronously. Deep links can name targets that don't exist yet,
but the scroll effect has no way to know when they finally mount. Add a
MutationObserver-based watcher to detect when async rows appear and
trigger scrolling.
Refs #16646
Unify native, WSL, and direct SSH conflict checks behind the execution host, remove the duplicated SSH classifier, and cover orphan/configured remote behavior across both paths.
Both files describe paths with POSIX literals while their subjects compose
paths through `node:path`, so the assertions only hold where the separator
happens to be `/`.
`node-markdown-document-discovery` keys its fake tree at `/repo/docs` and
`/repo/one`, but `discoverMarkdownRelativePaths` descends with
`join(absoluteDirectoryPath, entry.name)` — `\repo\docs` on win32. The child
lookup misses, `readDirectory` yields nothing, and the walk stops at the root:
`docs/guide.mdx` disappears and the depth-limit case never reaches its limit,
so it resolves `[]` instead of rejecting. Keying the children with `join` walks
the tree the subject actually walks.
`git-fetch-head-lock` expects `cwd: '/tmp/repo'` from a subject that returns
`path.resolve(cwd, 'repo')`, which is `C:\tmp\repo` on win32. Asserting through
`path.resolve` pins the behaviour — that `-C` and `--git-dir` are resolved
against the cwd — rather than the separator of whichever machine runs the suite.
Verified on Windows 11: the two files go from 3 failed / 12 passed to
14 passed / 1 skipped, and the wider `src/shared` run shows no regression.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Make worktree palette hint rows keyboard-clickable
Hint entries like "See more" are now CommandItems that can be navigated with arrow keys and activated with Enter, instead of being non-interactive divs. This allows keyboard-only users to access the expand actions without mouse interaction.
* Make worktree palette "See more" keyboard-navigable
Preserve cursor position when expanding via keyboard: auto-select the first
newly revealed item at the previous index and restore input focus.
* test(cross-version-wire): derive skew expectations from the baseline under test
The cross-version wire job pairs current code against whichever release tag is
newest, so a hand-written "the old side does not have X" assertion expires by
itself: v1.4.192 was the first tag containing the SnapshotStart `terminalOwner`
field, and cutting it turned the new-client/old-server pairing red on unrelated
pull requests with no code change anywhere.
Read what each build publishes from that build. Each host is now paired against
a client of its own version to produce a reference, and the skewed pairings are
compared against that reference, so the expectation is whatever the release
actually shipped. The same class of assertion in the agent-session suite —
"the old build advertises no structured capability and registers no structured
method" — becomes "each build's advertisement agrees with what it registers",
and the "client too old to know this capability" is derived by removing the
capability from the baseline's own list.
The guard is unchanged in strength: a field the old host still publishes may not
be dropped, skew may not change what a host puts on the wire, and a new pairing
asserts the oracle still stalls when a peer cannot decode an opcode the other
side sends.
* test(cross-version-wire): exercise release structured methods
* test(cross-version-wire): load the registered method manifest
* test(cross-version-wire): assert execution, not registration, on both host gates
The release-shaped checkout gate accepted any reply that was not
method_not_found, so a registered-but-throwing handler passed it. The
capability gate asserted a shared host spy had been called at all, so the
second method mapped to that spy could stop reaching the host unnoticed.
* test(cross-version): make the release-shaped skew cover the whole agent-session manifest
The release-shaped checkout is the only place the "registered means usable"
claim is executable today — the baseline release registers none of these
methods — and it was exercising one of sixteen. A handler registered and
returning an execution error passed the suite.
- Declare each method's result in the manifest, so "answered" is the contract
rather than "did not say method_not_found".
- Give each build a seam to install a host into its own module slot; a release
checkout has its own copy, so the working tree's host was never this
dispatcher's, and every host-backed method answered
structured_agent_session_unsupported — the capability gate's own words.
- Run one execution contract over both skews instead of two divergent loops.
- Pair the AI Vault never-called spy with a positive control; renaming the
runtime method it watches left it green.
---------
Co-authored-by: Brennan Benson <brennanbenson@Brennans-MacBook-Pro.local>