17 Commits

Author SHA1 Message Date
Vincent Koc 0b0cff180b fix(ci): stabilize Control UI startup gzip ratchet (#130688) 2026-08-27 14:38:05 +08:00
Peter Steinberger 9ccbbf83f2 perf(control-ui): load built-in theme palettes on demand (#130473)
* perf(control-ui): load built-in theme palettes on demand

Every built-in theme's tokens shipped in the startup stylesheet, so the default
path downloaded six palettes it never painted and each new theme taxed everyone.
That is what pushed the startup CSS ceiling from 45 to 47 KiB when Tide, Beacon,
and Phosphor landed.

Moves the twelve non-default palettes into public/themes/<family>.css, one file
per family covering both modes. Claw stays inline because its tokens are the
:root defaults, so the default path loses nothing and gains the bytes back.

The first-paint story this needed: index.html's boot script now links the active
family's palette during head parsing, which makes it render-blocking exactly
like the app stylesheet, so a persisted theme paints its own colours on the
first frame instead of flashing the default. The href is built from the mount
prefix the gateway already stamps on <html>, so it follows a configured Control
UI base path without the script having to know one. theme.ts keeps the link
correct when the theme changes at runtime, reusing the helper the webfont
stylesheets already use.

The nested resolve-theme ternary became a family table in the same script, since
it now picks an asset as well as a data-theme value.

  startup CSS  45.8 -> 42.2 KiB gzip, below the 44.3 KiB it measured before the
               three themes landed; ceiling restored 47 -> 45 KiB
  base.css     64.0 -> 35.8 KiB raw

Adds a regression test that blocks every bundle script and asserts the palette
still applies, so moving this back into the app bundle fails instead of silently
reintroducing the flash. Verified it catches that: with the boot-script link
removed the assertion reports `expected null to be '/themes/tide.css'`.

* fix(control-ui): publish themes after their palettes load

* fix(control-ui): clean up palette completion listeners

* refactor(control-ui): consolidate theme name resolution
2026-08-26 18:56:14 -07:00
Peter Steinberger 08a42a561c feat(control-ui): add the Tide, Beacon, and Phosphor themes (#130232)
* chore(control-ui): vendor JetBrains Mono and Atkinson Hyperlegible Next

Adds the woff2 faces for the Beacon and Phosphor themes plus the stylesheets
that declare them. Both families are SIL Open Font License 1.1 and ship
unmodified with their upstream license text, as the license requires.

Atkinson Hyperlegible Next was drawn by the Braille Institute for low vision:
its letterforms disambiguate the pairs that blur first at low acuity (I/l/1,
O/0, rn/m), which is why it belongs to the accessibility theme specifically.

base.css has always named JetBrains Mono first in --mono, but nothing shipped
it, so code blocks rendered in it only for people who happened to have it
installed locally. Phosphor makes that deterministic.

Latin and latin-ext subsets only, 195 KB. Served from the gateway rather than a
font CDN so font-src 'self' holds, no third-party request happens on load, and
an offline or LAN-only gateway still renders correctly. The url() references are
relative to their stylesheet so they follow a configured Control UI mount.

* feat(control-ui): add the Tide, Beacon, and Phosphor themes

Three built-in themes, each filling a gap the existing four share rather than
adding a fifth variation on them. Every shipped accent today is warm — coral,
crimson, chocolate, clay — and the only cool token anywhere is Claw's teal
secondary.

Tide is the cool one: steel cyan on deep slate, deep teal-blue on cool paper.
It deliberately ships no webfonts, so it costs nothing beyond its tokens.

Beacon is the accessibility one. It targets WCAG AAA (7:1) rather than the AA
4.5:1 floor the other themes hold, for low vision, direct sunlight, projectors,
and poor panels, and sets everything in Atkinson Hyperlegible Next. Reaching AAA
forced one deliberate break from the status-token convention: elsewhere each
--x-subtle is --x at 8%, but a label on a tint of its own hue caps achievable
contrast because the tint pulls the background toward the text. Beacon tints
neutrally and lets the label carry the contrast. Its focus ring is opaque and
3px for the same reason — a translucent ring is the first thing to vanish on a
bad panel. The contrast guardrail now enforces 7:1 for Beacon specifically, so a
later palette edit cannot quietly demote it to an ordinary dark theme.

Phosphor is the terminal one: phosphor green on green-cast black with the whole
surface, chat prose included, in JetBrains Mono. Monospace prose is a character
choice for an operator console, not a readability claim, which is why it is
opt-in.

Every palette was solved against the real guardrail before any CSS was written.
Worst text pairing per theme: Tide 6.12:1 dark / 4.63:1 light, Beacon 10.29:1 /
9.72:1, Phosphor 6.69:1 / 4.76:1. Worst status label on its own tint: Beacon
7.05:1 / 7.02:1, the others above 4.6:1.

Budgets: the startup CSS ceiling moves 45 -> 47 KiB and the JS ratchet baseline
moves 523 B. Every built-in theme's tokens ship in the startup sheet, so each
new theme costs ~0.5 KiB gzip whether or not anyone selects it. Moving per-theme
palettes to lazily linked stylesheets (as theme webfonts already are) would take
this back under the old ceiling, but it needs a first-paint story first: a late
palette flashes default colors where a late font only swaps.

* fix(control-ui): use Beacon's destructive foreground in chat confirmations

The chat confirmation button paints from --danger under --media-foreground, a
theme-invariant white, and each dark palette opts out of that pairing through a
selector list in chat/grouped.css. Beacon defines a deliberately light --danger
(#ffabab, chosen so status labels clear AAA) but was missing from that list, so
its confirm button rendered white on pale pink at 1.80:1 — on the theme whose
entire promise is contrast.

Adds Beacon to the list, which routes it to --destructive over
--destructive-foreground at 11.67:1.

Also adds a guardrail for the bug class, since the selector list has to be
extended by hand for every new dark theme and I have now missed it once. The
test reads membership back out of grouped.css rather than restating it, resolves
each theme's effective (background, color) pair, and holds it to that theme's
floor. It reproduces this defect at 1.80:1 against the pre-fix stylesheet.

Reported by ClawSweeper review on #130232.
2026-08-26 11:47:01 -07:00
Peter Steinberger 0f55efc443 fix(ci): restore Control UI startup CSS budget (#130046)
Amp-Thread-ID: https://ampcode.com/threads/T-01a03d42-8db7-733e-bbff-f3d2a08071d0

Co-authored-by: Amp <amp@ampcode.com>
2026-08-26 02:45:49 -07:00
Peter Steinberger ff73ba5f56 fix(ci): restore Control UI startup budget gate (#130015)
Amp-Thread-ID: https://ampcode.com/threads/T-01a037b5-3918-749f-90bd-5c9ac1dced16

Co-authored-by: Amp <amp@ampcode.com>
2026-08-26 01:48:46 -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
Peter Steinberger 8fe2c1b83c perf(ui): consolidate Control UI boot chunk graph for HTTP/1.1 gateways (#128514)
* perf(ui): consolidate Control UI boot chunk graph for HTTP/1.1 gateways

The Control UI boot flow (app shell + sidebar + chat route) lazily loaded
~124 automatic chunks in one burst after the gateway handshake, which the
gateway's HTTP/1.1 transport serializes into ~24 six-connection round-trips
on high-latency links (Tailscale, remote gateways).

Add a measured boot-module manifest (ui/config/control-ui-boot-modules.json,
regenerated via pnpm ui:boot-manifest:gen) and a control-ui-boot codeSplitting
group that merges exactly that module set into a handful of chunks with
recursive dependency inclusion. Lazy islands (locales, ghostty-web, novnc,
non-default routes) keep their own chunks; stale manifest entries degrade
gracefully back to automatic chunking.

Measured on the built dist with the mocked gateway (chat route, 3 runs):
unique boot JS requests 140 -> 45, raw boot JS 3751 -> 3717 KiB, chat
composer interactive at simulated 50 ms RTT ~1600 ms -> ~575 ms.

Largest-CSS budget rises 45 -> 47 KiB for the merged boot CSS; startup JS
gzip baseline ratchets down (345049 -> 339214 B) as consolidation shrinks
the startup graph.

* chore(ui): refresh boot module manifest after rebase onto current main

* fix(ui): stop the pending lazy shell action replay loop starving boot

When a pending lazy shell action (command palette open, panel toggle)
replayed while the shell was still splash-gated, the dispatched event had
no rendered element to consume it and re-entered requestLazyElement in a
microtask cycle: request -> load -> replay -> dispatch -> request. The
cycle starved tasks (Gateway WebSocket messages included), so the boot
never finished and the recovery e2e froze on the splash screen.

Gate replay on the element actually being rendered: the controller skips
the action after load until the host's render root contains the tag, and
restorePendingLazyAction skips dispatch while a defined element is still
render-gated. The host retries after every completed update, so the replay
fires on the update that first renders the element. Regression test fails
on the pre-fix controller.

* fix(ui): re-anchor the scope-upgrade details popover before opening

wa-popover resolves its `for` target once per property change and never
re-resolves a missing or replaced anchor. The trigger with the shared id
can render after the popover's first update (the header trigger ships with
the lazy chat chunk), leaving the opened popover permanently invisible:
active popup with a native [popover] part stuck at UA display:none because
showPopover() never ran without an anchor. Re-arm the watcher when opening
while the anchor is missing or disconnected.

* test(ui): compare settled layouts in device-scope stability assertions

The 0.5px no-move assertions sampled geometry that later reflowed when the
details surface's first render fetched glyph subsets, reporting sub-pixel
drift the open never caused. Burn in the one-time open per context and
sample the baseline adjacent to the click.

* fix(ui): map the keyboard shortcuts dialog in lazy replay gating

Current main added the keyboard-shortcuts lazy shell event; the replay
gate's exhaustive event-to-element record needs its entry.

* chore(ui): refresh startup budget baseline after rebase onto current main
2026-08-24 03:55:37 -07:00
Peter Steinberger ee7146a282 fix(ui): make startup gzip runtime-independent (#126795)
Host zlib versions compress identical startup assets into different bytes, causing false Linux budget failures. Emit canonical shipped pako gzip sidecars, restore the 512 B ratchet tolerance, and lower the startup baseline from 348351 B to 344531 B.
2026-08-20 12:00:01 -07:00
Pavan Kumar Gondhi 8668aeb969 fix(discord): bind transcript capture to source account [AI] (#118579)
* fix(discord): bind transcript capture to source account

* style(agents): keep transcript tool wiring compact

* fix(transcripts): declare account binding channels

* fix(transcripts): report effective capture account

* fix(transcripts): enforce account lifecycle ownership

* fix(transcripts): preserve cross-surface control

* fix(copilot): preserve transcript channel context

* fix(transcripts): fail closed for legacy channel owners

* fix(transcripts): add trusted legacy recovery

* fix(transcripts): preserve auto-start cleanup ownership

* fix(transcripts): reject untrusted account starts

* fix(transcripts): keep persisted ownership authoritative

* fix(transcripts): harden legacy recovery

* fix(transcripts): preserve agent ownership boundary

* fix(transcripts): scope account binding to source channel

* fix(transcripts): preserve unattributed owner isolation

* fix(transcripts): own configured captures by account

* docs(plugins): clarify transcript auto-start ownership

* test(transcripts): cover account-less recovery

* docs(transcripts): scope legacy recovery by provider

* fix(discord): reuse eligible account ordering for transcripts

* test(discord): use neutral transcript account fixtures

* fix(transcripts): keep accountless recovery local

* fix(discord): resolve transcript accounts by voice capability

* fix(transcripts): bound account resolution failures

* fix(transcripts): bound account tool output

* fix(transcripts): honor unresolved provider accounts

* fix(transcripts): preserve binding when providers are missing

* fix(transcripts): fail closed on unknown binding provenance

* fix(transcripts): qualify account lifecycle capability

* fix(transcripts): normalize provable legacy owners

* fix(transcripts): bind scheduled capture to caller authority

* fix(transcripts): preserve scheduled caller identity tuple

* fix(transcripts): preserve channel-less scheduled authority

* fix(plugin-sdk): publish transcript provider types

* fix(transcripts): use exact lifecycle ownership tokens

* fix(transcripts): preserve local ownerless lifecycle access

* fix(transcripts): allow local configured capture control

* fix(transcripts): preserve scheduled caller channel

* fix(transcripts): retain named-agent legacy recovery

* fix(transcripts): deny unrelated remote channels

* fix(doctor): validate transcript owner inference

* fix(transcripts): restrict legacy remote recovery

* fix(ci): align transcript Doctor checks

* fix(transcripts): require Doctor-owned legacy metadata

* fix(transcripts): reject unowned remote capture starts

* fix(transcripts): reject unbound Discord lifecycle calls

* fix(transcripts): distinguish legacy owner rows

* test(discord): keep unavailable account fixture typed

* fix(transcripts): mark current imports for Doctor

* fix(transcripts): complete account ownership validation

* fix(discord): restore transcript package boundary

* fix(discord): preserve bundled transcript entry boundary

* docs(transcripts): clarify Discord auto-start account

* fix(transcripts): bind account-owned imports

* fix: preserve transcript and cron policy state

* fix(cron): preserve scheduled transcript authority

* fix(discord): keep legacy transcript rows local

* fix(transcripts): narrow account ownership boundary

* fix(transcripts): preserve trusted caller ownership

* fix(discord): enforce transcript source authorization

* fix(ci): bound Control UI gzip build variance

* test(qa): align transcript scenario contracts

* fix(agents): repair rebased caller context

* fix(discord): restore rebased account ownership

* test(discord): restore voice account fixtures

---------

Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
2026-08-15 12:10:43 -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
Vincent Koc 8d3f3377da fix(ci): make Control UI artifact validation consistent (#122086) 2026-08-12 00:11:22 +08:00
Peter Steinberger db73b59c04 refactor: burn wrapper shadowing baseline entries (#122040)
* refactor: burn wrapper shadowing entries

* chore: refresh wrapper shadowing baselines

* test: update secrets runtime state mocks

* fix(ci): absorb Control UI build identity variance
2026-08-11 08:24:03 -07:00
Peter Steinberger 8dace97c2e perf(test): cut workflow guard overhead (#122038)
Co-authored-by: Amp <amp@ampcode.com>
2026-08-11 06:29:08 -07:00
Peter Steinberger fa03d9b913 refactor: consolidate coercion helpers (#121366)
* refactor: consolidate coercion helpers

* fix: remove duplicate coercion imports

* fix: preserve serialized coercion guard

* chore: ratchet coercion helper carve-outs

* fix(test): keep gauntlet subprocess startup lean

* fix: preserve imported session timestamp semantics

* fix: preserve catalog timestamp string semantics

* chore: align plugin SDK surface ratchet

* fix: preserve trajectory and SDK string contracts

* fix(test): preserve QA record assertion semantics

* fix: complete standalone record guard rename

* refactor(cron): use canonical string coercion

* fix(acpx): preserve Pi timestamp parsing

* test(channels): adapt custody test harnesses

* test(telegram): classify media harness as test support

* test(acpx): split timestamp contract coverage

* test(channels): support generated custody contracts

* chore: ban the full coercion helper name set

Extends the declaration guard to all eleven consolidated helper names and
renames the cron schedule-identity readNumber wrapper to readScheduleInteger
so the banned generic name cannot regrow.

* fix(scripts): repair release-validation guard drift and lint cause

Restores the renamed isJsonRecord guard in assertTrustedWorkflowHarness after
main added isRecord call sites in parallel, and attaches the caught YAML error
as the thrown error cause (preserve-caught-error was red on main).

* fix: preserve Claude timestamp string semantics

* fix: preserve persisted timestamp string semantics

* fix: preserve date-first timestamp contracts

* fix(openai): harden delegation failure formatting

* chore: close coercion helper guard gaps

* test(openai): model non-error delegation rejection

* chore: refresh plugin SDK API contract

* fix(tasks): use canonical string field reader

* fix(ai): use canonical provider error field coercion

* fix(browser): migrate native bootstrap coercion

* docs(plugin-sdk): clarify text record export compatibility

* fix(gateway): normalize approval execution identity

* test(outbound): isolate message action poll harness
2026-08-11 00:02:18 -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
Peter Steinberger 5207c4765d feat(ui): float task-suggestion cards top-right with copy-prompt action (#121259)
* feat(ui): float task-suggestion cards top-right with copy-prompt action

- Float a compact tray in .chat-main__conversation with absolute positioning, min(400px, 100% - margins) sizing, elevation, and internal scrolling.
- Remove the icon column, keep dismiss in the header row, and move a compact split button to the bottom-right.
- Add Copy prompt to the menu via shared copyToClipboard, copying the raw prompt while display-only sanitization remains documented inline.

Release note: Suggested-task cards now float as compact cards at the chat top-right and offer Copy prompt in the action menu.

* style(ui): neutral grey task-suggestion card and actions

Drop the accent-tinted card border/background, mute the eyebrow, and use default grey button treatment for the split control per operator direction.

* fix(ui): ungate copy-prompt and surface clipboard outcomes

- Menu now always renders with the client-local Copy prompt while acceptance-mode items stay capability+scope gated (addresses ClawSweeper finding 1).
- Copy success shows a transient Copied label and failures surface through the pane error path (finding 2).
- Task-suggestion pane logic extracted to chat-pane-task-suggestions.ts and the tray adapter moved into the component to keep files under the 700-line cap.

* fix(ui): test task suggestions through tray adapter

* test(ui): cover task-suggestion clipboard failure

* test(ui): split task-suggestion pane coverage

* chore(ui): raise startup JS budget for task-suggestion card feature

QA-profile measurement was 324617-324620 B versus the 324608 B effective budget; this owner-directed PR #121259 bump follows the baseline file’s documented feature-growth precedent.

* chore(ui): raise startup JS ceiling to 318 KiB for task-suggestion cards

The fixed 317 KiB ceiling (324608 B) was within ~20 B of CI-measured startup bytes after accumulated main drift; the task-suggestion card feature crossed it. Ceiling raised with in-file maintainer-approval comment per its own convention; ratchet baseline set to CI-measured 324626 B.

* fix(ui): keep task-suggestion copy available read-only

Remove the stale accept-or-dismiss tray guard now that Copy prompt is a client-local action. Keep Gateway-backed accept and dismiss controls independently gated, with a regression for operators who can only list suggestions.

* test(ui): cover list-only task-suggestion copy

Update the mocked-Gateway E2E to match the repaired read-only behavior: the suggestion remains visible, its mutating Start action is disabled, and Copy prompt stays available.
2026-08-09 18:45:47 -07:00
Peter Steinberger c70aee247e refactor(scripts): migrate JavaScript tools to TypeScript (#121005)
* refactor(scripts): migrate JavaScript tools to TypeScript

* fix(ci): keep changed-scope preflight zero-install

* fix(ci): preserve zero-install script owners

* fix(ci): complete script migration follow-through

* fix(release): keep stable closeout zero-install

* fix(scripts): preserve standalone execution boundaries

* fix(scripts): repair standalone loader boundaries

* fix(scripts): normalize gateway observation ids

* fix(scripts): keep Docker packager standalone

* test(scripts): preserve rebase cleanup helpers

* test(sessions): use tracked temp directory
2026-08-09 07:21:35 -07:00