Skip to content

fix(client): register the turnTail entry under the list-slot contract - #755

Closed
hengne wants to merge 2 commits into
omdsh-dev:mainfrom
hengne:fix/turn-tail-list-slot
Closed

hengne wants to merge 2 commits into
omdsh-dev:mainfrom
hengne:fix/turn-tail-list-slot

Conversation

@hengne

@hengne hengne commented Sep 23, 2026 •

Copy link
Copy Markdown

Problem

On DeepSeek Harness 0.1.6-alpha.2 / 0.1.7-alpha.2 the plugin's client entry fails to activate:

list slot "conversation.chat.turnTail" requires options.id
web boot: 1 entry did not activate — dsh-better-sidebar: failed

conversation.chat.turnTail became a list slot in 0.1.6-alpha.2 (packages/client/ui-chat/src/client/contract/slots.ts); it was a chain slot before. registerTurnTailInterception registers the chain shape (select, no id), so ui-slots throws at registration and the whole plugin client goes down with it — sidebar tabs included.

Why not simply switch to a list entry

(First revision of this PR did that; Copilot's review was right.) A list host renders every entry, so a sidebar entry would sit next to the default ui-deliverables row and duplicate its chips. Shadowing the deliverables cell (same id, lower priority) would replace the whole tail, including its changed-files card, for turns the sidebar declines.

And the takeover is not needed on those hosts: the host's openFile funnel goes through sidebarRight.openResource(fileAddress), and this plugin registers its editor / files tab types in the extension band, which per tab-registry.ts takes over the builtin kind's address claims — so the default row's chips already open in the sidebar.

Fix

src/client/intercept.tsx

  • Keep the chain registration (select, priority: -1) for hosts ≤ 0.1.6-alpha.1 — behavior there is unchanged.
  • Swallow exactly the list-slot refusal (requires options.id) and return a working no-op disposer, so the plugin client activates on newer hosts; any other registration error is rethrown.
  • The selector is exported as selectTurnTail(store); JSDoc rewritten to describe both host contracts.

tests/turn-tail-intercept.spec.ts: two new cases — list host → nothing registered, disposer idempotent; unrelated registration error → rethrown. Existing chain-host cases unchanged.

Verification

  • pnpm typecheck, pnpm lint, pnpm test (127 files / 1348 tests) pass.
  • On a real 0.1.7-alpha.2 harness (16 plugins) the plugin activates; produced-file chips in the default row open in the sidebar editor.

🤖 Generated with Claude Code

conversation.chat.turnTail is a list slot since DSH 0.1.6-alpha.2: every
entry needs an id and the host injects the owner props instead of a
chain matched value. Registering with select and no id throws
'list slot "conversation.chat.turnTail" requires options.id' and the
whole plugin client entry fails to activate on 0.1.6/0.1.7.

Move the routing decision into an exported selectTurnTail(store) and a
thin entry component that renders nothing when the selector declines;
register with id and no select. Spec updated for the list contract.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Hengne Li <Hengne.Li@cern.ch>
Copilot AI lite review requested due to automatic review settings September 23, 2026 08:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The list-slot entry may duplicate the default produced-files row; documentation and suspended-state test coverage also need updates.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity · 1 Low severity

Open (2)
What changed in this PR

Updates turn-tail interception to support the host’s list-slot contract and restore plugin activation.

Changes:

  • Adds a list-slot ID and moves routing into selectTurnTail.
  • Conditionally renders produced-file entries.
  • Expands registration and selector tests.
File Summary
tests/​turn-tail-intercept.spec.ts Tests list-slot registration and selector behavior.
src/​client/​intercept.tsx Implements list-slot registration and routing. Critical (1 vote): may render duplicate produced-files rows. Nit (2 votes): stale chain-slot documentation. Nit (1 vote): missing suspended-state selector test.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/client/intercept.tsx Outdated
Comment on lines +136 to +139
const SidebarProducedFilesEntry = (props: { openInSidebar: (path: string) => void, onShowInFolder: (files: readonly string[]) => void }) => {
const matched = select(props)
if (matched === null) return null
return <SidebarProducedFiles {...props} matched={matched} />

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — a list entry sits next to the deliverables row and duplicates it, and shadowing the deliverables cell would also hide its changed-files card. Reworked: the chain takeover stays for hosts ≤ 0.1.6-alpha.1, and on list-slot hosts the registration is skipped (only the requires options.id refusal is swallowed; anything else rethrows). No second row there; the default row's chips already open in the sidebar because this plugin's editor/files tab types are in the extension band, which takes over the builtin kind's address claims. Spec covers the skip and the rethrow.

Comment thread src/client/intercept.tsx Outdated
Comment on lines +129 to +133
// `conversation.chat.turnTail` is a LIST slot since DSH 0.1.6-alpha.2 (it was
// a chain slot before): every entry needs an `id`, the host renders all
// entries and injects the owner props instead of a chain `matched`, so the
// routing decision moves into the entry component, which renders nothing
// when the selector declines. Registering with `select` and no `id` throws

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewrote the leading JSDoc: it now documents both host contracts (chain ≤ 0.1.6-alpha.1, list ≥ 0.1.6-alpha.2) and why the list-slot hosts skip the takeover.

Review follow-up: on a list-slot host (DSH >= 0.1.6-alpha.2) a second
turnTail entry does not replace the deliverables row, it is rendered next
to it and duplicates the chips. The takeover is not needed there: the
host's openFile funnel goes through sidebarRight.openResource, and this
plugin's editor/files tab types sit in the extension band, which takes
over the builtin kind's address claims, so the default row's chips
already open in the sidebar.

Keep the chain registration (select, priority -1) for older hosts and
swallow exactly the list-slot refusal ('requires options.id') so the
plugin client still activates; any other registration error is
rethrown. JSDoc rewritten for both contracts; spec covers the skip and
the rethrow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Hengne Li <Hengne.Li@cern.ch>
@hengne

hengne commented Sep 24, 2026

Copy link
Copy Markdown
Author

Superseded — v0.21.1 ships the same conclusion independently: the whole turn-tail interception is removed (only openSidebarFile kept in sidebar-file.ts), which is a cleaner fix than my chain/list branch — no registration at all on hosts where a second row would duplicate the deliverables one. Closing in favor of the shipped release. Thanks for the review, and for getting to this before I could revise again!

@hengne hengne closed this Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants