Commit Graph

5450 Commits

Author SHA1 Message Date
Peter Steinberger c8418459f8 fix(ui): reconcile every null tombstone and align session sort with the gateway (#124104)
* fix(ui): reconcile every null tombstone and align session sort with the gateway

The sessions.changed merge deleted null-tombstoned fields from a hand-kept
eleven-field list while the gateway's tombstone policy
(session-event-payload.ts) had grown past it: toolOverrides, observerDigest,
controlOwnerSessionKey, restartRecoveryStatus, and goal leaked literal null
into rows typed optional-not-null, and every new tombstoned field would
silently repeat the drift. One loop over the event's null-valued keys now
owns the rule; updatedAt/activeLeafEntryId (the schema's only nullable row
fields) keep their explicit handling. Net −24 lines.

Riders in the same owner neighborhood:
- compareSessionRowsByUpdatedAt gains the gateway's ascending-key tie-break
  (session-list-order.ts: "Stable key ties keep offset paging deterministic")
  so tied rows — including all updatedAt:null rows — stop visibly swapping
  when the canonical refresh replaces an event-driven reconcile.
- Deleted the unreachable kind === "cron" wire guards (gateway folds cron to
  direct before projection, #115299; both sites needed an `as string` cast
  because the protocol type has no such member).
- The stale-active-snapshot path returns the original result identity when no
  defaults were passed, so the caller's result === state.result publish gate
  skips a spurious sidebar re-render on every stale event.

* test(ui): pin the ascending-key tie for equal-updatedAt sidebar rows

* test(ui): tolerate CI renderer jitter in the typing-shift picker guard

The 2px y-tolerance flaked at 2.41px on CI renderers; the #122809
regression this guards against shifts by a full indicator row (~20px).
2026-08-15 01:59:28 -07:00
Peter Steinberger 9a175aab62 fix(ui): agent roster stays current during delayed startup (#124103)
* fix(ui): fence agent roster startup adoption

* fix(ui): reject stale chat roster before mutation

* refactor(ui): simplify agent roster ownership

* fix(ui): fence startup behind in-flight roster refresh
2026-08-15 01:44:13 -07:00
Peter Steinberger f4beff12cd fix(ui): stop per-message branch-list polling and unlatch transient branch failures (#124094)
* fix(ui): stop per-message branch-list polling and unlatch transient branch failures

Four coupled defects around the chat branch dropdown, fixed at their owners:

- Every persisted session.message and every sessions.changed triggered an
  unconditional sessions.branches.list RPC per viewing client; the gateway
  branch cache watermark is max(seq), so each message append made the next
  list a full transcript rescan. Branch topology only changes on structural
  mutations, and the producer already records the reason — the reload is now
  gated on rewind/branch-switch/fork/reset/new.
- A transient listBranches failure recorded success (chatBranchesSessionKey
  set with branches=[]), permanently hiding the branch dropdown for that
  session+connection with no visible outcome. The catch no longer latches;
  the next history load retries naturally.
- sessions.branches.list errored for upstream-linked sessions even though
  "no local branches" is their truthful steady state; the fresh-session
  sibling was already converted to {branches: []} for the same reason
  (spurious gateway-log failures). Only mutating siblings fail closed now.
- Branch state was keyed by raw session-key spelling and strict-compared in
  the header and history gate, blanking the dropdown across the legacy
  main -> agent:main:main alias window (same class as #124020); both
  compares use areUiSessionKeysEquivalent.

Riders per pathfinder rule: deleted the write-only chatBranchesLoading flag,
a duplicate activeLeafEntryId recompute, and two provably-no-op second
dropPreSessionStartAnnouncePairs passes in chat-history-pages.ts (the filter
is single-pass complete and its inputs were already filtered).

Production LOC +27/-34 (net -7); regression tests fail pre-fix.

* refactor(ui): move branch-display ownership out of the header render

check-lint-core-4 flagged chat-pane-header.ts at 702 counted lines after the
equivalence gate landed there. The branches-for-display decision belongs to
the state owner anyway: displayedChatSessionBranches in chat-history.ts now
owns it and the header render consumes the fact.
2026-08-15 01:33:17 -07:00
Peter Steinberger aaa509b26e refactor(types): remove chained type assertions in core and ui (#124073)
* refactor(types): remove chained assertions in core and ui

* fix(types): preserve legacy cron migration identity

* fix(types): preserve settings patch key types
2026-08-15 01:32:04 -07:00
Peter Steinberger f2c721b0ac fix(test): unshadow capture-helper catch bindings
check-lint-core-4 (run 31872054461) flagged no-shadow: the failure
diagnostics refactor in c59ef0b40c named its parameter 'error' while
two inner catches reused the name.
2026-08-15 00:35:54 -07:00
Peter Steinberger d2be00e171 feat(ui): persistent emoji session icons in the sidebar (#124034)
* feat(gateway): persist emoji session icons

* feat(ui): add persistent session icon menu

* docs: explain persistent session icons

* docs: list Set icon in the session context menu enumeration

* test(gateway): canonicalize group defaults workspace path

* fix(ci): sync session icon protocol coverage

* fix(sessions): enforce RGI emoji sequences for session icons

ClawSweeper P2: single non-emoji graphemes like 漢 passed the segmenter-based predicate. Anchored \p{RGI_Emoji} admits exactly one interchange emoji sequence and deletes the length cap and ASCII carve-out.

* fix(sessions): compile RGI emoji validation
2026-08-15 00:15:55 -07:00
Peter Steinberger c59ef0b40c fix(test): serve deep-link assets like the Gateway in the Control UI e2e preview server
Root cause of the checks-ui-e2e boot-stall flake (run 31867505277): the
built index.html references modules relatively (./assets/*), and the real
Gateway resolves /assets/ at any path depth (src/gateway/control-ui.ts),
but the e2e preview server let Vite's SPA fallback answer
/chat/assets/index-*.js with index.html as text/html. The browser rejects
that module silently (console-only, no pageerror), so a document reloaded
on a deep link like /chat/research never boots. Under CPU load the
harness's replaceState("/")+reload pair raced the router's redirect and
reloaded on the deep link, producing the all-null 30s poll.

- Preview server now mirrors the Gateway's depth-insensitive /assets/
  resolution; the racy replaceState canonicalization hack is deleted.
- Deterministic regression test boots a document directly on
  /chat/research; mutation-proven (fails with the middleware disabled).
- Failure diagnostics generalized: boot waits capture screenshot, script/
  resource evidence, pageErrors, and a navigation-traced event ring.

Proof: Testbox tbx_01m022feapn38ypws3mb2b9jhv saturated loops — pre-fix
2/10 failed with the exact CI signature; post-fix 0/14 at the same pace.
2026-08-15 00:12:14 -07:00
Peter Steinberger 05028da50d fix(ui): refresh model config after reconnect (#124065) 2026-08-14 23:54:58 -07:00
Patrick Erichsen ab4557d22c fix(ui): remove question expiry timer (#124063) 2026-08-15 06:42:05 +00:00
Patrick Erichsen f6359140f1 fix(ui): show skill titles in chat reference picker (#124017)
* fix(ui): use skill names for chat references

* fix(ui): show skill titles in reference picker
2026-08-14 23:39:02 -07:00
Patrick Erichsen 1e91253f1e fix(ui): slow active-run spinner (#124058) 2026-08-14 23:29:38 -07:00
Peter Steinberger 45ad442497 fix(ui): surface failed Canvas dashboard pins (#124044) 2026-08-14 23:28:29 -07:00
Peter Steinberger 8b200f3523 fix(ui): keep the chat transcript anchored while the pane resizes (#124013)
* fix(ui): keep the chat transcript anchored while the pane resizes

The width-change path wiped the virtualizer's entire item-size cache
(instance.measure()), collapsing every offscreen row to the 120px estimate
with no scroll compensation: the reader teleported mid-transcript and the
trickle of remeasure corrections made text visibly jump for several frames
on every pane or window resize.

Keep the stale offscreen sizes as estimates instead. The existing
synchronous resizeItem pass plus TanStack's per-row ResizeObserver re-seed
connected rows with fold-based scroll compensation, so the anchor row holds
still; offscreen rows correct lazily as they connect. End-pinned transcripts
keep following the tail via the existing height-change scrollToEnd path.

Regression e2e: mid-transcript anchor stays within 60px across
1280->1000->820->1280 resizes (pre-fix: anchor scrolled out of the viewport
entirely and scrollTop drifted by 250px+). Wipe introduced in #115059,
refined by #117687/#123713 without addressing anchoring.

* test(infra): deflake ephemeral-port rebind proof under runner contention

The bind->release->rebind cycle races foreign processes on shared CI
runners: a stolen port between release and rebind fails the assertion
(EADDRINUSE, seen on checks-node-compact-large-17). Retry the whole cycle
on a fresh ephemeral port, bounded at 5 attempts; a genuine release
regression still fails every attempt because the collision is with our
own lingering listener.

* test: prove port release on the allocated port; add end-pin resize proof

Address ClawSweeper review on #124013:
- ports-probe: replace the fresh-port retry with a connect probe against the
  same allocated port. A lingering listener accepts the probe; a released
  port refuses it. Unlike a rebind, the probe does not collide with foreign
  outbound sockets that transiently occupy the port on busy runners, so the
  release contract stays tied to one port with no retry at all.
- chat-resize-anchor e2e: add a width-only end-pinned case proving the
  virtualizer's wasAtEnd compensation keeps the transcript within 2px of
  the end across 1280->1000->820->1280 resizes.

* test(ui): widen resize-anchor drift bound for Linux font metrics

The fold-spanning anchor row re-wraps by a renderer-dependent amount
(42px macOS, 63px Linux CI at 820px). 120px keeps a wide margin below
the pre-fix failure mode (anchor out of viewport, 250px+ drift).

* test(tooling): register manager-session-update-race in memory helper routing

#124024 added the memory-core test on main without updating the
cross-lane routing expectation in test-projects.test.ts, breaking the
core-tooling shard on every merge ref that includes it.
2026-08-14 23:00:13 -07:00
Peter Steinberger f24bd8a2a2 fix(ui): use session-key equivalence for active-row lookups in model/thinking/fast-mode paths (#124020)
Six sites strict-compared row.key === sessionKey while the sibling locked
check already used areUiSessionKeysEquivalent (#104045 upgraded only the
model path). In alias/transition windows (pre-hello legacy 'main' vs
canonical agent:main:main) the optimistic thinking/fast-mode row patch
found no row — the click produced no immediate visible change — and the
display resolvers missed the active row, falling back to agent defaults.
All lookups now go through the equivalence helper the rest of the flow
uses.
2026-08-14 22:39:54 -07:00
Peter Steinberger f8fcc7f99b test(ui): remove sidebar capture harness (#124031) 2026-08-14 22:29:48 -07:00
Jesse Merhi edb5adfbf5 Start new sessions with folder group defaults (#123276)
* feat(ui): add folder group session defaults

* fix(ci): align folder group contracts

* fix(protocol): refresh Android gateway methods

* fix(ui): reuse folder picker for group defaults

* fix(ui): harden session group defaults

* test(ui): align group defaults with current main

* test(ui): keep group catalog defaults path-free

* fix(ui): close folder group CI gaps

* fix(ui): satisfy folder group CI contracts

* fix(session-groups): enforce defaults safety

* test(gateway): keep group defaults in workspace

* fix(session-groups): enforce defaults participation

* fix(session-groups): close review authorization races

* fix(session-groups): canonicalize defaults authorization

---------

Co-authored-by: Jesse Merhi <jesse-merhi@users.noreply.github.com>
Co-authored-by: Jesse Merhi <openclaw@users.noreply.github.com>
2026-08-15 15:24:13 +10:00
Peter Steinberger af76f494cc test(ui): give the run-inspector bottom-bound assertion its real sub-pixel tolerance
Broke the main gate on run 31865993941 (checks-ui-e2e 2/12):
expected 609.81 <= 609.21. The preceding scroll poll tolerates a <=1px
remainder because scrollHeight/clientHeight are integer-rounded, while
measureWithinAncestor compares fractional getBoundingClientRect values,
so the true bound is 2px, not 1.
2026-08-14 22:11:17 -07:00
Peter Steinberger 6a78332c4c fix(ui): dedupe chat header owner presence (#124006) 2026-08-14 22:03:27 -07:00
Peter Steinberger c7090706d4 fix(ui,gateway): refresh the agents-page model catalog on picker open and give catalog errors a next step (#124019)
The agents-page model catalog was cached per agent for the whole
connection: availability facts (provider keys added/removed, new models)
went stale with no refresh path except a full reconnect, while the chat
composer's sibling picker refreshes on open. Opening the overview picker
now re-reads through the same ensureModelCatalog owner with a refresh
bypass of the per-agent cache.

sessions.patch's 'model catalog unavailable' error told the operator
nothing actionable; it now states the catalog is still loading and to
retry — the failure text names the next step per doctrine.
2026-08-14 22:00:55 -07:00
Peter Steinberger 7ac49e280e refactor(ui): delete the permanently no-op onModelChanged seam (#124009)
state.onModelChanged has been () => undefined since the original Control
UI refactor (65e12328aa) and was never overridden; three call sites
awaited a decoy. Effective-tools previews key their requests on the model
override, so the post-switch refresh only needs a re-render, and the
model-switch reconcile already awaits the canonical session-list refresh.
Tests that used the seam as a deferral hook now hold the list refresh
open instead — the barrier the production code actually awaits.
2026-08-14 21:58:42 -07:00
Peter Steinberger f6f0fd9e1e fix(ui): retire the local model override once the Gateway confirms the patch (#124005)
* fix(ui): retire the local model override once the Gateway confirms the patch

settleModelOverride re-asserted the requested model into the shared
override map after every successful patch, and the reader prefers the map
over the server row unconditionally — so any external model change
(second operator window, channel /model, fallback rotation persisted by
the Gateway) never reached this window's picker or its effective-tools
key for the rest of the connection. The refreshed row already carries the
confirmed selection; retire the entry instead of shadowing the recorded
fact. deferListRefresh callers keep the published value (their refresh
has not run), rollbacks and newer-claim transfers are untouched.

* test(ui): split model-override lifecycle coverage into a focused file (max-lines)
2026-08-14 21:39:19 -07:00
Peter Steinberger 617b045887 fix(gateway,ui): discard prepared attachment media on pre-persist exits and release catalog-open payloads (#123983)
* fix(gateway,ui): discard prepared attachment media on pre-persist exits and release catalog-open payloads

Two attachment-lifecycle leaks with the same shape: state whose owner
dropped the reference without reclaiming the resource.

- chat.send stages inbound media (~/.openclaw/media/inbound) during
  attachment preparation; the abort and session-routing-changed exits
  between preparation success and user-turn persistence returned without
  deleting the staged files. Nothing references them afterward, and the
  inbound sweep is disabled unless attachments.ttlHours is set, so default
  installs orphaned them permanently. Both exits now discard through the
  same deleteMediaBuffer path the error exits already use.
- Opening a terminal-catalog session cleared state.chatAttachments without
  releasing payload-store entries, stranding Files and object URLs for the
  tab lifetime; every sibling discard path releases first.

* test(gateway): drop duplicate unused inbound baseline block
2026-08-14 21:30:11 -07:00
Vyctor H. Brzezowski 9837ad59fa fix(ui): keep the GitHub mark on its label's line when chat wraps (#123355)
* fix(ui): keep the GitHub mark on its label's line when chat wraps

The mark is an in-flow atomic inline, which carries a soft wrap opportunity
after it, so a chat line could end on the octocat and start the next one with
the reference it labels. Painting the mark out of flow and reserving its space
with the anchor's own padding removes that opportunity: nothing inside the
anchor precedes the label any more.

The anchor stays inline, so a long bare URL still fills the line it starts on
instead of moving down whole -- the sibling file-link chip's atomic treatment
could not be reused here.

* test(ui): route GitHub link layout test through Node
2026-08-15 01:26:30 -03:00
Peter Steinberger 622eec9c36 fix(ui): stage every gateway-honored agent model shape instead of silently dropping input (#123995)
The agents-page fallback editor gated staging on a resolvable primary
model, but the gateway resolver honors { fallbacks } with no primary
(agent-scope.ts, including the explicit empty-array-disables-global
contract). With a fully implicit default model, typing a fallback chip
cleared the input and staged nothing — no chip, no error, no write.
Clearing the primary had the sibling bug: removeFormValue deleted the
whole model node including authored agent fallbacks.

One stageModelShape owner now writes the smallest representable shape
(bare string, { primary, fallbacks }, { fallbacks }, or removal), and
both entry points share existingModelParts instead of duplicating
existing-shape probing.
2026-08-14 21:14:50 -07:00
Peter Steinberger 8739432d4c fix(ui,gateway): single attachment admission funnel + WS-frame-budget clamp on advertised ceiling (#123977)
* fix(ui,gateway): make attachment admission one funnel and clamp advertised ceiling to the WS frame budget

Three residual gaps from the #123654 size-limit fix shared one root cause:
attachment admission policy was scattered instead of owned.

- hello-ok advertised the decoded config ceiling (20MB default, higher with
  mediaMaxMb) without accounting for base64 4/3 expansion against the 25MiB
  WS frame cap, so a 19.6-20MB attachment passed the client guard and the
  encoded chat.send frame still hard-dropped the connection (1009) for
  every pane. The policy owner now clamps the advertised maxBytes to what
  one frame can carry.
- Large-text paste, data-URL image paste, and browser-annotation handoff
  constructed attachments without any size check, bypassing the guard that
  only lived inline in appendAttachmentFiles. All intake paths now share
  one admission funnel (chat-attachment-admission.ts).
- Zero-byte files rendered a normal chip, then the payload assembler
  silently dropped them on send; the funnel rejects them at intake with a
  named toast.

* refactor(ui): drop unused exported type from attachment admission module
2026-08-14 21:10:48 -07:00
Peter Steinberger 4e64649191 fix(ui): stop per-event full-history reconciles for queued sends behind an active run (#123966)
resumeStoredChatOutboxes runs on every session.message/sessions.changed
event; a never-attempted durable head parked behind an active run issued a
1000-message chat.history request per transcript event only to learn the
run was still active. The session row the wakeup event itself reconciled
already answers that question, so consult it first and keep the history
fetch for attempted rows whose delivered-detection genuinely needs it.
2026-08-14 21:08:42 -07:00
Vyctor H. Brzezowski 9955b70211 fix(ui): toggle terminal main content layout (#122464) 2026-08-15 01:06:54 -03:00
Peter Steinberger 295f2c0212 fix(ui): align the sidebar to a shared grid and dedupe owner presence (#123938)
* test(ui): add sidebar alignment capture harness

* fix(ui): align sidebar rows to shared grid

* fix(ui): distinguish sidebar owner presence

* fix(ui): visually hide sidebar pages label

* fix(ui): collapse the hidden pages head row

* fix(ui): keep the floating pages action clickable

* fix(ui): scope facepile dedup to the rendered lead and reveal the pages editor on touch

ClawSweeper P1s on #123938: the facepile unconditionally excluded the
session creator even when the lead chip showed the archivist or nothing
at all, hiding a live viewer; renderSessionLeadingState now returns the
rendered owner identity as the single dedup source. The hover-revealed
pages editor gains the standard hoverless-pointer visibility override.
2026-08-14 20:42:37 -07:00
Peter Steinberger 636fc94171 fix(ui): prevent stale session header errors after navigation (#123958)
* fix(ui): retire stale header outcomes

* test(ui): await retained pane presentation

* fix(ui): bind header outcomes to presentation lifetime
2026-08-14 20:38:40 -07:00
Peter Steinberger f827e784c9 fix(ui): retire companion state by lifecycle (#123941) 2026-08-14 20:33:52 -07:00
Peter Steinberger 2ebaebd484 fix(ui): re-attach chat transcript virtualizer after dashboard→split face switch (#123974)
* fix(ui): re-attach chat virtualizer when a foreign host re-stamps the transcript

Switching a board session from Dashboard back to Split left the docked chat
pane blank (sizer-height empty space, no rows) until an unrelated state change
re-rendered the pane or the user scrolled.

Root cause: the transcript template is stamped by openclaw-chat-sidebar-region
(it receives the chat template as a property and renders it in its own, later
update cycle), but the TanStack virtualizer only re-resolves its scroll element
inside the pane host's update. After the face-switch re-stamp no pane update
follows, so the virtualizer stayed detached: scrollElement null, scrollRect
zeroed by the hide transition, calculateRange null, zero rows painted.

Fix, at the owner (ChatSessionVirtualizerHost): attachment now follows the DOM
identity the scroll-element ref records — an identity change queues a microtask
that re-runs the virtualizer adapter's update hook. Zero-size rects from hide
transitions (display:none sidebar panels, unmount teardown) are ignored so they
can no longer wipe measured row heights or become the virtualizer viewport.

Live-verified against a remote gateway where the broken state persisted 5+
seconds pre-fix and one attachment sync healed it. Regression tests fail
pre-fix; e2e covers the deferred-latency dashboard->split flow.

* chore(ui): raise startup JS baseline for transcript virtualizer re-attach fix

Measured 333338 B gzip on PR #123974 run 31861214469 (+345 B over the base
run 31860496921 at 332993 B; prior baseline 332040 B predates recent main
drift). Absolute 350 KiB maintainer cap unchanged. Growth is the foreign-host
attachment sync in chat-transcript-controller.ts — real fix code, not
speculative surface.
2026-08-14 20:31:08 -07:00
Jason (Json) c1002c3ec4 fix(ui): dismiss landed steer notices (#123908)
* fix(ui): retire landed steer notices

* fix(ui): preserve steer transcript order
2026-08-14 21:19:30 -06:00
Jesse Merhi 47442197a1 feat(ui): review install policy warnings (#120900)
* feat(ui): review install policy warnings

* fix(ui): keep install feedback on runtime plugin
2026-08-15 13:07:02 +10:00
Jason (Json) cae6030575 fix(ui): clear proven uncertain sends (#123927) 2026-08-14 20:49:45 -06:00
Peter Steinberger 6abfedcca5 fix: stale Control UI tabs stop retrying after updates (#123932)
* fix(ui): preserve stale client refresh compatibility

* fix(ui): cover build-aware frozen clients
2026-08-14 19:29:59 -07:00
Peter Steinberger 7160c4de0b refactor(ui): remove disconnected build mismatch projection (#123916) 2026-08-14 18:52:22 -07:00
Peter Steinberger 0982ee5729 perf(ui): reduce startup connect error overhead (#123910) 2026-08-14 18:07:20 -07:00
Peter Steinberger 7f891f4a78 fix(ui): stale tabs keep dispatching after gateway updates (#123882)
* fix(ui): reject stale bundled clients at connect

* chore: keep release notes out of runtime fix

* fix(ui): preserve stale-build admission invariants
2026-08-14 17:05:10 -07:00
Peter Steinberger 0a6b95a3df feat: cloud workers for the codex runtime (remote-exec placements) (#123743)
* feat(gateway): add remote-exec cloud placements

* feat(codex): run cloud turns through remote exec

* fix(sandbox): quote ssh_config path directives containing whitespace

Crabbox lease keys default to ~/Library/Application Support/... on macOS;
unquoted IdentityFile/UserKnownHostsFile/CertificateFile arguments tokenize
on the space and fail as 'extra arguments'. Found via live remote-exec
cloud-worker proof.

* test: consolidate gateway maintenance schedule coverage

* fix(ci): invalidate plugin sdk declarations on state changes
2026-08-14 16:46:56 -07:00
Peter Steinberger 1cf932295c refactor(ui): trim pairing uncertainty state (#123856)
* refactor(ui): trim pairing uncertainty state

* test(ui): stabilize navigation and worker readiness

* test(ui): drop popover flip geometry replay
2026-08-14 16:16:57 -07:00
Patrick Erichsen f726393812 fix(ui): offer permanent lobster dismissal (#123789)
* fix(ui): add persistent lobster dismissal

* fix(ui): keep the lobster dismiss menu from scrolling its own items

The dismiss menu anchors a synthetic trigger at the raw pointer position,
and the pet always sits on the sidebar footer ledge at the bottom of the
viewport. Web Awesome caps its popup to `--auto-size-available-height`,
and its `size` middleware runs after `flip`, so the menu was shrunk in
place to 60px against 64px of content instead of moving up. The two
dismissal items then scrolled inside a 6px overflow, which shows as a
scrollbar for anyone running the system setting that always renders them.

Clamp the anchor to the viewport the way every other pointer-anchored
menu already does (session-menu.ts:264, catalog-session-menu.ts:55,
native-link-menu.ts, sidebar-menus-controller.ts:270), so the popup keeps
the room it needs and renders both items in full.

* docs(web): describe the lobster dismiss menu options

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: vyctorbrzezowski <krzyszchweski@gmail.com>
2026-08-14 20:00:50 -03:00
Jason (Json) 89a424bcb9 fix(ui): page activity indicator matches session rows (#123813)
* fix(ui): align page working indicator with sessions

* test(ui): align lifecycle checks with home spinner
2026-08-14 16:58:44 -06:00
Peter Steinberger 3ef7f590a7 fix(ci): invalidate plugin SDK declarations on auth changes (#123861)
* fix(ci): invalidate plugin sdk declarations on auth changes

* test(ui): await device migration overlay updates
2026-08-14 15:21:29 -07:00
Jason (Json) 386b12ba9c fix(ui): sort created sessions by creation time (#123828) 2026-08-14 15:44:38 -06:00
ClawSweeper 4fd825bc0a fix(ui): contain mobile lightbox images (#123840)
Co-authored-by: RoboClaw <309084314+roboclaw-bot@users.noreply.github.com>
2026-08-14 14:32:36 -07:00
ClawSweeper c12745429a fix(ui): fork active sessions from stable history (#123718)
* fix(ui): fork active sessions from stable history

* fix(protocol): align active fork types

* test(ui): match main-session fork routing

* fix(sessions): unify stable fork admission

* fix(native): fork active sessions from stable history

* fix(android): fork active sessions from stable history

* style(android): simplify active fork result handling

* fix(native): preserve legacy fork transport API

* test(native): complete legacy fork transport stub

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-08-14 14:13:25 -07:00
Peter Steinberger 3dff1727b1 fix(control-ui): give chat links a contrast-safe color token (#123824)
chat links used --accent while user bubbles are filled from --accent-subtle (and peer bubbles from a per-sender hue), so links sat on their own hue at 3.95:1 worst case. New --link/--link-hover tokens derive from each palette's audited --accent-hover and clear WCAG AA on every bubble surface and sender hue; the hover opacity fade, which lowered contrast, is replaced by a color shift.
2026-08-14 14:03:28 -07:00
Vyctor H. Brzezowski dc8facb060 improve(ui): explain the Utility model setting (#123277)
* improve(ui): explain utility model setting

* fix(ui): anchor utility model help above trigger
2026-08-14 17:43:52 -03:00
Vyctor H. Brzezowski b90001934a fix(ui): keep the chat header blend off the transcript scrollbar (#123308)
The header-to-transcript blend spanned the full pane width, so it painted over the strip where the transcript scrollbar lives and the thumb vanished under the fade while scrolling. Stop the blend one transcript gutter short of both pane edges and make that gutter a token shared with the transcript column, which keeps every pixel of content faded while the scrollbar stays visible (symmetric so RTL keeps the same clearance).
2026-08-14 17:30:05 -03:00
Peter Steinberger 58ab589da5 fix(ui): stale tabs restore terminals without owners (#123660)
* fix(ui): fence stale same-version clients by build

* test(ui): satisfy build identity lint guards

* fix(ui): complete build identity fencing

* chore: leave release notes to release prep

* test(ui): align build identity fixtures

* fix(ui): fence reconnects on pending worker

* test(ui): stabilize cross-platform update proof

* test(ui): satisfy service worker lint

* test(ui): preserve offline socket cohorts
2026-08-14 13:19:02 -07:00