The provider-card seat's keyConfigured now derives from the reference the
page would use — the profile's apiKeyEnv, or the page's derived
<ROUTE>_API_KEY while the profile names none — so the add-provider draft
agrees with its own editor about an existing conventional credential (the
store joins the derived describe in the same batched call, as
ProviderRow.derivedCredential). ModelsSectionProps makes the renderSlot seat
required so a direct render that forgets it fails to compile; the one such
render in provider-form.client.spec regained a real mount and the test
boilerplate collapsed to renderSlot={() => null}. The extension-slots Agent
Note now states the keyed cell's real override rule (same priority throws,
a different priority shadows), and docs/subsystems/slots.md carries the two
new seats in its hierarchy, both languages.
pi-ai credentials carry optional members as explicit undefined (a github.com
Copilot grant holds enterpriseUrl: undefined), and the store bridge committed
the object verbatim, so the credential store's strict validator refused the
write and sign-in failed after the provider had already authorized it.
toRecord now drops explicitly-undefined members and renders undefined array
entries null, exactly as JSON.stringify would; everything else passes through
untouched so genuinely unstorable values still fail loud at the store.
Review follow-ups: wait on the caller's own locator instead of assuming
it is the page's first composer, describe the actual read-only window
(submit adjudication and locked states — a running turn stays editable
for queueing), drop the pre-Lexical narration from the JSDoc, and record
the gesture-semantics trap as an Agent Note.
The Models section now declares two SlotMap seats for out-of-tree plugins:
settings.models.provider-card (keyed by the row's settingsNs, dispatched on
saved cards, the first-run setup posture, and the add-provider draft, with
the row view, configured join, and confirmed api-key state as owner props)
and settings.models.footer (ordered list after the rows and add controls).
Without registrants both seats render nothing. First consumer: the
llm-pi-ai-oauth companion plugin's sign-in surface.
`import.meta.resolve`'s `parentURL` argument takes effect only under
`--experimental-import-meta-resolve`, which no launch passes, so the
fallback resolved from this module rather than from the harness — the one
question it existed to answer. The disk walk is the whole answer now, and
the refusal memo it needed goes with it. A `file:` URL joins the file
branch rather than the package one, where a resolver would only normalize
it and report a missing target as present, and a row is skipped on the
Loader's own `Boolean(disabled)` so `disabled: 0` is checked like the
Loader checks it.
A broken card says so through `aria-disabled` rather than `disabled`, and
refuses the pick in its own handler. `disabled` took it out of the tab
order, which with the reason moved onto the badge left it unreachable
without a pointer — reachable before this change, so hiding it was a
regression rather than a path that never existed.
Both notes this decision partly supersedes are updated in place and
cross-linked, one README pair loses an editing residue that repeated a
sentence, and the single-row diagnostic no longer reads "row row 1".
A running turn disables the composer by flipping contenteditable to
false on the same element. fill() throws there immediately — a disabled
textarea used to hold it back through actionability — and isEnabled()
reports true for a div regardless, so the permission-policy scenario's
post-settle wait never waited and its next gesture raced the re-enable
render. The window is a few frames wide; #3083's Remote-routed subagent
control stretches settle enough to hit it on CI.
writeComposerDraft now waits for contenteditable="true" before acting,
and the permission-policy scenario drives all four sends through it with
the settle wait pinned to the attribute.
Discovery proved only that a composition parsed, so a preset naming a
package a later rename took away kept a healthy card and its place in
every picker until a person switched to it. It now resolves each row it
can prove will start, reading the package off disk and falling back to
the resolver only for names that look absent — the resolver costs a
synchronous hooks-thread round-trip under the source launch's tsx hook,
which the walk avoids for every row it clears.
The mount diagnostic followed `AggregateError.errors` but never a cause,
so a group that failed on two rows named neither. It now follows a cause
that carries more than its own message.
A refused switch left the chip's label snapping back with no account of
why, which is the only account there can be for a preset that resolves
and then refuses. It announces through the shared Toast, which gained a
caller-set hold for a cause that names packages and rows.