Commit Graph

371 Commits

Author SHA1 Message Date
Vyctor H. Brzezowski e647794c20 feat(ui): open bare pair command without a chat turn (#120855)
Co-authored-by: Patrick Erichsen <patrick.a.erichsen@gmail.com>
2026-08-12 21:09:13 -07:00
Peter Steinberger ffb2ed9e89 refactor: remove residual normalization aliases (#122956)
* refactor: remove residual normalization aliases

* test(gateway): isolate approval authority handshake
2026-08-12 20:26:30 -07:00
Peter Steinberger fd0fc80c8c test(microsoft-foundry): remove global test bridge (#122881)
* test(microsoft-foundry): remove global test bridge

* test(microsoft-foundry): type boundary fixtures

* perf(ui): keep route transition out of startup
2026-08-12 18:53:44 -07:00
Peter Steinberger cef071582e feat: let limited browsers request admin access (#121459)
* feat(gateway): add live device scope upgrades

* feat(ui): add limited-access upgrade flow

* fix(protocol): refresh Swift scope upgrade models

* perf(ui): lazy-load device scope upgrades

* fix(ci): complete scope upgrade generated surfaces

* perf(ui): lazy-load GitHub link hovercards

* fix(ui): keep admin repair guidance focusable

* fix(ui): gate and refresh scope upgrade banner

* refactor(ui): keep gateway client within line budget

* fix(ci): align rebased scope upgrade checks

* fix(ui): resolve scope upgrade in browser tests

* fix(gateway): honor refreshed scope upgrade deadline

* fix(gateway): honor refreshed scope upgrade deadline

* fix(gateway): coalesce scope upgrade waiters

* fix(ui): gate scope upgrade actions

* chore(plugin-sdk): refresh rebased API baseline

* fix(scope-upgrade): return canonical request ids

* fix(ui): preserve gateway event type binding

* fix(protocol): generate scope upgrade result models

* fix(ui): preserve scope upgrade recovery guidance

* chore(plugin-sdk): refresh rebased API baseline

* test(ui): avoid scope upgrade navigation race

* docs(control-ui): clarify scope upgrade approver

* test(gateway): align appended method counts

* chore(plugin-sdk): refresh rebased API baseline

* refactor(ui): keep place picker within line budget

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* fix(gateway): preserve scope-upgrade browser origin
2026-08-12 18:32:00 -07:00
Josh Lehman a3207b574c fix(ui): show progress during new session startup (#122713)
* fix(ui): show progress during new session startup

Refs #122703

* oc-1fa: keep new-session progress through handoff

* fix(ui): keep new-session handoff live

* fix(ui): preserve navigation callback contract
2026-08-13 00:17:12 +00:00
Peter Steinberger adf8e254ab fix(ui): let read-only operators copy task results (#122841)
* fix(ui): let read-only operators copy task results

* refactor(ui): consolidate operator scope checks
2026-08-12 15:58:55 -07:00
Peter Steinberger 87b503675a fix(ui): recover failed bundled plugin views (#122823)
* fix(ui): recover failed bundled plugin views

* fix(ui): guard automatic plugin chunk reloads
2026-08-12 15:10:21 -07:00
Peter Steinberger c23d66e3b5 refactor: consolidate coercion ownership (#122692)
* refactor: consolidate coercion ownership

* test: align shard check with weighted planning

* chore: refresh plugin SDK API baseline
2026-08-12 09:25:28 -07:00
Peter Steinberger 557a8aeab0 refactor(ui): delete the orphaned custodian panel toggle contract (#122663)
Removing the Ask OpenClaw button from the chat workspace rail (#122507) left
CUSTODIAN_PANEL_TOGGLE_EVENT with no production dispatcher, so its event
constant, detail type, panel listener, handleToggleRequest, toggle(), the
shell's deferred-load handler and its forwarding delegate were all dead.

The floating panel keeps its real open path: app-shell-navigation raises
minimizeRequestId when the operator leaves the /custodian route mid-conversation
and the panel opens itself when the store has a real user turn. Preloading is
unchanged and still gated on openclaw.chat, so the panel mounts exactly when it
could be available.

Tests move onto that surviving path rather than being deleted; the unused
custodian.panel.toggle string goes with the contract.

Production LOC: -64, none added.
2026-08-12 08:00:21 -07:00
Peter Steinberger df707a9670 feat: view this machine in the Desktop panel (#122545)
* feat(gateway): add gateway-host desktop source behind desktop.host lab

Introduce the host as a first-class desktop source so operators can view
the machine OpenClaw runs on, not just cloud-worker environments:

- protocol: desktop.observe / desktop.launch with a discriminated
  DesktopSource union (host | environment) plus an additive auth hint;
  EnvironmentSummary gains a top-level desktop flag
- config: desktop.host { enabled, port?, passwordFile? }, Labs-gated
- rfb-probe: pure RFB version/security-type parser used to detect an
  already-running loopback VNC server and classify its auth
- host-source: attaches to 127.0.0.1:<port>, refuses unauthenticated
  (None) and unsupported (VeNCrypt) servers, and refuses ARD with the
  supported alternative until the macOS milestone
- host-guidance: per-OS enablement text so no path dead-ends
- doctor + status report host desktop availability and auth type only

worker.desktop.observe/launch stay as delegating aliases with identical
behavior. Also drops the now-unused WorkerDesktopTunnels type export.

Live-verified against macOS Screen Sharing: probe reads RFB 003.889,
returns security types [30,33,36,35], classifies ard-account.

* test(gateway): probe RFB handshakes through the socket boundary

The probe's banner and security-offer parsers were exported solely so
unit tests could call them, which the dead-export gate rejects and which
tests internals rather than behavior. Keep them module-local and drive
the probe through a scripted loopback server instead.

The boundary tests also cover what pure-function vectors could not:
handshakes split across packets, legacy RFB 3.3 single-word security,
server-rejected handshakes, early hangups, and connect timeouts.

* feat(ui): let the Desktop panel view this machine, not just cloud workers

The Desktop panel was gated on a cloud-worker session placement, so an
operator running OpenClaw locally had no way to see the machine hosting
their main session even with a VNC server running on it.

Availability now follows the advertised desktop.observe method plus
operator.admin instead of session placement, and the picker lists every
environment whose summary reports a desktop, with the gateway row shown
as "This machine". Sources are passed to the generic desktop.observe /
desktop.launch RPCs; the app launcher stays worker-only. When a host
attach needs a password the gateway did not supply, the panel prompts and
keeps the value in memory for that connection only.

Adds the hostDesktop Labs toggle for desktop.host.enabled.

* fix(scripts): keep the env-var ratchet usable in shallow checkouts

The env-var budget check resolved its base ref, then hard-failed when
`git merge-base` found no shared ancestor. Shallow clones and grafted
agent checkouts resolve origin/main but truncate the history behind it,
so an advisory growth ratchet took down the whole check:changed gate
with "Could not resolve env-var count merge base for: origin/main".

Only the growth comparison needs a baseline, and the script already has
a no-baseline path. Treat git's exit 1 with empty output (no shared
ancestor) as that case and say so on stderr; a genuine failure still
exits 128 and still throws, and the absolute count-vs-budget check runs
either way.

* test(ui): measure the inline-code chip against its line box

The inline-code spacing test compared the chip's height to a prose text
rect, so it silently measured the monospace font's default line spacing.
That is ~17px on macOS and several px shorter on Linux, so the assertion
passed on CI and failed locally at 4.5 against a 3.75 bound -- after the
bound had already been widened once to chase browser font metrics.

Compare the chip to the paragraph's CSS line box instead, which is what
"the chip must not disrupt the line" actually means and is platform
independent. The horizontal gap stays as-is: it is em-derived padding
plus border, and it is the assertion that catches detached punctuation.

Verified both directions on macOS: the file is fully green, and
restoring the pre-fix 0.15em/0.35em padding still fails the gap
assertion at 5.41.

* feat(gateway): view macOS Screen Sharing from the Desktop panel

Modern macOS only offers ARD account authentication for Screen Sharing,
so the host desktop source refused every Mac. The Gateway now performs
the ARD handshake itself against the loopback server and hands the
browser a plain RFB 003.008 no-auth handshake, so the operator's macOS
account password authenticates the desktop without ever reaching the
browser, the observe result, a URL, or a log.

- rfb-preauth: ARD (type 30) Diffie-Hellman with MD5-derived AES-128-ECB
  credentials, and VncAuth (type 2) bit-reversed DES, both under a single
  10s negotiation deadline; Apple's RFB 003.889 maps to 3.8
- observe-bridge: runs pre-auth before splicing and starts the view-only
  filter at clientInit, since the browser handshake is consumed here;
  worker tokens keep the original version start phase
- host-source: attaches ARD, requiring per-observation credentials that
  live only in the one-shot observer token and are dropped after use
- doctor: offers an explicitly confirmed sudo launchctl repair when
  Screen Sharing is off, and prints the System Settings path otherwise

Live-verified against this Mac's Screen Sharing: the DH exchange and
credential framing are accepted and the server returns SecurityResult.
The VncAuth DES vector is confirmed against OpenSSL independently.
2026-08-12 06:58:30 -07:00
Peter Steinberger d44f70eb4b feat(pairing): one-paste device pairing via oc-pair setup links (#120768)
* feat(pairing): one-paste device pairing via oc-pair setup links

Implements milestone 3 from docs/plan/runners.md.

* fix(pairing): sign bootstrap handshake, keep URL candidates, wire pairing countdown

* test(gateway): update client callsite guard

* fix(pairing): preserve setup URL context paths

* fix(ui): keep pairing help aligned with setup mode

* fix(pairing): isolate bootstrap credentials

* perf(ui): keep one-paste pairing within bundle budget

* refactor(pairing): isolate native pair URL prefix parsing

* fix(pairing): preserve candidate lifecycle state

* fix(pairing): retire shared credentials after bootstrap

* fix(pairing): apply rotated manifest through client owner

* test(pairing): prove bootstrap retirement across reconnect

* fix(pairing): preserve native gateway context paths

* fix(pairing): carry native context paths through reconnect

* fix(ios): preserve encoded gateway context path

* chore(plugin-sdk): refresh pairing API baselines
2026-08-12 02:01:33 -07:00
Peter Steinberger bfe1f33ea0 improve(ui): make Control UI feel native on mobile (#122492)
* improve(ui): make Control UI feel native on mobile

* fix(ui): keep coarse-pointer input floor text-scale aware

* fix(ui): let self-sized controls opt out of the touch input floor

* fix(ui): fold per-control coarse-pointer font floors into the shared touch floor
2026-08-12 00:46:35 -07:00
Peter Steinberger 87ecc4085d feat(webui): auto-request notification permission on first chat send (#122446)
* feat(webui): auto-request notification permission on first chat send

The Control UI now asks for notification permission automatically on the first chat message you send (once per browser/origin; web push in browsers, native prompt in the macOS app; denied permission is never re-asked automatically — Settings -> Notifications stays the manual path).

* fix(webui): preserve notification prompt gesture boundary

Restrict the one-shot notification prompt to direct non-command composer sends, invoke browser permission synchronously, and keep Settings documented as the management and recovery path.

* fix(webui): exclude deferred notification sends

Require an actively dispatching browser input event before consuming the notification one-shot, so retained catalog handoffs and other deferred sends cannot suppress the first real composer prompt.
2026-08-11 23:52:05 -07:00
Peter Steinberger b080dd1e76 refactor: consolidate coercion contracts (#122458)
* refactor: consolidate coercion contracts

Centralize exact string, record, numeric, date, Boolean, argument, and structured-error coercions while preserving call-site semantics.

Migrate canonical-name collisions and deprecated internal SDK bypasses, deleting 55 net production/tooling lines. Expand declaration ownership enforcement to 101 allowed helpers and add a narrow export-completeness audit.

* fix: preserve standalone script coercions

Keep copied Control UI tooling self-contained and retain the trusted release harness module-relative source seam when the harness runs against an old target cwd.
2026-08-11 23:26:37 -07:00
Peter Steinberger 964c8c84c1 refactor: consolidate coercion ownership (#122299)
* refactor: consolidate coercion ownership

Centralize four canonical coercion helpers, migrate exact core and plugin duplicates through narrow Plugin SDK facades, and enforce declaration and plugin-normalization ownership boundaries.

The sweep adds eight focused SDK exports while deleting more production and tooling code than it adds. User-visible behavior is unchanged except for safer equivalent object and UI parsing at existing boundaries.

* fix: guard integer option ownership

Register resolveIntegerOption with the canonical function owner and extend the declaration-guard fixture so future local duplicates fail validation.

* fix: keep integer helpers on numeric facade

Remove the unshipped duplicate string-coerce exports and route every affected plugin consumer through the existing number-runtime contract.

* fix: point numeric coercion to number runtime

Make boundary and declaration diagnostics recommend the canonical numeric facade, with failing-before coverage for both guidance paths.
2026-08-11 17:14:53 -07:00
joshavant 5f7ea6b548 fix(ui): lazy-load run inspector copy 2026-08-11 18:44:38 -05:00
Vyctor H. Brzezowski cfc684e49c fix(ui): remove the pre-creation session row (#122259) 2026-08-11 18:18:03 -03:00
Peter Steinberger cad77fb39c refactor: consolidate remaining coercion helpers (#122020) 2026-08-11 10:22:01 -07:00
Peter Steinberger 5c81ed5883 refactor(ui): split runtime config into layered modules (#122117)
* refactor(ui): split runtime config into layered modules

* refactor(ui): delete redundant config wrappers

* chore(lint): ratchet max-lines baseline after ui config split
2026-08-11 10:17:00 -07:00
Peter Steinberger 2d69e39796 fix(ui): scope Desktop panel to cloud sessions (#122145) 2026-08-11 10:06:40 -07:00
Vyctor H. Brzezowski e7ba2e1f6b fix(ui): give the Gateway update a visible, truthful outcome (#121686)
* fix(ui): give the Gateway update a visible, truthful outcome

Confirming an update in the Control UI could end in nothing the operator
could see. The sidebar card only knew `updateRunning`, which clears as soon
as `update.run` answers, so a dev-channel handoff — git pull, install, full
build, restart — spent minutes back on the stale "N commits behind" call to
action. A verified install then published a null banner, so success was
never announced at all, and the reload that a verified install triggers
would have destroyed any in-memory notice anyway.

The lifecycle was already recorded; nothing rendered it. Feed the sidebar
the reconciliation fact the settings page already uses, narrate the wait in
the shell callout that survives the restart, and record the verified result
so it outlives the reload it causes.

Failures now name their cause. The updater already stores the failing step
and its captured output in the restart sentinel and `update.status` returns
it whole, so a disk-full install reads "The update failed at install:
ENOSPC: no space left on device" instead of a reason slug plus "see the
gateway logs". Missing handoff-reason guidance is filled in, and the
duplicate post-restart banner resolver is folded into the shared one.

Also fixes a callout clipped by the fixed shell chrome controls, and the
"Available 246 commits behind" double framing in the confirmation.

* fix(ui): report the Gateway update in the dialog that starts it

Follow-up to the first pass: the shell-top callout was the wrong surface for
this. The dialog the operator clicked is now the one that reports the whole
update, and the ambient surfaces are the fallback for anyone who leaves it.

Confirming no longer closes the dialog. It becomes stateful — the danger
button turns into a disabled "Updating…" with a spinner, and the body
narrates the phases in place: installing, then "The Gateway is restarting.
This page disconnects and reconnects on its own." The dialog is mounted on
document.body, outside the shell, so losing the connection cannot unmount
the only surface still reporting. A request the Gateway never accepts stops
spinning after a bounded grace and says so.

A failure lands in the dialog with its recorded cause and next step. Success
still arrives after the reload a verified install triggers, so it stays a
toast — no dialog exists by then.

The shell-top status callout is gone. The same outcome now renders beside
the sidebar update card that started the update, and stays hidden while the
dialog owns the report so the two surfaces never duplicate each other.

Splits the wire-payload readers out of update-overlay-helpers into
update-schedule-dto and moves update.run answer classification next to them,
keeping both owners under the file-size budget.

* fix(ui): keep the update dialog open through the install in E2E and use a defined warn token

* refactor(ui): wire the update progress stream into the floating card and drop unused video capture

* fix(ui): report the current update, not the previous failure

The dialog subscribed to progress before starting the update. Producers
replay their current snapshot synchronously on subscribe, so a retry
still carrying the previous attempt's failure banner painted that old
error as this update's outcome.

Start the request first, and treat a failure present on the subscribe
emit as the previous attempt's: an accepted run clears the banner before
its first await, so a failure that survives means the request was
refused, which the accept timer already reports truthfully.

* fix(ui): keep update helpers internal

* chore(perf): raise startup-JS budget to 350 KiB

Approved by maintainer Vyctor on 2026-08-11.

Trigger: after #121734, main measured 326650 B—6 B below the prior 319 KiB hard ceiling. Current main subsequently moved the ceiling to 320 KiB; this records the authorized final 350 KiB decision for #121686.

* fix(ui): announce update success without session storage

Show the verified install identity directly when the current document does not reload, so denied session storage cannot suppress the operator-visible outcome.

* test(ui): isolate update toast in overlay tests

The Node-only overlay suite now reaches the direct non-reload success toast. Mock the DOM-owned toast boundary so reconciliation assertions can complete without a browser document.
2026-08-11 13:49:12 -03:00
Vyctor H. Brzezowski d91b3970b4 fix(ui): show mobile toasts above the drawer (#121858)
* fix(ui): keep toasts above modal overlays

* fix(ui): route toasts through open modal state

* fix(ui): simplify modal toast routing

* fix(ui): preserve modal toasts with atomic moves

* fix(ui): route toasts across modal roots

* test(ui): cover nested shadow modal toasts

* fix(ui): keep modal toast lifecycle internal
2026-08-11 05:53:57 -03:00
Vyctor H. Brzezowski 3dc03f9265 fix(ui): confirm destructive sidebar session actions in-app (#121286)
* fix(ui): confirm destructive sidebar session actions in-app

The sessions sidebar still gated batch delete, single delete, cloud-worker
stop, and preserved-worktree removal on native window.confirm. In an
embedded WebView without a dialog bridge that call resolves false, so each
action returned early with no request, no error, and no visible outcome.

PR #118250 introduced showConfirmDialog and converted the Sessions,
Worktrees, and Nodes pages but not the sidebar operations layer, leaving
one product action with two confirmation mechanisms. Route all four sites
through the same helper and let the already-present post-decision scope
guards do their job, extracting the cloud-worker reclaim guard above the
await so the modal never opens for a stop this surface never performs.

Closes #121275

* feat(ui): let operators opt out of the session delete confirm

Session deletes are the repeatable, per-row destructive action in the
sidebar, so their confirm now offers "Don't ask me again". Stopping a cloud
worker and removing a preserved worktree deliberately get no opt-out: the
first is a rare shared-resource action, the second destroys the only copy
of uncommitted work. The checkbox exists only for callers that pass a skip
preference, so the serious confirms stay unskippable by construction.

The preference is device-local in UiSettings rather than a synced ui.prefs
key, so opting out on one browser cannot lower the bar on the operator's
other devices. Appearance -> Sidebar carries the matching toggle with the
standard reset affordance, which is how asking gets turned back on.

* fix(ui): scope the delete opt-out copy and refresh its settings view

Autoreview surfaced two preference-consistency defects.

The setting copy promised a general "session or a selection" policy, but
only the sidebar honours it; the Sessions page delete paths still prompt.
Extending the preference there would cross into another owner's in-flight
surface, so narrow the copy to what actually ships and keep the extension
as the recorded follow-up.

Persisting the opt-out also wrote local storage without notifying the
appearance subscription, so a mounted Settings -> Appearance kept showing
the toggle enabled while deletes already skipped the prompt. Publish the
refresh through the scope's theme capability after persisting.

* fix(ui): space the confirm opt-out away from the message copy

The checkbox rendered flush under the message with no separation, so it
read as a third line of the paragraph rather than a distinct control, and
the group crowded the action row.

Give it the 12px separation the details block already uses in this card,
keeping the 16px action gap below, plus the 10px control gap and 16px box
the config-form checkboxes use so the box and label align. Verified in
light and dark at desktop and mobile widths.

* fix(ui): use the cursor-action token on the confirm opt-out

The spacing fix hardcoded cursor: pointer, which the Control UI cursor
policy forbids outside link rules; controls consume var(--cursor-action)
so the hand stays configurable from one place.

Caught by ui/src/styles/cursor-policy.node.test.ts. Token resolves to the
same hand, so the published dialog captures are unaffected.

* fix(ui): keep the delete opt-out to the surface its setting names

deleteSession is shared: the chat-pane header menu calls it too. The
skip preference was applied inside the operation, so opting out from the
sidebar also silenced future header confirmations while the setting text
promised it applied to sidebar deletes only.

Make the opt-out opt-in per caller with a default that keeps asking, and
let the sidebar be the one caller that offers it. The header and any
future caller now match the copy without having to remember anything.

* refactor(ui): split the session group catalog out of the operations runtime

Adding the in-app group confirm on main pushed
session-organizer-operations.runtime.ts past the 700-line ceiling once
this branch's session confirms landed beside it.

Move the catalog and section-order operations into their own module. They
write the group catalog directly and never touch session rows, so the
dependency runs one way and no import cycle appears. The shared access
gate moves to the batch-mutations sibling both now import, and the
runtime keeps re-exporting the catalog names the controller loads as one
namespace.
2026-08-11 02:15:05 -03:00
Peter Steinberger c896b9b2cd test(ui): remove duplicate update confirmation cases (#121836) 2026-08-10 21:40:03 -07:00
Peter Steinberger 750d0dcd9e improve(ui): make warm session switching instant (#121625)
* perf(ui): make warm session switching instant

Amp-Thread-ID: https://ampcode.com/threads/T-019fe957-0e49-707f-859f-9600ef536125

* fix(ui): harden retained session ownership

Amp-Thread-ID: https://ampcode.com/threads/T-019fe957-0e49-707f-859f-9600ef536125

* test(ui): scope retained session assertions

Amp-Thread-ID: https://ampcode.com/threads/T-019fe957-0e49-707f-859f-9600ef536125

* fix(ui): align generated image preview checks

* fix(ui): preserve early transcript reading position

Amp-Thread-ID: https://ampcode.com/threads/T-019fe957-0e49-707f-859f-9600ef536125

* fix(ui): adopt latest-navigation-wins router

Amp-Thread-ID: https://ampcode.com/threads/T-019fe957-0e49-707f-859f-9600ef536125

* fix(ui): restore retained pane reactivity

Amp-Thread-ID: https://ampcode.com/threads/T-019fe957-0e49-707f-859f-9600ef536125

* test(auto-reply): align item lifecycle expectations

* fix(ci): repair current-main validation gates

Amp-Thread-ID: https://ampcode.com/threads/T-019fe957-0e49-707f-859f-9600ef536125

---------

Co-authored-by: Amp <amp@ampcode.com>
2026-08-10 16:17:25 -07:00
Shakker fb9b1aba9e fix: retain replacement attachment payload ownership 2026-08-10 16:10:18 +02:00
Shakker 607f50bece fix: preserve fallback attachment packages across remounts 2026-08-10 16:10:18 +02:00
Shakker c5a5ba2263 fix: preserve staged attachments across chat remounts 2026-08-10 16:10:18 +02:00
Peter Steinberger 6b565c047f feat(ui): show unsent-draft pencil on sidebar session rows (#121476)
* feat(ui): show unsent-draft pencil on sidebar session rows

Typed-but-unsent composer text now surfaces as a pencil badge on the
owning session's sidebar row (and Home row) once you switch away.
Draft persistence now notifies stored-outbox subscribers so the
indicator appears and clears live. The active session suppresses the
badge since its composer is already visible.

* chore: refresh merge ref for CI against current main

* chore: refresh merge ref against healed main

* fix(ui): notify draft indicator only on presence transitions

Unconditional notify on every draft persist let outbox-projection
subscribers re-persist a stale pane over a newer draft (chat-state
route-fallback invariant). The sidebar pencil only consumes presence,
so notify on empty/non-empty transitions only.
2026-08-10 04:53:15 -07:00
Peter Steinberger 3a1f645228 fix(ui): prevent duplicate initial prompts during session startup (#121498)
* fix(ui): unify initial prompt handoff projection

Route create-time prompts through the canonical session projection so live persistence and delayed history adopt one stable bubble. Preserve inline attachment content across reconnects and authoritative identity handoff while removing duplicate matching and post-reducer mutation paths.

* fix(ui): preserve initial prompt projection contracts

* fix(ci): restore exact-head validation
2026-08-10 02:38:33 -07:00
Peter Steinberger a6a464f4c0 feat(terminal): fullscreen /terminal route and dot-matrix claw intro (#121451)
* feat(ui): add fullscreen /terminal route and open button

* feat(terminal): dot-matrix claw intro banner and CLI mascot

* fix(terminal): satisfy lint and wizard-header assertions for the new mascot

* ci: account for fullscreen terminal startup bytes

* test(terminal): split session manager coverage
2026-08-09 23:52:59 -07:00
Vyctor H. Brzezowski 35e8798828 fix(ui): offer undo and a named recovery path when hiding a sidebar section (#121385)
* fix(ui): offer undo and a named recovery path when hiding a sidebar section

Hiding a session catalog section wrote the preference and returned nothing:
the section vanished, no undo was offered, and the only standing recovery
(Settings > Appearance > Sidebar) was never named anywhere near the action.

Announce the outcome at the producer. hideSessionCatalog now names the section
that disappeared, offers Undo, and carries the Settings path in the toast text
for after it times out. The hidden-catalog set gets one owner,
setStoredSessionCatalogHidden, so the sidebar, the undo, and the Settings
toggle all mutate the same authoritative read instead of each rebuilding the
set from its own copy.

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

* fix(ui): align the automation-sessions checkmark with its popover siblings

Every selection state in the sidebar sessions filter popover reads on the
trailing edge, but "Show automation sessions" used Web Awesome's
type="checkbox" checkmark, which its template renders before the label. The one
row in the menu that is a checkbox rather than a radio put its check on the
opposite side of the row from every neighbour.

Hide the leading part and render the shared session-menu__check in the details
slot, so the row matches GROUP BY / SORT BY / STATUS while Web Awesome keeps
owning the menuitemcheckbox role and aria-checked.

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

* fix(ui): stop toasts from ellipsing away their recovery instruction

app-toast__message was nowrap + ellipsis, so any message longer than one line
lost its tail. That is fine for a bare acknowledgement and wrong for a toast
that tells the operator how to undo or where to re-enable something: the part
that gets cut is exactly the part they need.

Wrap instead, clamped to three lines so a pathological string still cannot grow
the toast without bound.

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

* refactor(ui): make the hidden-catalog writer module-private

With setStoredSessionCatalogHidden owning every hide/show, the raw
storeHiddenSessionCatalogIds writer no longer has a caller outside its own
module and the deadcode:exports gate flagged it. Drop the export rather than
keep a second public way to write the same preference, and move the two tests
that used it onto the owner they are actually meant to exercise.

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

* fix(ui): anchor toasts to the bottom-right corner

Toasts were centered over the content column, which on chat routes puts a
passive notification directly on top of the composer. Move the shared
.app-toast container to the trailing bottom corner, the standard desktop
placement for passive notifications.

Anchoring it there alone would trade one overlap for a worse one: at a 20px
inset the toast lands exactly on the composer's mic and send controls. Chat
routes therefore lift it above the composer via --app-toast-inset-bottom on
.shell--chat, which the toast host inherits as a child of .shell. Routes
without a composer keep the tight 20px corner.

Phone widths keep the full-width bottom idiom, and now get it properly: the
centered toast used to collapse to its content width (200px measured at 390px
viewport), so the explicit left/right insets also widen it to 366px.

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

* fix(ui): drop the chat-route toast lift

The corner toast no longer clears the composer on chat routes. Overlapping
the composer region is the accepted trade for one placement rule on every
route, so the route-specific 140px lift and its custom property are gone and
every surface uses the same 20px safe-area corner inset. Wrapped toast text
balances its lines now that the message can run to two.

* fix(ui): open the sidebar recovery path from its toast

Naming "Settings > Appearance > Sidebar" in the hide toast still left the
operator to find it. The recovery sentence is now a link built from the
settings-search destination that already describes that block, so the toast
opens the re-enable list instead of describing where it lives. Toast messages
accept a template for that.

* revert: leave the automation-checkmark repair to its own PR

#121429 fixes the same Web Awesome checkmark alignment on its own, with
browser regression coverage this branch does not carry. Two PRs owning one
CSS part is worse than losing the drive-by, so this keeps to the hide and
recovery invariant.

This reverts commit 6b1438a5be4bb1c9dd4c2ce9cd7d0dd54e77cb00.

* fix(ui): let the hide outcome reach a phone operator

On a phone the sidebar only exists inside the navigation drawer, and that
drawer is a modal dialog: a toast raised behind it is both occluded and inert,
so hiding a section there stayed exactly as silent as before this branch.
Promoting the toast into the top layer only fixes the paint half — a modal
dialog inerts everything outside it, so Undo and the recovery link still take
no clicks. So the hide hands the drawer back to the shell first, through a
seam the sidebar can use for any outcome that belongs on the main surface.
It is a no-op wherever the drawer is not open.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 03:41:48 -03:00
Peter Steinberger deb63f9db3 fix(ui): make source builds deterministic (#121433) 2026-08-09 22:58:57 -07:00
Peter Steinberger b81946d6af fix(ui): make Cloud Worker startup responsive (#121200)
* fix(openai): filter platform-only ChatGPT catalog rows

* fix(ui): show Cloud Worker startup progress

* fix(ui): keep cloud startup out of app bootstrap

* fix(ui): reduce cloud startup bootstrap cost

* fix(ui): satisfy cloud startup lint

* fix(ui): keep cloud startup within UI limits

* fix(ui): satisfy cloud startup CI limits

* fix(ui): isolate concurrent cloud startup

* fix(ui): address cloud startup review findings

* fix(ui): persist cloud startup recovery per session

* style(ui): format cloud startup chat props
2026-08-09 22:42:45 -07:00
Peter Steinberger 7d1958bf61 fix(ui): center the onboarding dashboard and fix its card chrome (#121416)
* fix(ui): center onboarding dashboard and quiet its chrome

The custodian onboarding route carried the settings takeover class, so the
shell reserved a hidden 288px sidebar column and the whole page rendered
off-center. Onboarding now opts out of the takeover, drops the duplicate
header identity (the thread already introduces the mascot), and option-card
hover no longer lifts or re-tints cards.

* fix(ui): stop next-step cards overlaying the message meta row

The option-card and wizard-step thread attachments pulled themselves up with
a -12px top margin, covering the preceding message's sender/timestamp line.

* test(ui): align nudge e2e with the minimal onboarding header
2026-08-09 22:31:17 -07:00
Peter Steinberger 0ce757027a fix(ui): restore Desktop panel launchers (#121322)
* fix(ui): restore Desktop panel launchers

* perf(ui): keep Desktop launchers within startup budget
2026-08-09 19:08:54 -07:00
Peter Steinberger 055a2dc6ce feat: continue dashboard sessions from CLI URLs (#120893)
* feat(cli): ingest session targets

* refactor(ui): remove gateway scope shim

* docs(cli): document session targets

* fix(cli): classify session target failures

* fix(cli): keep session target result private

* fix(cli): simplify timeout option warning

* build: declare session URL contract dependency

* fix(cli): parse bare session URL options symmetrically

* fix(cli): preserve command-owned URL arguments

* build: keep session URL contract build-only

* fix: address session URL review findings

* test: preserve session key mock exports

* fix: keep session URL helpers internal

* fix(tui): preserve URL agent for global sessions

* fix(tui): keep URL agent input internal

* fix(gateway): reconcile websocket protocol owner

* fix(attach): preserve global session agent ownership

* fix(attach): enforce global owner at grant boundary
2026-08-09 16:44:25 -07:00
Peter Steinberger 810b99432c test(ui): remove source-mirror assertions (#121266) 2026-08-09 15:48:41 -07:00
Vyctor H. Brzezowski 95fdce7049 fix(ui): confirm disruptive Control UI updates before they start (#121234)
* fix(ui): confirm disruptive Control UI updates before dispatch

Every Control UI update affordance dispatched `update.run` (or the macOS
bridge `start-update` message) on the first click, so a misclick could
install new code, restart the Gateway, and interrupt active work.

Add one canonical confirmation gate in the Control UI owner boundary and
route the sidebar/settings-sidebar/floating update card and the
Settings -> Updates row through it. The gate owns the copy, the versions,
the restart consequence, the action-specific confirm label, and the
choice between the macOS bridge and `update.run`, so the bridge fork no
longer lives in the card and no surface can drift.

CLI, API, automation, agent-driven paths, automatic update campaigns, and
the `update.run` contract are unchanged.

Addresses the Control UI portion of #117178.

* style(ui): apply oxfmt to update confirmation sources

* perf(ui): load the update confirmation lazily and share its impact copy

Keeps the Control UI startup bundle under its 317 KiB gzip ceiling: the
confirmation dialog only loads when an operator opens it.

* test(ui): drive the update confirmation in sidebar and shell wiring tests
2026-08-09 19:40:06 -03:00
Peter Steinberger 8fdf7570a1 feat(gateway): live Desktop observer for cloud workers (Labs) (#120727)
* feat(gateway): live desktop observer for cloud workers

Adds live observation for cloud worker desktops through the gateway and Crabbox plugin, including desktop provisioning, persisted desktop metadata, tunneled WebSocket proxying, and the worker.desktop.observe protocol method.

The gateway, Crabbox plugin, and gateway protocol surfaces remain off by default behind the cloudWorkers.desktop Labs flag.

* feat(ui): Desktop panel for cloud worker observation

* docs(gateway): document cloud worker desktop lab

* fix(ci): regenerate contract baselines after rebase

* fix(protocol): regenerate Android gateway methods

* fix(ci): align rebased SDK and lint baselines

* fix(gateway): enforce view-only RFB boundary and fence desktop teardown

* fix(gateway): tighten RFB filter surface

* fix(state): keep pre-desktop databases readable and harden view-only RFB

* fix(gateway): fence desktop observer upgrades behind work admission

* fix(gateway): bind desktop observer tokens to their owner epoch

* fix(ci): regenerate config and SDK baselines after rebase

* fix(ci): regenerate native protocol and SDK baselines

* fix(ci): regenerate contracts after main rebase

* fix(state): register desktop metadata as lazy additive

* fix(ci): regenerate SDK baseline after final direct-merge rebase
2026-08-09 09:37:01 -07:00
Peter Steinberger 8616c0c374 refactor: finish shared test helper migrations (#120996)
* test: finish shared helper migrations

* test: fix helper migration CI

* style: fix test import ordering

* test(acpx): restore deferred void types

* test: fix helper migrations after rebase
2026-08-09 06:00:06 -07:00
Shakker 7874024748 fix: preserve staged browser annotations across remounts 2026-08-09 13:56:55 +02:00
Colin Johnson c44b01614d fix: profile saves leave stale sidebar identity (#120791)
Profile identity changes now refresh immediately across connected browsers, including revisioned avatar URLs that avoid stale cached images.

Fixes #120784
Prepared head SHA: 20fb9ea42d
Co-authored-by: Colin Johnson <211764741+Solvely-Colin@users.noreply.github.com>
Co-authored-by: Shakker <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd
2026-08-09 11:58:19 +01:00
Peter Steinberger 6f56d797d7 fix(ui): survive route notFound at startup and stop silent chat draft loss (#120960) 2026-08-09 02:57:54 -07:00
Peter Steinberger e40e352fe7 refactor(session-url): centralize normalization and contract tests (#120945)
* refactor(session-url): centralize contract normalization

* fix(ui): resolve normalization agent-id alias

* test(session-url): make grammar tables type-safe
2026-08-09 02:56:07 -07:00
Peter Steinberger f67da420f6 feat(ui): labs-gated CLI agent targets in new-session model picker (#120949)
* feat(config): gate CLI agent discovery

* feat(ui): add CLI agents to model picker
2026-08-09 00:00:00 -07:00
Peter Steinberger b4cedfd40e fix(ui): show dev checkout commit lag in Updates (#120769)
* fix(ui): report dev checkout update status

Show tracked-upstream commit lag and verified install/commit timestamps in Settings > Updates. Verify the post-restart Git revision before reporting success, and surface same-revision updates as an explicit no-op unless plugin convergence changed the install.

* test(ui): expect authoritative update reconciliation
2026-08-08 17:21:42 -07:00
Peter Steinberger bd6d35443f fix(ui): keep dashboards alive across view switches (#120640)
* fix(ui): retain dashboards across view switches

Keep visited dashboard runtimes alive across Chat, Split, and Dashboard transitions, suspend hidden work, and avoid same-face persistence requests.

Refs #120639

* fix(ui): preserve session owner during route loads

Carry the last successful route match through pending loads so face-only navigation retains dashboard runtimes without stranding session or catalog switches.

Refs #120639

* fix(ui): preserve sandbox bridge while hidden

Suspend hidden board frame work without disposing the one-shot sandbox handshake, loaded document, or MessagePort.

Refs #120639

* fix(ui): forward sandbox readiness while hidden

Keep the constrained sandbox message channel alive during dashboard suspension so one-shot proxy readiness reaches the retained host without resuming widget work.

Refs #120639

* fix(ui): pause hidden plugin widgets

Propagate board activity through trusted plugin renderers so Workboard widgets suppress hidden refresh and mutation work, then refresh once without remounting.

Refs #120639
2026-08-08 15:53:59 -07:00
Peter Steinberger 47fd629c8d feat(gateway): persist per-origin device auth (#120533)
* feat(gateway): add per-origin device auth

* fix(ui): remove unused gateway scope alias

* test(tui): split gateway chat coverage

* test(tui): update connection test routing

* fix(auth): preserve legacy device auth migration guard
2026-08-08 13:53:31 -07:00
Peter Steinberger f15ce3941f refactor(ui): devices is the user-facing word for paired hardware (#120689)
Implements docs/plan/runners.md milestone 1b.
2026-08-08 13:47:17 -07:00