fix: put the recall chip in the header utilities, not the cell the sidebar's expand button owns
Release / release (push) Successful in 6s

The chip registered into `conversation.session.header.corner`, which is a
SINGLE cell the product already occupies: the client catalog lists its
occupants as `client-ui-sidebar-right ExpandButton` and its replaceRisk as
`shadows-shipped-ui`. Two registrations at priority 0 in a single cell are a
hard error inside the slot registry, and the error lands in the OTHER
plugin's `slots.inject()` callback: the renderer's `changed` handler catches
it, calls `stop()` on that inject controller — killing that contribution for
the rest of the fiber's life — and rethrows it from a microtask, which is the
uncaught `single slot ... already has a registration at priority 0 (registered
by dsh-recall)` in the console. So the chip was not merely racing the shipped
right-sidebar expand control; it was taking it down.

It now registers into `conversation.session.header.utilities`, a list cell
(`replaceRisk: none`, right-aligned, same Session scope and therefore the same
`useProjection` prop) as `{ id: 'dsh-recall', order: 20 }`, so it stays at the
right-hand end without competing for a seat.

The suite could not have caught this: the slot stub accepts any registration
and knows nothing about cardinality, keys or collisions. It now asserts the
chip carries a list id and that this plugin registers nothing into the corner.
This commit is contained in:
NotExpectedYet committed 2026-09-27 06:36:49 +01:00
1 parent 811871e978
commit 28df7a0e56
3 files changed
+61 -26

No files matched your search

+27 -12
View File
@@ -405,17 +405,32 @@ test('the chip and the sidebar tab survive a deployment with no settings provide
// profile without a settings provider loses the page and keeps the rest.
const { registered, tabTypes } = mount({ withConfigForms: false })
assert.deepEqual(registered.map((item) => item.meta.name).sort(), [
'conversation.session.header.corner',
'conversation.session.header.utilities',
'sidebar.right.pane.tab',
])
assert.equal(tabTypes.length, 1, 'the tab type is independent of settings')
})
test('the header chip is a single-seat contribution with no key', () => {
test('the header chip is an additive list entry carrying its own id', () => {
// The header's far-right corner is a SINGLE cell, and the product already
// occupies it with its right-sidebar expand control — the client catalog
// reports that key as `replaceRisk: "shadows-shipped-ui"`. A second
// registration at the same priority is a hard error inside the registry,
// thrown while the OTHER plugin's `slots.inject()` callback runs: the
// renderer catches it, stops that controller for the rest of the fiber's
// life, and rethrows it uncaught. So the chip belongs in a list cell, where
// a unique id makes it additive.
const { registered } = mount()
const chip = registered.find((item) => item.meta.name === 'conversation.session.header.corner')
const chip = registered.find((item) => item.meta.name === 'conversation.session.header.utilities')
assert.ok(chip !== undefined)
assert.equal(chip.meta.key, undefined, 'the corner is a single seat, so it takes no key')
assert.equal(chip.meta.id, 'dsh-recall', 'a list cell is addressed by its id')
assert.equal(typeof chip.meta.order, 'number', 'the order places the cell within the list')
assert.equal(chip.meta.key, undefined, 'a list cell takes an id, not a key')
assert.deepEqual(
registered.filter((item) => item.meta.name === 'conversation.session.header.corner'),
[],
'the corner is a single cell the product owns; a registration there shadows shipped UI',
)
})
test('the sidebar tab registers a type AND a body under the same id', () => {
@@ -440,7 +455,7 @@ test('the sidebar tab registers a type AND a body under the same id', () => {
test('the browser half contributes three seats, none of them the retired 0.1.5 one', () => {
const { registered } = mount()
assert.deepEqual(registered.map((item) => item.meta.name).sort(), [
'conversation.session.header.corner',
'conversation.session.header.utilities',
'plugins.bundle.config',
'sidebar.right.pane.tab',
])
@@ -590,17 +605,17 @@ function usageView(overrides = {}) {
test('the header chip renders nothing when the conversation never used it', () => {
const { renderSlot } = mount()
// The corner is laid out only while its occupant renders something, so this
// is the state that must cost no space at all.
assert.equal(renderSlot('conversation.session.header.corner', { useProjection: () => undefined }), null)
assert.equal(renderSlot('conversation.session.header.corner', { useProjection: () => usageView({ searches: 0, excerpts: 0 }) }), null)
// The utilities list draws nothing for an entry that renders nothing, so
// this is the state that must cost no space at all.
assert.equal(renderSlot('conversation.session.header.utilities', { useProjection: () => undefined }), null)
assert.equal(renderSlot('conversation.session.header.utilities', { useProjection: () => usageView({ searches: 0, excerpts: 0 }) }), null)
// And a host without the projection service at all.
assert.equal(renderSlot('conversation.session.header.corner', {}), null)
assert.equal(renderSlot('conversation.session.header.utilities', {}), null)
})
test('the header chip leads with what was kept out, not what it cost', () => {
const { renderSlot } = mount()
const chip = renderSlot('conversation.session.header.corner', { useProjection: () => usageView() })
const chip = renderSlot('conversation.session.header.utilities', { useProjection: () => usageView() })
assert.ok(chip !== null)
const text = String(chip.props.children)
assert.match(text, /Recall 3/)
@@ -613,7 +628,7 @@ test('the header chip leads with what was kept out, not what it cost', () => {
test('the chip tooltip carries the figures the chip has no room for', () => {
const { renderSlot } = mount()
const chip = renderSlot('conversation.session.header.corner', { useProjection: () => usageView({ rereadTokens: 500_000 }) })
const chip = renderSlot('conversation.session.header.utilities', { useProjection: () => usageView({ rereadTokens: 500_000 }) })
const detail = chip.props.title
assert.match(detail, /3 searches/)
assert.match(detail, /9 excerpts from 5 files/)