Commit Graph

5450 Commits

Author SHA1 Message Date
Peter Steinberger 63bc139872 fix(ui): unread badge latches un-clearable when the read patch is never sent (#123252)
sessions.patch resolves null without sending a request when the
connection scope cannot be captured, but the read-marker guard only
unlatched on promise rejection — so the once-per-episode latch stayed
set with no request in flight, and the unread badge stayed lit on the
open session until navigating away and back. Treat a null resolution
like a failure.
2026-08-13 21:12:21 -07:00
Peter Steinberger dc61fe5abe fix(ui): automations model picker renders raw "common.default" i18n key (+3 sibling call-site drifts) (#123238)
* fix(ui): automations model picker shows raw "common.default" key instead of Default

t("common.default") never existed in the catalog, so the picker's
inherit option rendered the literal key string in every locale
(introduced in #122964 when cron's free-text model input became the
shared model picker). Use the existing quickSettings.model.default key.

Same-sweep i18n call-site drift fixes:
- sessions view Reasoning override now routes through
  buildSessionLevelOptions like its Fast/Verbose siblings (adds the
  missing sessionsView.stream key; deletes the unlabeled duplicate
  withCurrentOption helper)
- activity entry summary collapses the placeholder-only
  activity.entrySummary pass-through (two silently dropped params) to
  the direct label call; orphan key removed
- group-delete confirm drops its dead {group} param (string has no
  placeholder since the reword)

* test(ui): compact multi-line expects in cron view test to stay under max-lines

The file sits at the 1000 counted-line cap on main; extracting named
intermediates keeps the new inherit-option regression net-neutral.
2026-08-13 21:10:32 -07:00
Peter Steinberger f820897670 fix(ui): typing a decimal point into numeric config fields deletes the stored value (#123210)
* fix(ui): typing a decimal point into numeric config fields no longer deletes the stored value

For <input type=number>, partial numeric text ("0.", "-", "1e")
reports value === "" with validity.badInput. The input handlers
treated that as an intentional clear: they committed undefined, the
800ms autosave persisted the removal, and the identity resync wiped the
user's in-progress text from the DOM. Typing 0.5 into sampleRate stored
5.

Resolve numeric input state (badInput/empty/value) once in a shared
helper used by both renderTextInput's number branch and
renderNumberInput, absorbing their duplicated parse/commit/revalidate
logic. badInput now surfaces the invalid-number validity message
without committing; genuine clears keep removing optional overrides.

* test(ui): use plain validity objects instead of spreading ValidityState

Spreading a class instance drops its prototype (no-misused-spread); prod
only reads validity.badInput, so a plain object is the honest stub.
2026-08-13 21:08:04 -07:00
Vyctor H. Brzezowski 72ec2a0bb1 fix(ui): show resolved agent names in New Session (#122463)
* test(ui): reproduce New Session raw agent labels

* fix(ui): resolve New Session agent names

* fix(gateway): preserve agent name source

* chore(protocol): regenerate Swift models

* chore(plugin-sdk): refresh API baselines

* fix(ui): scope agent target label fallback

* chore: format agent identity schema

* fix(ui): honor resolved agent name precedence

---------

Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
2026-08-13 21:04:39 -07:00
Peter Steinberger b9c6789560 feat(secrets): authenticated egress substitution proxy with destination binding (#123216)
* feat(secrets): add authenticated egress substitution proxy

* feat(secrets): bind egress substitution to hosts

* ci(codeql): classify egress proxy bypass tunnel in network boundary query

* refactor(proxy-capture): use the canonical IP parser instead of node:net

* fix(secrets): compare proxy tokens with a process-keyed MAC
2026-08-13 20:49:31 -07:00
Peter Steinberger f2862fa265 fix(ui): read popover cost from usage.cost and key usage empty state off content (#123269)
The chat message meta popover read message.cost, a field no producer ever
writes — AssistantMessage nests cost under usage.cost — so the $ line was
permanently dead. Read usage.cost with the bare-field fallback removed to a
single expression.

The usage page empty state keyed off !data.totals, but the gateway always
returns a totals object (all-zero when idle), so the "no usage data yet"
card never rendered; it also ignored data.error, so a failed load could show
the empty card under the error callout. Key it off actual content and never
render it in the error state.
2026-08-13 20:46:20 -07:00
Peter Steinberger 4f5ec03b38 fix(ui): selecting an adopted catalog session renders it twice in the sidebar (#123250)
Adopted catalog keys are excluded from thread roots because the Coding
catalog section renders them live, but the selected-session fallback
re-insertion had no adopted guard — its lineageRoot sibling does. Select
an adopted Codex/Claude session and it appeared both as a top-level
thread and inside the catalog group, both aria-current. Add the same
guard to the fallback filter.
2026-08-13 20:44:15 -07:00
Vyctor H. Brzezowski 7025da83e8 fix(ui): rotating another device's token ends with nothing on screen (#121361)
* fix(ui): report the outcome of a cross-device token rotation

Rotating another device's token ended with nothing on screen. The Gateway
returns the replacement only to a device rotating its own token
(shouldReturnRotatedDeviceToken; docs/cli/devices.md), so every rotation of
another device answered without a token and the Control UI, which only rendered
when one came back, showed no dialog, no message, and no error while the target
device was invalidated and disconnected.

device.token.rotate now records how it delivered the replacement in a
tokenDelivery discriminator ("in-band" | "withheld-cross-device"), additive and
optional so gateways that predate it still decode. The Devices page reports the
recorded fact rather than inferring one from a missing token: the show-once
reveal for an in-band rotation, and an outcome dialog for a withheld one that
states the token is not shown here, that the device was disconnected, that it
picks up the replacement on its next connect with a Gateway token or password,
and that a device which can only sign in with its device token has to be paired
again.

The reveal component takes an optional secret: with one, dismissal stays refused
because the value is unrecoverable; without one there is nothing to lose, so
Escape and backdrop close it like any dialog.

* fix(ui): lead the rotation outcome with what happened, not why

The withheld-rotation dialog opened on a negation, stacked three clauses of
protocol reasoning, and left "do I have to do anything?" to the last sentence.

It now reads outcome, then next step, then the one exception, with the security
rationale demoted to a muted trailing line: the device is named, told to be
signed out, and the common case says nothing else is needed. The rotate callback
carries the row's resolved label so the dialog names the same device the operator
clicked instead of rederiving the precedence.

The reveal component takes paragraphs plus an optional muted note, so an outcome
and its exception stay separate instead of collapsing into one block.

* fix(ui): give the rotation outcome a visual hierarchy

Four near-equal paragraphs in a plain box read as one undifferentiated block:
the reassurance, the conditional branch, and the footnote all carried the same
weight, and the calm text was rendered in muted grey because it reused
.exec-approval-sub.

The dialog now has zones. A success mark in the shared icon-tile geometry sits
beside the title, the two reassurance sentences carry full --text weight, the
one conditional branch becomes a house .callout.info so it cannot be skimmed
past, and the security rationale is a 12px muted footnote. Spacing follows the
card's 4px rhythm.

The acknowledge button follows the confirm-dialog convention: the accent button
is the action that commits something, so a show-once reveal keeps it and a
report of work already done uses the neutral button.

* fix(ui): collapse the rotation outcome to a single reassurance

The body zigzagged: it announced a new token, then raised a disconnect, then
spent the next line walking that alarm back. The middle beat existed only to be
contradicted, so it is gone -- the disconnect is transient and self-healing, and
naming it bought the reader nothing.

The title now carries the announcement and the device name, the body is the one
reassurance, and the callout drops its "this device" echo. The neutral dismiss
button gets --border-strong: .btn's resting border is within ~4/255 of --card in
dark, so it read as unpainted on this surface. The callout wraps balanced.

Both dialogs now pass a single message, so the paragraph-array support added for
the previous structure is removed rather than left speculative.

* fix(ui): key the rotation callout to an observable symptom

"If it can only sign in with its device token" asked the operator to know which
credential the device holds, which is internal state they cannot see. The
condition is now the symptom that state produces: the device does not come back
on its own. Same behavior, same technical truth, decidable from the Devices page.

* fix(ui): reject contradictory device-token rotation results

The Devices page inferred the outcome from an unchecked RPC payload, so an
explicit `tokenDelivery: "in-band"` with no token, a withheld result that
carried one, or a delivery mode this client predates all rendered as a
successful withheld rotation - telling the operator the device re-credentials
itself while their previous token was already invalid.

rotateDeviceToken now parses the response into the closed outcome: the two
current pairs, the two legacy omission states from gateways released before
tokenDelivery, and every other explicit pair through the existing error path,
which surfaces as the page's danger callout carrying the recovery step.

* chore(plugin-sdk): refresh the API contract for the rotation result schema

Registering DeviceTokenRotateResultSchema in PluginLifecycleProtocolSchemas is
what makes the generators emit the Swift model, and that registry sits in the
type closure of 18 plugin-sdk exports, so their closureHash entries move. This
is generator output rather than a hand edit: the export set and every declaration
are unchanged, only the hashes.

* fix(ui): reject malformed device-token rotation envelopes

The parser trusted the envelope around the fields it read. A null, scalar, or
empty payload carries neither tokenDelivery nor token, so it matched the legacy
omission state and produced the reassuring completion dialog after the previous
credential had already been invalidated. A blank token did the same, though the
result schema bounds token to a non-empty string.

Only DeviceTokenRotateResultSchema's shapes are accepted now: the payload must be
a record that identifies the grant it rotated - every Gateway answering this
method returns deviceId and role, before and after tokenDelivery existed - and
token must be either absent or a non-empty string. This has to happen here
because the browser Gateway client resolves frame.payload directly, so the
registered result schema never runs on the client.

The mid-flight-reconnect reveal test asserted on a two-field stub no Gateway
sends; it now uses the real response shape.

* fix(ui): reject rotation results that do not answer the request

The rotation parser accepted any envelope naming some device and role. It
never checked scopes or rotatedAtMs, both required by
DeviceTokenRotateResultSchema, and never compared the returned grant with the
one that was requested -- so an incomplete or unrelated reply opened a success
dialog claiming a credential the operator may still hold had been replaced.

Require the full result shape and bind it to the requested grant, comparing on
the same trim normalization the device-auth store applies. The page fixture
now echoes the requested grant instead of hardcoding device-1, which is what
let a cross-device test accept a success dialog for the wrong device.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(ui): complete the rotation fixture the epoch test resolves

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(ui): update token rotation E2E envelope

* fix(protocol): correlate rotated token delivery

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
2026-08-13 20:41:04 -07:00
Peter Steinberger a9802ff418 fix(ui): Enter-send wedges dead when IME composition is abandoned via blur (#123249)
Browsers can drop compositionend (element detach or blur mid-IME).
composerComposing persists across renders in the pane state map, so a
stuck flag killed Enter-send, input history keys, and command menus
until the Send button — which force-resets the flag — was clicked.
handleBlur was a half-repair of this invariant: it cleared the
composingDraft shadow state but not the flag. Clear both.
2026-08-13 20:40:30 -07:00
Peter Steinberger 68c9d6dfeb fix(ui): screen readers announce the session rail clock every second (#123240)
The expanded session rail section was aria-live="polite" and contains a
1Hz elapsed-run clock, so screen readers announced the time every second
for the whole run — the exact anti-pattern exec-approval-card documents.
Messages were also double-announced via the nested thread live region.
Drop the section-level live region; the thread keeps its own.

Also give the initials-avatar divs role="img" so their aria-label
(the sender name) is honored by AT instead of reading raw initials.
2026-08-13 20:38:23 -07:00
Peter Steinberger 160188a3cd fix(ui): settings search no longer promises update fields the Updates page cannot edit (#123211)
Search matched the full update.* schema, but the Updates page renders
only curated channel/auto rows and the Advanced page excludes the scoped
update section — so a hit on update.checkOnStart landed on a page where
the field is editable nowhere but the Raw editor. Generalize the
memory-only visibleMemorySchema special case into a per-route
visible-keys map so search promises exactly what each curated
destination can edit.
2026-08-13 20:36:00 -07:00
Peter Steinberger 1e56f1b839 improve(ui): promote the live Chrome Web Store listing (#123377)
* feat(release): automate Chrome Web Store updates

* fix(release): keep Chrome store updates browser-only

* chore(release): leave changelog to release flow
2026-08-13 20:32:10 -07:00
Peter Steinberger 361e9251ce fix(ui): mixed-case deep links no longer torn between app pre-gate and router (#123207)
routeIdFromPath matched static route paths case-sensitively while
uirouter's pathKey lowercases them, so a /Usage deep link failed the
startApplicationRouter pre-gate and was rewritten to /chat even though
the router itself would have matched it. Lowercase the candidate path in
the pre-gate so both matchers agree.
2026-08-13 20:32:05 -07:00
Peter Steinberger b18f91dc38 fix(ui): icon and avatar fetches 401 first on every page load under token auth (#123199)
The Control UI put the hello device token first in its ordered HTTP
credential candidates, but the plugin/catalog/workspace icon byte routes
only accept the gateway shared secret. Every icon fetch on every page
load therefore 401'd once before retrying with the working token — and
each of those 401s is recorded by the gateway as a shared-secret
brute-force failure, escalating loopback penalty delays (250ms-5s
serialized before the response) and risking the 5-minute remote IP
lockout, where the icon loader treats the resulting 429 as terminal.

Order shared secrets first; pairing-only browsers still reach the device
token as the final candidate. Also switch the profile page hero avatar
from a single-candidate fetch (device token only, permanently 401 with
no fallback) to the same ordered candidates helper.
2026-08-13 20:29:15 -07:00
Peter Steinberger 563cea4e66 fix(ui): shared picker's 138px floor overflows narrow grid cells (#123175)
The phone-width ellipsis fix (#123085) promoted a chat-composer-scoped
138px minimum onto the shared select-picker inline style, which beats
every consumer stylesheet. Containers that legitimately shrink below
138px overflow instead: the cron 'Every N <unit>' and stagger-window
unit pickers spill out of their minmax(0,1fr) grid cells at 320px, and
workboard's min-width:0 became dead. Cap the floor at the host width
with min(138px,100%); the readable-label floor is preserved everywhere
the container is wide enough (the mobile e2e asserts it at 364px). Also
fix the stale '8ch' comment from the pre-138px iteration.
2026-08-13 20:07:14 -07:00
Peter Steinberger c66d5d84db fix(ui): clearing a cron delivery destination in the Control UI silently keeps the old chat (#123171)
Blanking the 'To' field in the Automations delivery editor omitted the
key from the cron.update patch. The Gateway merges delivery patches
by key presence, so the stored destination survived and every future
run kept announcing to the old chat with no error. Send an explicit
null clear on update, exactly like the accountId field one line above
(same bug class fixed there in #105762). Webhook mode is unaffected:
its blank-URL validation rejects the form before submit.
2026-08-13 20:03:06 -07:00
Peter Steinberger 31d54a61fb fix(ui): one failed full-message load pins the truncated preview for the whole session (#123324)
* fix(ui): retry failed full-assistant-message loads instead of pinning the truncated preview

Since ce466fccb0 removed the manual Show-more toggle, a failed
loadFullAssistantMessage left {status:error} that no renderer reads and the
render-time kick-off never retried (it only fired when no expansion state
existed). One transient RPC failure pinned the '...(truncated)...' preview
for the rest of the session with no visible outcome. Retry on later render
passes while attempts remain, bounded by revision so a dead loader cannot
hot-loop.

* fix(ui): render a retry affordance once automatic full-message loads exhaust

Addresses ClawSweeper P2 on the bounded-retry fix: after
FULL_MESSAGE_RETRY_REVISION_LIMIT the truncated preview previously stayed
silent. The exhausted error state now renders an inline error line with a
Retry button that re-enters the canonical loader via the same toggle path;
the retry bound is a named shared constant.
2026-08-13 19:51:15 -07:00
Jason (Json) 95240c0d57 fix(ui): pin notifications to viewport top (#123423) 2026-08-13 19:43:22 -07:00
Peter Steinberger eecf488c32 fix(ui): hidden retained chat panes silently clear unread for sessions you are not viewing (#123247)
Since retained panes (#121625) every pane — hidden or presented — keeps
its sessions subscription alive, and applySessionsState marked the
pane's selected session read unconditionally. New activity in a
recently-viewed session was cleared by its hidden pane instantly: the
sidebar unread dot never stuck, agentStatus attention was wiped by the
read patch, and "Mark as unread" self-reverted. Gate the read-marking
on this.presented, matching applyActiveSessionBindings.
2026-08-13 19:37:51 -07:00
Vyctor H. Brzezowski b0583e2643 fix(ui): contain long sharing member labels (#123017) 2026-08-13 23:19:26 -03:00
Peter Steinberger 6d716d9318 fix(ui): keep composer pickers visible near viewport edges (#123386)
* fix(ui): keep composer pickers visible near viewport edges

Model and effort pickers now share viewport-aware, preferred-above placement through the anchored overlay. This replaces the fixed CSS positioning policy so either picker can flip and shift within the viewport.

* test(ui): move picker regression to model flow suite

Keep the viewport-placement regression in the existing model and reasoning E2E owner so the composer redesign suite remains below the max-lines gate without losing coverage.

* test(ui): wait for adaptive pickers before interaction

Wait for the Web Awesome popup content to become visible before measuring menus or sending keyboard input. This aligns existing E2E interactions with the asynchronous details toggle lifecycle.
2026-08-13 18:53:34 -07:00
Peter Steinberger 90e039c30f fix(ui): do not call a portal unreachable when policy refused the probe (#123344)
* fix(ui): do not call a portal unreachable when policy refused the probe

The Portals page probes a portal with fetch before mounting its preview,
but a Content Security Policy refusal fails the same way a dead port
does, so a reachable portal was reported as unreachable. That fires
whenever the request Host differs from the hostname the browser used —
the Control UI reached through a portal, or a proxy that rewrites Host.

Frames obey frame-src, not connect-src, so a refused probe says nothing
about the portal: report it as blocked and mount the preview anyway.

Also stop an unrelated chat geometry assertion from flaking the merge
gate: boundingBox returns subpixel floats (16.000001907348633 on CI),
so compare with tolerance instead of exact equality.

Found by running a dev gateway on a cloud box and reaching its Control
UI through a portal; the browser console showed the connect-src refusal
for a portal that curl could reach.

* test(auth): isolate OAuth callback unit coverage

* test(ui): assert attachment failures through toast host
2026-08-13 18:00:20 -07:00
Peter Steinberger 79f41079d2 fix(ui): re-clicking the active nav item no longer stacks duplicate history entries (#123205)
Clicking the sidebar item for the page you are already on pushed an
identical history entry every time, so browser Back appeared dead until
every duplicate was popped. navigateAndWait now downgrades push to
replace when the computed target equals the current history location,
reusing the existing sameRouteLocation helper; context.replace collapses
into the same navigateWithMode owner instead of a parallel copy.
2026-08-13 17:57:22 -07:00
Peter Steinberger bbe97388d3 fix(ui): create automations for selected agent in all-agents view (#123381)
* fix(ui): preserve selected automation owner

Keep the concrete selected agent on model lookup and new automation creation while the Automations page is filtered to All agents, avoiding AgentSelectionRequiredError in multi-agent setups.

* fix(ci): remove duplicate Codex test shard assignment

Keep run-attempt-state.test.ts in the light Codex app-server shard only so the full-suite inventory remains one-to-one.
2026-08-13 17:50:15 -07:00
Peter Steinberger f0076b0ca6 feat(ui): thicken chat effort slider into capsule rail (#123380)
Restyle the effort popover slider from a 3px line to a full-height capsule rail with a pill thumb.
Retune the fill and dots for the tall track.
Verify the result in both dark and light themes.
2026-08-13 17:27:47 -07:00
Peter Steinberger 09940f718d fix(ui): stop app-card badges from squeezing titles into mid-word wraps (#123375)
On narrow cards the nowrap bundled-app badge left the title as the only
shrinkable item in the title row, so "Apple Watch" / "Wear OS" broke
across two lines. Let the row wrap: the badge drops to its own line
(left-aligned under the title) while wide cards keep it inline right.
2026-08-13 17:09:25 -07:00
Colin Johnson 9f263b2b19 fix(ui): keep remote input out of chat composer (#123293)
Co-authored-by: Solver <solver@localhost>
2026-08-13 19:58:48 -04:00
Peter Steinberger 1348387076 refactor(plugins): replace node-llama-cpp with managed llama-server (#123105)
Move llama.cpp chat and local embeddings onto a verified externally managed llama-server runtime. Remove the in-process native runtime, forked embedding workers, and node-llama-cpp dependency while preserving guided setup, local GGUF models, tool-capable agent runs, diagnostics, and operator docs.
2026-08-13 16:58:20 -07:00
joshavant 639e7718f3 fix(models): preserve refresh generation and fallback 2026-08-13 18:57:50 -05:00
joshavant bb65c12e0c test(ui): preserve deferred discovery proof 2026-08-13 18:57:50 -05:00
joshavant 21a3bfb4e4 fix(ui): keep model retry callback void 2026-08-13 18:57:50 -05:00
joshavant 6659007352 fix(models): preserve explicit discovery outcomes 2026-08-13 18:57:50 -05:00
joshavant a16b41488a test(ui): expect startup metadata to avoid model lookup 2026-08-13 18:57:50 -05:00
joshavant e34fd3016b fix(models): keep read-only refresh on prepared owner 2026-08-13 18:57:50 -05:00
joshavant 2cf9c99f8d fix(models): retry superseded catalog owners 2026-08-13 18:57:50 -05:00
joshavant d83f7b815d fix(models): defer live catalog discovery 2026-08-13 18:57:50 -05:00
Peter Steinberger 1287befae7 fix(release): preserve upgrade diagnostic probe (#123338)
* fix(release): preserve upgrade diagnostic probe

* test(vitest): reinstantiate contract projects for audits

* style(test): format upgrade diagnostic assertions

* test(ui): isolate route transition outlet fixture
2026-08-13 16:56:43 -07:00
Vyctor H. Brzezowski 9ee74069b0 feat(ui): add fullscreen mode to Desktop preview (#123278)
* feat(ui): add Desktop preview fullscreen

* test(ui): use real VNC for Desktop proof

* test(ui): satisfy Desktop proof checks

* test(ui): wait for VNC framebuffer pixels

* test(ui): format framebuffer readiness check
2026-08-13 20:50:13 -03:00
Peter Steinberger 6000ef2bbe fix(ui): localize JSON-collapse summaries, JSON badge, and Tool sender label (#123325)
jsonSummaryLabel returned hardcoded English ('Object (7 keys)', 'Array (2
items)', 'JSON'), the chat-json-badge span carried a raw 'JSON' literal, and
the tool sender label was a raw 'Tool' string — all rendered on localized
chat surfaces while sibling strings go through t(). #115730's localization
sweep missed these. Raw-copy baseline shrinks by the two badge entries.
2026-08-13 16:36:49 -07:00
Peter Steinberger 81a80a63e7 fix(ui): dropped attachments that fail to read vanish with no visible outcome (#123274)
* fix(ui): report attachment read failures instead of dropping files silently

Unreadable drops and pastes (folder drops, permission-denied files) resolved
null in readAttachmentFile and were filtered out with no visible outcome —
the attachment simply never appeared. Name the skipped files in a toast while
still attaching the successful siblings; aborted batches stay silent.

* test: assert the read-failure toast message is a plain string (oxlint no-base-to-string)
2026-08-13 16:33:30 -07:00
Vyctor H. Brzezowski f73b436f05 refactor(ui): make chat rails full-height resizable columns (#122475)
* refactor(ui): make chat rails resizable columns

* fix(ui): preserve board runtime across rail modes

* fix(ui): unify chat rail chrome

* fix(ui): unify rail header controls

* refactor(ui): normalize rail header chrome

* fix(ui): render background tasks rail

* test(ui): preserve workspace collapse semantics

* fix(ui): keep companion separator single

* test(ui): distinguish companion sheet and column borders

* test(ui): compare shared rail focus chrome

* test(ui): drive rail focus with keyboard

* fix(ui): align single-panel rail headers

* fix(ui): add custodian rail toggle label

* test(ui): narrow mock chat request params

* test(ui): follow shared rail controls

* test(ui): keep rich rail state visible

* fix(ui): center terminal rail actions

* test(ui): keep chat notices in conversation column

* test(ui): detect focus inside rail shadow roots

* test(ui): respect desktop overlay interactions

* test(ui): restore rails after route navigation

* test(ui): rebuild route-scoped rail matrix

* test(ui): preserve custodian rail during matrix

* chore(ui): keep chat pane within lint budget

* test(ui): tolerate subpixel icon geometry
2026-08-13 19:49:11 -03:00
Peter Steinberger cf885476a1 test: stabilize hosted UI and gateway races (#123357)
Await intercepted banner-module routes and isolate optional-module failure from stale-chunk reloads. Settle and atomically sample responsive geometry. Complete probe handshakes before teardown, and assert portal server ownership instead of reusable ephemeral ports.
2026-08-13 15:44:24 -07:00
Peter Steinberger 67a1cdda00 docs(ui): record stylesheet policy (tokens, breakpoint ladder, dead-CSS audit, no-layer decision) (#123185) 2026-08-13 15:00:03 -07:00
Peter Steinberger f8bca11d77 chore(ui): add dead-CSS audit script and delete unreferenced stylesheet rules (#123178)
* chore(ui): add dead-CSS audit script and delete unreferenced stylesheet rules

* fix(scripts): type-safe AST walking in the dead-CSS audit

* fix(scripts): detect ternary-headed class stems and restore live wizard rules
2026-08-13 14:53:17 -07:00
Peter Steinberger b7b33149b0 fix(ui): responsive layout test no longer flakes under load (#123300)
* test(ui): stabilize responsive layout geometry

* test(ui): fix lint errors in layout-settle helper

Rename the evaluate-scope selector binding and stop returning the rAF id
from the Promise executor (no-shadow, no-promise-executor-return).
2026-08-13 14:31:48 -07:00
Peter Steinberger 3cdbe3a511 fix(ollama): verify guided setup with runtime model (#123190) 2026-08-13 14:27:34 -07:00
Peter Steinberger dbea5a77f0 fix(ui): make Control UI terminal readable in light mode (#123184)
The light terminal theme reused the dark-background ANSI palette (1.4-2.6:1
contrast on #f7f8fa; brightWhite was 1.06), and the intro banner hardcoded
256-color indices 223/216 that bypass the client theme entirely.

Light mode now gets its own darkened ANSI-16 palette (>=4.5:1, bright
variants darker for emphasis, brightWhite maps to strongest ink), and the
banner emits themable ANSI-16 yellow/bright-red so each mode owns its
rendering. Dark mode is visually unchanged.
2026-08-13 14:14:35 -07:00
Josh Lehman f04fdd5594 fix(ui): align transcript search corner radius (#123299) 2026-08-13 14:12:30 -07:00
Josh Lehman 0b847dc92f fix(ui): keep question prompts from fading transcript (#123301) 2026-08-13 14:11:48 -07:00
Peter Steinberger be7f3225f7 fix: auth-cold gateways hide all models and dead-end the operator (#123130)
* fix(control-ui): recover auth-cold model pickers

Keep configured auth-cold models visible but disabled in chat and new-session, block submission, and link Model Setup. Fixes #123080.

* fix(control-ui): preserve rebased model picker behavior

* fix(ui): split composer model picker under the lint size cap

Repairs main's red check-lint caused by the #123090 restore.

* fix(ui): align auth-cold picker CI contracts
2026-08-13 14:09:35 -07:00