* fix(ui-primitives): keep tall menus inside the mobile viewport
* fix(client): anchor model picker with Floating UI
Use Floating UI for fixed model-menu placement and automatic viewport updates. Preserve top-end alignment and defer automatic focus until placement is ready. Guard asynchronous results across updates and menu teardown.
* fix(client): preserve menu blur under pinned group titles
Keep the current provider title outside the native scroll viewport so
model rows cannot bleed through during fast scrolling. Preserve the
enclosing menu fill and backdrop filter, and update both model pickers,
lifecycle tests and ARIA goldens.
Validation: doc-sync, full build, final type checks, focused lint and
all 12 model-picker browser checks pass. The full unit run has 42,597
passing tests and four failures in unchanged gateway, third-party-
notices and Inspector tests. The Inspector case passes in isolation.
* fix(client): stabilize floating panels during browser zoom
* fix(client): keep portal menus aligned during Safari zoom
* fix(client): commit floating menu positions before paint
* fix(client): unify floating panel positioning
* fix(client): validate grouped menu headings
* fix(client): hide inactive pinned menu headings
* test(client): await floating menu layout across consumers
* test(ui-chat): await folded reply menu positioning
* test(client): align current consumers with asynchronous panel layout
* fix(client): prevent mobile model search focus zoom
* refactor(client): inject mobile device state from layout
* fix(client): keep anchored panel layout synchronous
* fix(client): address panel review coverage and documentation
* fix(client): keep viewport changes focused on iOS zoom
* fix(client): cover anchored panel lifecycle and scrollbar colors
* fix(client): preserve zoomed model menu scrolling and alignment
* fix(client): hide empty model menus and stabilize viewport pan tests
* test(web): use native wheel input for zoomed viewport panning
* fix(client): keep automation pickers scrollable when zoomed
* fix(client): allow horizontal gestures across clock columns
* fix(client): ellipsize overflowing floating-panel labels
* test(web): make clock overflow fixture independent of system fonts
---------
Co-authored-by: Zrs000 <[email protected]>
Single RemoteError with a merge-extensible, domain-prefixed code map;
owners throw at the failure point; streams surface marked failures;
clients consume ctx.remote directly with isRemoteFailure as the only
discrimination point and construct no failure instances.
textContent/data-placeholder probes replace inputValue/placeholder reads,
evaluate-string selectors move to the composer anchor, queued fills wait
out the submit round-trip's read-only span, and the refreshed aria goldens
drop the hover tooltip the old interaction order happened to capture.
The client-bundle preset pins the production/development exports condition
(lexical's node-condition file selects its flavor with a top-level await a
CJS bundle cannot carry); the composer carries an explicit aria-label (a
div's data-placeholder does not name it the way a textarea placeholder
did); READMEs, the composer.bar slot doc, and the Agent Note record the
editor architecture; web e2e drives the contenteditable surface.
**A hand-declared route must not offer a reasoning effort.** The earlier
commit read the create card's missing control as drift and added one. It
is the other way round: such a model has no reasoning capability — pi-ai's
installed catalog is what supplies one, and it ships nothing under the
route — so `resolveModel` throws UNSUPPORTED_REASONING_EFFORT for every
model on it and the whole provider drops out of the picker. Verified
against the adapter, not inferred. The create card no longer offers it and
the editor withholds it on the directory's `declared` bit, which is the
real bug: that control has always been wrong for these routes.
**A blocked composer locked the way out of the block.** Reusing the
no-workspace inert posture disabled the model seat along with everything
else, so the bar asked for a model while preventing the one control that
picks one. A block now rides its own `blocked` owner prop: the textarea,
send, commands, plan seat, and access chip all lock, and the model seat
alone stays live.
**A Provider ID could derive an illegal credential reference.** The card
accepted a digit-leading id, whose derived `123_API_KEY` then failed at
the credential seam with a raw regular expression the user cannot act on.
The id must now start with a letter, and a test pins the relation between
the two rules rather than the regex.
A default naming a route the Models page has since removed left the
composer saying 选择模型 while the input still accepted a message, which
then failed inside the adapter mid-turn.
`session.prompt` now refuses with `model-unavailable` before opening a
turn. That is the enforcement boundary: the method stays callable no
matter what a client disables. `session.models` reports the same fact as
`routable`, and ui-model pushes a block through the new
`ctx.conversation.blocks` registry so the bar renders the disabled
textarea it already renders without a workspace, carrying the blocker's
own reason. The push direction is forced — ui-model already depends on
ui-conversation, so ui-conversation cannot read it back.
The gate is `routable`, not "matches no advertised group": catalog
membership is advisory, so a route serving a model it stopped advertising
is missing from the groups yet perfectly usable, and `null` before the
first load never blocks so a slow Host cannot lock a working composer.
The scaffold gains a route-only adapter for fixture-less keyless
scenarios. Registering zero providers is a test artifact — every product
composition mounts one — and the goldens that froze the seat's fallback
label now show the model those scenarios actually route to.
A row's stored profile could not tell a hand-declared gateway from a
shipped provider whose models someone narrowed — both look identical from
outside the adapter — so the Models page had no way to mark the routes a
deployment added itself.
The directory entry now carries `declared`, answered by the owning adapter
against its own installed catalog, and the page renders a Custom tag from
it. Absence stays "this adapter draws no such distinction" rather than
"shipped", so a route no adapter claims is labelled neither way.
Also records the default-route work's Agent Note and the e2e evidence for
all three changes: the composer switch writing the section, and the Models
page declaring a route with its own reasoning effort.