Commit Graph

5717 Commits

Author SHA1 Message Date
Peter Steinberger 860e8e301f fix(ui): reset /model default through server directive (#129895)
* fix(ui): reset model through server directive

* test(ui): target visible mobile transcript message
2026-08-26 00:08:42 -07:00
Peter Steinberger 6d1c1c9f57 refactor(cron): unify heartbeat automations under cron ownership (#129862)
* refactor(cron): unify heartbeat automations under cron ownership

* test(cron): align heartbeat automation fixtures and snapshots

* test(heartbeat): preserve rebased manual-wake coverage

* test(ui): scope mobile session actions to active chat pane
2026-08-26 00:08:18 -07:00
RoboClaw 1ab9852320 improve(ui): collapse mobile composer controls (#129853)
* improve(ui): collapse mobile model controls

* refactor(ui): align mobile composer controls

* test(ui): capture mobile composer states

* fix(ui): preserve mobile settings semantics

* fix(ui): restore mobile picker focus

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

* test(ui): await sidebar narration preload

---------

Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-08-26 00:07:50 -07:00
Peter Steinberger 3d81c85f2a fix(ui): model picker no longer collapses to one model after refresh (#129878)
* fix(models): stop models.list from silently collapsing to the harness default

Explicit models.list refresh reads now run to completion instead of racing
the 750ms browse deadline, and implicit reads that do hit the deadline fall
back to the published prepared catalog instead of returning an empty
snapshot that the Codex harness augmentation refilled with only the default
model. The Control UI model picker now revalidates silently in the
background: a warm catalog stays rendered and interactive, and the
'Refreshing models…' state is deleted end to end (chat + New Session).

* test(ui): prove the model picker stays interactive during background refresh

* test(ui): give the shared chat-pane test host a model catalog

The background-revalidation change reads chatModelCatalog.length in
refreshPageChat; createTestChatPane omitted the field, breaking the
chat-pane lifecycle suites on CI.
2026-08-26 00:07:02 -07:00
RoboClaw 636e21b198 fix(ui): prevent horizontal model picker scrolling (#129928)
Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-08-25 23:55:29 -07:00
RoboClaw 1f881019f4 fix(ui): align mobile toast actions (#129909)
* test(ui): cover responsive toast alignment

* fix(ui): align mobile toast actions

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

---------

Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-08-25 23:40:19 -07:00
Peter Steinberger 1473333f46 feat: bring Linux desktop onboarding to macOS parity (#129815)
* feat: bring Linux desktop onboarding to macOS parity

* fix: keep onboarding recovery within startup budget

* fix: satisfy onboarding startup and CI contracts

* fix: preserve Linux setup recovery and JSON5 configs

* refactor: keep onboarding recovery inside startup budget

* refactor: deduplicate first-run startup routing

* fix(linux): secure remote gateway pins and saved credentials
2026-08-25 23:34:34 -07:00
Hannes Rudolph 53e942ae8d fix(ui): keep hidden session files cold (#129620)
* fix(ui): defer hidden session file listing

* fix(ui): refresh deferred session files on reopen

* fix(ui): recover deferred workspace reloads

* test(ui): align sidebar hydration with lazy files

* fix(ui): keep hidden workspace actions deferred

Co-authored-by: Hannes Rudolph <49103247+hannesrudolph@users.noreply.github.com>
Co-authored-by: Josh Lehman <550978+jalehman@users.noreply.github.com>

---------

Co-authored-by: Josh Lehman <550978+jalehman@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-25 23:34:25 -07:00
Vyctor H. Brzezowski 7b3fb42617 improve(control-ui): redesign composer slash and skill invocations (#129707)
* improve(control-ui): align composer invocations with current main

* fix(ui): close composer invocation regressions

* test(ui): follow grouped slash hint rule

* fix(ui): preserve reasoning reset cleanup after rebase

* fix(ui): keep checkbox capability rows switch-free

The unified capability toggle row embedded a wa-switch even in checkbox
mode, reintroducing a hidden-but-real switch control inside the
new-session composer (breaking the rail-owns-switches contract asserted
by new-session-page.places e2e) and an aria-hidden focusable control.
Checkbox rows rely on the dropdown item's own checked state.

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-25 23:18:47 -07:00
Peter Steinberger 48e026161e fix(ui): preserve Settings navigation selection and workspace fragments (#129888)
* fix(ui): preserve Settings navigation selection and workspace fragments

* test(ui): scope mobile session menu proof to active chat pane
2026-08-25 23:06:27 -07:00
Peter Steinberger 4e864dff21 fix(ui): fill collapsed progress-card hover to the composer edge (#129869)
Collapsed, the composer progress card's hover highlight painted only the
<summary> band, leaving slivers of the resting background in the underlap
strip and the arcs beside the composer's rounded corners. Move the
collapsed hover fill to the card container (clipped to the full rounded
shape, extending under the composer) and keep the summary-scoped fill for
the expanded state.
2026-08-25 22:29:02 -07:00
Peter Steinberger ced723c2a8 fix(ui): preserve cloud worker provider-owned settings (#129846) 2026-08-25 22:25:55 -07:00
Peter Steinberger 3d37bb4cba fix(ui): explain empty chat exports (#129881) 2026-08-25 22:22:08 -07:00
Peter Steinberger 43fb810d89 fix(ui): surface agent config errors and hydrated identity avatars (#129868) 2026-08-25 21:48:28 -07:00
RoboClaw 44907105c6 fix(ui): keep mobile updates in Inbox (#129864)
Remove the duplicate update row from the compact session menu while preserving the stale-client refresh action. Keep mobile update confirmation covered through the canonical Inbox flow.

Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-08-25 21:46:37 -07:00
RoboClaw 46fab66580 feat(ui): align session menus (#128995)
Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-08-25 21:45:54 -07:00
Peter Steinberger 781be142fc fix(gateway): isolate session event ownership (#129313) 2026-08-25 21:45:39 -07:00
RoboClaw 37945f1fdf fix(ui): show Draft mode once in new-session composer (#129858)
Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-08-25 21:43:49 -07:00
Vyctor H. Brzezowski 552f160389 fix(ui): preserve new composer state across reconnects (#128620)
Prevent delayed rewind, branch-switch, and fork completions from overwriting replacement chat state after reconnects while preserving canonical history reconciliation.

Refs #128617.
Reviewed-by: @shakkernerd
Co-authored-by: Shakker <165377636+shakkernerd@users.noreply.github.com>
2026-08-26 05:28:22 +01:00
ClawSweeper ce08e259e9 fix(codex): prevent steered answers from disappearing or crossing threads (#128257)
* fix(codex): isolate steered thread transcripts

* fix(ui): scope terminal queue cleanup

* refactor(codex): split steering snapshot assembly

* fix(codex): suppress silent steering boundaries

* chore(ui): document chat event ownership

* test(ui): await Lobsterdex bootstrap requests

* test(update): isolate managed service selectors

* test(ui): submit queue edits by keyboard

* refactor(ui): tighten chat event ownership

* refactor(ui): narrow terminal chat payloads

Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>

---------

Co-authored-by: RoboClaw <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-08-25 21:27:01 -07:00
Peter Steinberger 215de48bd5 perf(ui): streamline session switching and transcript rendering (#129834) 2026-08-25 21:23:29 -07:00
Jesse Merhi e9dfe8a71d refactor(ui): simplify sidebar hover stability (#128973)
* refactor(ui): simplify sidebar hover lifecycle

* refactor(ui): reduce sidebar hover machinery

* fix(ui): preserve sidebar row lifecycle invariants

* fix(ui): refresh adopted row marquee badges

* fix(ui): preserve collapsed project sections

* fix(ui): isolate catalog collapse identities

* fix(ui): remeasure resized session marquees

* fix(ui): migrate legacy catalog collapse groups

* fix(ui): keep trailing glyphs inside sidebar endcap
2026-08-26 14:22:03 +10:00
xingzhou ceb074ef46 fix(gateway): reject rootless session permission modes (#128149)
Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
2026-08-25 22:18:09 -06:00
Peter Steinberger b545224b33 fix(ui): pool reply-less wake activity into one chat rollup (#128442)
Consecutive runs whose entire visible outcome is tool activity (heartbeat
wakes, cron ticks) now collapse into a single expandable activity row
instead of stacking identical rows down the transcript. Runs with a
visible reply keep per-run activity separation. Group summaries reuse the
standalone row's tool display label, so the rollup reads
"Used Heartbeat Respond ×N". Splits tool call/result pairing out of
chat-thread-grouping.ts into chat-tool-activity-coalesce.ts (max-lines).
2026-08-25 21:15:01 -07:00
Peter Steinberger 1e84f429c7 fix(ui): own chat history issuance with a closed state machine (#129776)
* fix(ui): own chat history issuance with a closed state machine

loadChatHistory silently returned undefined when the pane was not yet
connected, so any caller racing the connection lifecycle left the pane
in skeletons forever: no request, no error, no recorded fact. Failures
that did happen fed a dismiss-only alert with no recovery (#128655).

Record issuance as one ChatHistoryLoadState union
(idle | pending-connection | in-flight | committed | failed) at the
owner, absorbing the old inFlightHistory nullable. Disconnected requests
record pending-connection and re-arm exactly once at the pane context's
connected transition; failures render a retryable transcript error and
retryable ones auto-resume on reconnect; invalidated in-flight loads
transfer intent back to pending instead of vanishing.

Closes #128655. Supersedes #128691.

* fix(ui): scope disconnect loading normalization to the transition

Repeated identical disconnected snapshots must stay no-ops for pane
ownership (chat-pane-lifecycle contract); normalize chatLoading only on
the connected->disconnected edge.

* fix(ui): keep failed history refreshes actionable over cached transcripts

A refresh failure with cached messages rendered nothing: the failure
panel only replaced an empty thread, so stale content sat with no error
or Retry. Render the same failure surface as a compact non-displacing
banner above the transcript when content exists, sharing one builder
with the empty-state panel. Adds the cached-refresh browser regression
with screenshot proof.
2026-08-25 21:13:23 -07:00
RoboClaw 5005661473 fix(control-ui): keep task progress full width on hover (#129832)
Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-08-25 21:01:16 -07:00
RoboClaw fde687b060 fix(ui): clarify sidebar update affordance (#129813)
Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-08-25 20:59:23 -07:00
RoboClaw 971feb5736 fix(ui): count only actionable Inbox entries (#129786)
Keep informational updates visible without contributing to the global or per-tab attention badges.

Closes #129779

Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-08-25 20:39:16 -07:00
Peter Steinberger e82700626d fix(ui): serialize remote session permissions before sending (#129674)
* fix(ui): serialize remote session permissions before sending

* test(ui): await mobile side-panel close before progress assertion
2026-08-25 20:35:09 -07:00
Peter Steinberger c8382f9db9 fix(ui): surface rejected WhatsApp actions without stalled refreshes (#129800) 2026-08-25 20:22:05 -07:00
Peter Steinberger 422d6818e4 refactor(workers): unify cloud bootstrap lifecycle (#129719)
* refactor(workers): unify cloud bootstrap lifecycle

* fix(workers): preserve cloud setup and repair qa fixtures
2026-08-25 20:11:33 -07:00
Peter Steinberger 03262dfb04 refactor(ui): tidy the roster refresh owner and session test harnesses (#129757)
* refactor(ui): name the roster load decision blocks and dedupe test harnesses

Extract preserveCurrentSessionRow and retainSessionPaginationWindow from
load()'s 140-line body so its spine reads as normalize -> loading publish ->
request+owner merge -> provisional return -> decide -> publish; collapse the
hasForegroundListOptions/hasSeededListOptions boolean pair into one closed
listOptionsSource mode. Behavior-neutral.

The event-refresh and owner-first suites hand-rolled copies of
sessionsResult/createHarness/sessionChangedEvent; session-capability
test-support already owned the canonical versions, so it gains selfUser
support plus createSessionCapabilityHarness and the copies are deleted.

* test(ui): move the attention-panel surface test to its component suite

The app-sidebar harness mocks sidebar-attention.ts away (health RPCs must
stay out of client call-order assertions), so the transient-menus case
asserting the real attention panel renders was import-order dependent:
deterministic-fail in local single-file runs on clean main, pass in CI's
worker mix. The invariant (panel opens inside openclaw-menu-surface) now
lives in sidebar-attention.test.ts against the real component, and the
harness mock documents where attention rendering coverage belongs.

* test(ui): give the moved attention-panel test its agent selection

sidebar-attention reads context.agentSelection.state during refresh
ownership; CI's striped worker mix exercised that path and crashed the
render, leaving the trigger unrendered.
2026-08-25 20:05:44 -07:00
Peter Steinberger 5684507a41 fix(ui): preserve configured models when catalog requests fail (#129767) 2026-08-25 19:50:44 -07:00
Peter Steinberger 72adefbca0 fix(ui): keep modal keyboard ownership across shadow roots (#129747) 2026-08-25 19:42:45 -07:00
Peter Steinberger 0f5cb35a04 perf(ui): replace warm refresh sleeps with owner signals (#129748) 2026-08-25 19:39:01 -07:00
Josh Avant 1c37c8cdc7 fix(exec): scope reusable approvals to their working directory (#129636)
* fix(exec): bind durable approvals to working directory

* chore(apps): refresh native string inventory

* test(node-host): preserve prepared working directory

* fix(exec): use shared path safety facade

* fix(exec): revalidate approved directory identity
2026-08-25 18:24:14 -07:00
RoboClaw 238c884986 fix(ui): expose mobile Inbox dismiss actions (#129379)
Keep per-item dismissal visible on touch layouts, add selected-tab Dismiss shown for dismissible entries, and preserve non-dismissible approvals.

Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-08-25 18:09:17 -07:00
RoboClaw d6510fa961 fix(ui): show delegated work on selected threads (#129328)
Fixes #129318

Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
2026-08-25 18:07:10 -07:00
Peter Steinberger 6133ad92a0 fix(ui): show resolved agent workspace and effective model consistently (#129701) 2026-08-25 17:36:28 -07:00
Josh Lehman 2dbaeef693 fix(ui): keep active commentary after session navigation (#129640)
* fix(ui): restore active commentary after navigation

* test(ui): wait for responsive activity layout
2026-08-26 00:27:41 +00:00
Peter Steinberger 18ad0dda91 test: remove duplicate model-readiness assertion (#129683)
Amp-Thread-ID: https://ampcode.com/threads/T-01a037b5-3918-749f-90bd-5c9ac1dced16

Co-authored-by: Amp <amp@ampcode.com>
2026-08-25 17:18:20 -07:00
Peter Steinberger c2602193d6 fix(ui): honor forced filtered session roster refreshes (#129484) 2026-08-25 17:13:48 -07:00
Peter Steinberger fbd067732a fix(ui): show channel configuration save errors inside the editor (#129654)
* fix(ui): show failed channel saves inside the settings editor

* test(gateway): isolate captured agent command calls between cases

* test(gateway): mock canonical terminal transcript lifecycle decision

Co-authored-by: Dallin Romney <dallinromney@gmail.com>

---------

Co-authored-by: Dallin Romney <dallinromney@gmail.com>
2026-08-25 17:04:17 -07:00
Peter Steinberger 088c45ab7c fix(ui): clear provider credential drafts when switching agents (#129676) 2026-08-25 17:00:32 -07:00
Vyctor H. Brzezowski 64853b1a9d improve(control-ui): restructure the composer as a multiline surface (#124301)
* improve(control-ui): align composer stack with current main

* refactor(ui): isolate new-session permission state

* fix(ui): close composer CI regressions

* test(ui): align composer presentation contracts

* test(ui): fix composer browser contracts

* test(ui): stabilize composer browser ownership

* test(ui): target presented mobile composer

* test(ui): select accessible mobile composer

* test(ui): open mobile rail session directly

* fix(ui): keep mobile composer content laid out

* fix(ui): address composer review findings

* fix(ui): close composer interaction gaps

* fix(ui): bound swarm task popover

* fix(ui): restore progress-card docking beside the wide composer

The multiline restructure dropped the .session-progress-card--dock
placement shipped in #129141 and left the completed marker inside the
collapsed-only summary span, so it vanished when the disclosure opened.
Restore the dock positioning block and hoist the marker into the
always-visible summary grid.

* fix(ui): bound swarm task popover by its composer anchor

The popover capped its height against the raw viewport, so on short
mobile viewports its top rendered above the screen. Register the swarm
group with the existing composer popover anchor observer and consume the
measured --chat-composer-popover-max-height, keeping the viewport calc
only as fallback. The new actual-tree mobile regression failed with
-71px top clipping before the fix.

* fix(ui): restore Draft capability controls and split the new-session composer

Restores the Draft option and selected-state indicators the capability
menu lost in the restructure, and moves the capability/visibility
controls into composer-capability-controls.ts so composer.ts drops back
under the 700-line lint gate (677 lines) without a suppression.

* test(ui): prove permission-mode final effect before session creation

Adds mocked-gateway proof that an admin's Full selection reaches
sessions.create as permissionMode:"full" exactly once, and that a
write-scoped operator's retained Full selection is rejected before
creation with a visible status notice and zero create requests. Also
aligns the mobile footer reading-order assertion with the shipped
attachments-draft-model order.

* test(ui): register swarm popover layout test as node-driven

chat-swarm-progress.browser.test.ts drives its own Playwright chromium
from node, but d3396c4d3b added it without registering it in
nodeDrivenBrowserLayoutTests, so the browser-mode chromium project tried
to fetch it into the page and failed on the node-only playwright import.
This was the deterministic checks-ui failure on both prior heads.

* test(ci): register codex attempt-startup-retry in its support lane

#129505 added extensions/codex/src/app-server/attempt-startup-retry.test.ts
without lane ownership, so the full-suite coverage guard
(test/vitest-projects-config.test.ts) fails on every full run. Register it
beside attempt-startup.test.ts in the attempt-support lane.

* test(ui): expect last-activity suffix in composer summary label

Merge weave: #129520 appends the accessible last-activity to the summary
aria-label; align the restructured composer card's assertion with it.

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-25 16:59:46 -07:00
RoboClaw 3cc64f90ed fix(ui): keep pinned sidebar pages through reconnect (#129517)
Co-authored-by: roboclaw-bot <309084314+roboclaw-bot@users.noreply.github.com>
2026-08-25 16:44:05 -07:00
Peter Steinberger e2c8dae785 fix(ui): stop warm sidebar refreshes from blanking foreign-owned rows (#129558)
* fix(ui): stop warm sidebar refreshes from blanking foreign-owned rows

The owner-first roster plan (#128767) published its provisional owner-only
window as a full membership replacement on every primary refresh, so on
multi-owner gateways every other user's sessions blinked out of the sidebar
until the shared merge landed - up to once a second under event load. The
provisional phase now publishes only when no roster is on screen (cold start
keeps its fast first paint), and the shared phase merges the owner window
from the initial load's returned rows instead of reading published state;
the mergeExisting option is gone. A warm refresh whose shared phase fails
now keeps the previous roster instead of collapsing to owner-only rows.

The thread and child session lists also rendered rows positionally with
map()+keyed(), so any reorder tore down and rebuilt every shifted row's DOM
(spinners restarting, avatars remounting). Both now use repeat() with key
identity, matching the catalog renderer.

Regression coverage: owner-first-roster.test.ts pins the publish-sequence
invariant and shared-failure retention (both fail pre-fix); the new browser
test proves row DOM identity survives a reorder (fails on map()). The
owner-first tests moved out of index.event-refresh.test.ts, which hit the
max-lines limit.

* test(ui): prove foreign-owned rows survive a warm owner-first refresh

Browser-level regression for the warm-refresh half of this fix: holds the
shared phase deferred after a sessions.changed event and asserts the
foreign-owned row never leaves the DOM. Fails on pre-fix code (the row
count drops to zero the moment the provisional owner window publishes).

* test(anthropic): pin the local retired-profile id to the plugin-sdk constant

The provider-policy artifact keeps this id as a local literal so it never
imports the provider-auth barrel (#129052 regressed dist-less CI checkouts
into 120s jiti compiles of ~2.2k modules); this parity test stops the two
constants from drifting apart.
2026-08-25 16:38:55 -07:00
Peter Steinberger 0246aaf06d improve(ui): speed up session loading and chat rendering (#129656)
* perf(ui): reduce session loading and render overhead

* test(ui): align shell fixtures with appearance settings owner
2026-08-25 16:32:12 -07:00
razshlomo a74080d5d5 fix(control-ui): chat Markdown preview renders RTL documents left-to-right (#129129)
* fix(control-ui): render the chat Markdown preview in its own text direction

The chat transcript already picks a direction per message
(`chat-message-markdown.ts` sets `dir` from `detectTextDirection`), but the
Markdown preview in the chat detail panel never did. A Hebrew or Arabic
document opened there rendered left-to-right: bullets and quote bars on the
wrong side, table columns in the wrong order, and sentence-final punctuation
detached from its sentence.

Detect the direction from the document source and set `dir` on the reader, then
let the shared `.sidebar-markdown` rules mirror with it by using logical
properties instead of left/right ones. Code blocks get `unicode-bidi: plaintext`
so each hard-wrapped line keeps its own direction — that also covers the raw-text
view, which renders the whole document as one fenced block. Block art carries no
strong characters, so it stays left-to-right as before.

Under LTR the logical properties resolve to exactly the previous physical ones,
so nothing changes for existing documents.

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

* fix(control-ui): make the task-list checkbox gap direction-aware

The sidebar sheet moved its indents, quote bar and cell alignment to logical
properties, but `.task-list-item-checkbox` kept `margin-right`. In an RTL
document the label sits to the left of the box, so the physical margin pushed
the gap to the outer edge of the row instead of between the box and its label
— the one remaining place in this sheet where direction still leaked through.

`margin-inline-end` is what the message renderer already uses for the same
control (`chat/text.css` positions it with `inset-inline-start`), so this also
lines the two sheets up.

The RTL and LTR browser tests each grow a task item and assert the computed
margin lands on the inline-end side, mirroring the existing quote-bar and
list-indent assertions.

Formatting proof (node_modules absent in this checkout, so the pre-commit
oxfmt hook could not run): oxfmt 0.60.0 --check and oxlint 1.78.0 both pass
on the changed test file.

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

* test(control-ui): assert the checkbox gap by side, not against zero

The assertions added in the previous commit demanded the opposite margin be
0, and `checks-ui` failed on exactly that: 3px in RTL, 4px in LTR. Those are
the UA sheet's own `margin: 3px 3px 3px 4px` on an `input[type=checkbox]`.
The rule overrides only the inline-end side, so the opposite side keeps the
default and never reaches zero.

Assert instead that the sheet's 0.4em resolved onto the expected physical
side — margin-left under RTL, margin-right under LTR — which is the mapping
the fix is actually about and owes nothing to UA defaults.

Verified against a real Chromium before pushing: with `margin-inline-end`
both directions land 5.33px (0.4em of the 13.333px UA control font) on the
inline-end side; with the pre-fix `margin-right` the RTL assertion fails and
the LTR one passes, which is the regression shape this test exists to catch.

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

* ui(chat): scope the code-block bidi rule to the markdown reader

`unicode-bidi: plaintext` sat in the shared `.sidebar-markdown pre` rule, which
also matches the skills, session-progress-card and agent status-file previews.
Those surfaces never opt into a document direction, so this branch was changing
their code-block rendering while declaring them unchanged.

Move the declaration to `.sidebar-markdown-reader pre` — the chat preview is the
only consumer that sets `dir`. The shared layout properties stay shared, and the
existing direction test already queried the reader-scoped selector, so it is
unaffected. Adds a regression test pinning that a bare `.sidebar-markdown`
surface keeps its previous code-block rendering.

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

---------

Co-authored-by: yehezkel-assistant[bot] <3902212+yehezkel-assistant[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 16:31:32 -07:00
Peter Steinberger 5b0c094258 feat(ui): stack the docked progress card under the suggestion tray (#129443)
The suggestion tray and the docked progress card both floated over the
conversation's top-right area as independent absolutely-positioned
elements. The tray is 450px at z-index 24 and the card sat at z-index 3,
so whenever a suggestion arrived while a plan was live the tray covered
the card outright: session state disappearing with no trace, which is
the failure mode this repo weighs heaviest.

Give the gutter a single owner. .chat-gutter-stack is one absolutely
positioned column at top-right; the tray and the card become its
children and cannot overlap by construction. The tray keeps its exact
geometry (same inset, same width, position: relative only so its stacked
-card shadow still anchors to itself); the card right-aligns beneath it.

The card is now top-anchored rather than pinned to the composer's bottom
edge, so appended steps and a longer note extend downward instead of
shifting the rows already being read, and the empty gutter falls below
the card instead of above it. Its width becomes a fixed token rather
than gutter-derived arithmetic: the pane already gates this placement on
a measured gutter wide enough to hold it, so the clearance is guaranteed
and a stable card beats one that grows with the window.

Covered by a new placement case asserting the card sits fully below the
tray when both render.
2026-08-25 16:01:21 -07:00