* test(ui): seed mock activity sessions across owners
* feat(ui): consolidate activity filters into toolbar
Also restores the shared .input styling contract for standalone form controls.
* test(ui): update activity feed e2e for toolbar people popover
The retryable-send and retryable-history tests asserted the resend side
effect immediately after `vi.advanceTimersByTimeAsync(100)`. Sinon's async
tick yields exactly one real macrotask after firing the last timer, and the
retry chain (`void scheduleStoredChatOutboxDrain` -> `chat.history` ->
`chat.send`) consumes exactly that one boundary, so the assertions had zero
slack and flaked under CI contention.
Wait for the outcome with `waitForFast` instead, and fold the outbox-empty
assertion into the wait since retirement lands after the send call.
* test(control-ui): add --operator-scopes flag to the mock dev server
* feat(control-ui): simplified settings experience for non-admin operators
Non-admin browsers previously saw every settings page, many of which
dead-ended or rendered enabled controls whose RPCs fail with
'missing scope: operator.admin'.
- config.schema drops from operator.admin to operator.read: the schema is a
static document describing options whose values are already readable via
read-scoped config.get; admin-only schema only broke read-only settings
rendering (Automation/Infrastructure/AI Agents/Communications showed
'Schema unavailable. Use Raw.').
- Settings sidebar and settings search hide admin-only routes (custodian,
labs, updates, automation, infrastructure, mcp, security, secrets,
cloud-workers, communications, ai-agents, model-setup) for non-admin
viewers; legacy gateways without advertised scopes keep the full UI.
- Channels, Devices, Worktrees, Memory Import, Profile gate their mutation
controls on actual scopes with 'Browsing only…' notices instead of
enabled-but-failing buttons; Devices no longer fires device.pair.list /
exec.approvals.get without the scopes to call them (kills the two red
error callouts on page load).
- Scope-upgrade banner: dismissing it in the guidance phase (no in-app
upgrade path) now hides it fully instead of leaving a permanent chip.
- Config write coordinator surfaces scope refusals as a visible
admin-required error instead of silently resolving false.
* test(control-ui): advertise config.schema in the mock dev gateway
ensureSchemaLoaded now checks method advertisement + scope before loading
the schema; the mock harness must advertise config.schema like a real
gateway does or schema-driven settings pages render empty in the mock.
* fix(control-ui): close the worktree create draft on scope downgrade
* perf(doctor): isolate memory health artifact
Doctor lint loaded the broad Memory Core API barrel only to register health checks and read isolated check IDs. That synchronously pulled the full memory public graph into the first lint run, consuming most of the 120-second test budget.
Load a dedicated doctor-health public artifact instead and verify it is packaged. The bisect boundary was 9de3ca5fc9 (#125571); because that commit only adds upgrade-test assets, it exposed a pre-existing runner-sensitive cost rather than introducing the expensive import path.
* test(control-ui): restore device lifecycle test boundary
* perf(control-ui): lazy-load settings sidebar
* fix(ui): recheck access after confirmations
* fix(control-ui): gate presence-driven device reloads on pairing access
The presence connectivity-change path still called device.pair.list without
operator.pairing, the same invariant the pair-event and poller paths already
guard; a limited browser got a doomed RPC on every connectivity change.
* fix(control-ui): fail open on schema loads for legacy scope-less gateways
canCallGatewayMethod hardened to strict advertisement+scope checks (#125478),
which made the new ensureSchemaLoaded gate silently skip config.schema for
legacy hellos without advertised scopes or a method list. Schema loads now
skip only on a definitive denial (method advertised absent, or advertised
scopes without operator.read), reusing the fail-open hasOperatorReadAccess
semantics the rest of the non-admin UI uses; regression test pins the
legacy snapshot path.
* test(control-ui): split schema-access coverage into its own file
runtime-config-capability.test.ts crossed the max-lines cap; the legacy
fail-open regression and its denial counterpart move to a colocated
schema-access test file.
* fix(scripts): keep mapped Vitest lanes at their measured no-output floor
The codex extension shard legitimately works in silence beyond 300s under
the default reporter (measured 61s import + 293s testing at ~95% CPU); the
CI-wide OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS=300000 env override shrank the
lane below that and the watchdog killed healthy runs, flipping with
incidental flake output (#125825). Per-config entries in
VITEST_CONFIG_NO_OUTPUT_TIMEOUT_MS now act as measured silence floors: a
global env value may widen a mapped lane's window but no longer shrinks it;
unmapped configs and the explicit '0' disable keep env verbatim. Adds the
codex extension lane to the map at the extra-long tier (same class as the
discord entry from #123025).
* feat(gateway): proxy channel conversation avatars
* feat(discord): capture conversation avatars
* feat(slack): capture DM sender avatars
* test(discord): bind guild avatar mock
* feat(ui): render channel conversation avatars
* fix(ui): align sidebar owner fixtures
* fix(gateway): version channel-avatar routes by media revision
A stable per-session URL let AuthenticatedAvatarRouteLoader's blob and
sticky-404 caches pin a mounted row to a stale or blank avatar after the
backing media changed. Append an opaque digest of the media reference so
replacement and 404-recovery change the route identity.
* test(ui): align sidebar owner facet
* fix(ui): keep owner chip until channel avatar loads
A session with a channelAvatarUrl suppressed its owner chip even while the
blob was loading, auth was not ready, or the route 404ed, leaving an empty
lead slot. The chip now rides as fallback content inside the avatar element
and yields only to a usable image. Covers 404 and auth-pending states;
avatar rows keep renderedOwnerId unset so an owner-viewer stays visible in
the facepile.
* perf(ui): keep channel avatar fallback within budget
* perf(ui): lazy-load the channel avatar element
The avatar element and its authenticated blob loader rode the startup
bundle through session-leading-indicator, pushing startup JS 51 B over the
CI gzip budget. Channel avatars are not startup-critical: register the
element on the first avatar row; the owner-chip fallback covers the
one-time upgrade window. Startup JS returns ~1 KiB under the ceiling.
* build(ui): raise startup baseline for channel avatars
CI-measured startup JS is 344379 B against a 343289 B baseline (+1090 B).
The avatar element and blob loader are code-split out of startup (previous
commit); the residual is the sidebar lead-slot render branch and row
plumbing, which cannot be deferred. Baseline updated via
check-control-ui-performance --update-baseline with CI bytes per the
script's contract; well inside the 4096 B ratchet step and 358400 B
ceiling.
Move GitHub markdown label formatting back behind the markdown loading boundary and exercise markdown-table interactions through the real delegated click owner. This restores startup bundle margin without changing the committed budget baseline and prevents the isolated table test from depending on leaked listeners.
* fix(ui): refresh chat checkout side panels
Retire checkout-owned PR, branch, Files, and Review state when a
structural session mutation or logical connection epoch replaces the
checkout. Surface an actionable reload path for transient sidebar chunk
failures while keeping chat usable.
Release note: Control UI checkout side panels now refresh reliably after
session replacement and reconnect, and failed sidebar chunks can recover
without leaving blank content.
Refs #125767
* fix(ui): keep structural refresh within startup budget
Record the structural-session classification once at the canonical event
parser and carry that fact to PR, session, and chat consumers. This removes
duplicate payload classification and keeps the checkout refresh repair within
the Control UI startup bundle budget.
Refs #125767
* test(ui): align worker refresh with chunk recovery
Advance the mock Gateway build identity with replacement assets so stale
sidebar chunks can recover without creating an artificial version-skew loop.
Assert that the catalog-owned terminal intent survives both refresh owners and
opens exactly once.
Refs #125767
* fix(ui): retire checkout summaries at consumers
Keep structural mutation policy in the PR snapshot store, match every watched
session alias, and retire derived summaries through fenced chat/sidebar
consumers. Preserve canonical summary identity and avoid adding structural
event policy to the startup session parser.
This also restores the Control UI startup bundle below its enforced budget on
the latest main base.
Refs #125767
* test(ui): accept either update recovery owner
Allow stale-chunk document recovery or service-worker activation to win the
pre-activation race, while still requiring the catalog terminal intent to
settle exactly once after both recovery owners finish.
Refs #125767
* refactor(ui): keep workspace agent resolver private
The split workspace state owner consumes the pane agent resolver internally;
do not retain a stale export after adapting the checkout-ownership repair.
Refs #125767
browser no longer chooses or persists run targets; steer sends ride the generic durable outbox with queueMode steer and no expectedRunId/expectedLeafEntryId; deletes steer-lifecycle.ts, steered-chip.ts, kind:"steered", steerTargetRunId, sendState:"steering", target-bound retry, uniqueness failure rows; persisted outbox rows normalize at load; net -625 production LOC.
Show bounded predecessor diffs by default so operators can understand applied Skill Workshop revisions without manual comparison. Keep the full revision body one click away and preserve oldest-revision fallback behavior.
* feat(ui): promote session controls to toolbar
* test(ui): add session toolbar proof capture script
* chore: register session toolbar proof script
* test(ui): target ungrouped session drop zone
* fix(ui): trim session toolbar startup cost
* docs(ui): point new-session guide at the Sessions toolbar
Also name the accepted tradeoff for the inert ungrouped-collapse preference at the partition site (ClawSweeper P1/P3 response).
* test(ui): deflake new-session workspace-memory e2e
Menu-geometry assertions now measure relative to the picker anchor in one atomic evaluate (immune to unrelated page settles, still catches focus-induced moves). The post-reload refill raced the composer's async draft restore, which appended the stored draft to the typed text; waiting for the restored draft asserts the documented persistence instead. Failed CI shard checks-ui-e2e 5/12 on runs 32122284238 attempts 1-2.
* fix(ui): use canonical session owner filter state
* chore(ui): keep session toolbar proof artifacts ignored
* fix(ui): keep virtual transcript geometry atomic
Keep the connected transcript DOM paired with its matching virtual row model while MCP app teardown is pending, preventing late resize observations from corrupting replacement row sizes. Adds focused teardown, append, range, focus, and overlap regressions.\n\nCloses #125836
* refactor(ui): extract transcript range projection
* feat: credit linked session participants as co-authors
Authenticated profiles can link GitHub and receive automatic co-author credit in shared coding sessions.
* style: format rebased co-author registries
* fix: mark profile schema DDL boundary
The Applied view rendered every applied proposal as an independent recency-sorted row, so one skill's revision history looked like duplicate skills. Applied now shows one row per live skill with a revision-count badge and grouped tab count; the detail pane gains a lazily loaded History section with operations from the ledger's recorded kind. Pending/rejected/quarantined/stale keep one row per proposal.
Fixes#125632
Two landed-PR races left main red (run 32131816085):
- #125692's rewritten markdown-tables dialog test drives every interaction
through direct handler calls except the reopen, which used a raw
expand.click() that only worked via a leaked cross-file document listener
in the shared lane; #125766 moved the file to the isolated lane where no
such listener exists. Reopen now uses the file's own handler idiom.
- Accumulated startup-JS creep from landed features put main 50 B over its
gzip budget (344395 B vs 343289 B baseline + 1056 B tolerance). Baseline
updated to main's CI-measured bytes via the script's --update-baseline
contract.
* fix(tts): normalize streamed speech and report fallbacks
Streamed Discord voice now strips Markdown and raw link destinations like buffered synthesis.
Record the first failed streaming provider and reason code before successful file fallback.
* test(ui): stabilize reconnect retry timing
Drive the retry deterministically by advancing the exact 100 ms fake timer while preserving the ordering and outbox proof. Repairs CI run 32110130614 job 95627654678.
* fix(discord): ignore expected TTS stream skips
Address the ClawSweeper finding by retaining warning metadata only for actual failed attempts. Expected unsupported-for-streaming skips stay silent while real streaming failures still warn after successful file fallback.
* test(ui): settle interaction state before assertions
Repairs CI run 32117859632 by invoking the markdown table handler directly with a targeted event and waiting for durable IndexedDB composer state before navigation or page close.
* test: remove incidental UI and process identities
CI run 32120793258 exposed two owner-specific test assumptions.
Preserve the child-session hover visibility and pointer assertions, then focus and activate its menu with Enter so an unrelated delayed hovercard cannot intercept the pointer.
Assert the exact owned descendant PID is dead when QA cleanup completes instead of checking a globally reusable PGID.
* test(ui): stabilize connected send retry timing
CI run 32125741781 exposed the wall-clock race in the connected retry test.
* refactor(ui): extract chat pane rail state
chat-pane-render.ts sits at 702/700 effective max-lines on main, latently
failing the lint lane for any change that pulls it into scope. Extract the
rail/sidebar-slot model building into chat-pane-rails.ts (behavior-neutral)
to heal it without a suppression.
* test(ui): isolate markdown-tables from shared module graph
The shared UI runner reuses its module graph across jsdom registries; when
another worker file evaluates markdown-tables.ts unmocked first, this file's
clipboard vi.mock can bind to a stale instance and the copy spy records zero
calls (checks-ui failures on PR #125668). The test belongs on the canonical
singleton-sensitive list, which exists for exactly this class.
* fix(ui): align session owner filtering terminology
Owner filters now use owner terminology consistently across the Gateway, Control UI, and generated native protocol clients. The old terms were prerelease-only, so no migration or compatibility aliases are needed.
* fix(gateway): preserve creator provenance filtering
Keep creatorId scoped to immutable createdActor provenance while ownerId queries the current projected assignable owner. This preserves independent creator and responsibility filtering after reassignment.
* test(ui): wait for remembered draft before reload
CI exposed an IndexedDB draft-persistence race: the test reloaded before the write completed and then re-filled the message while restoration arrived. Wait on the canonical durable-draft helper before reload so the reload cannot race the write.
* fix(ui): keep session menus clear of hovercards
Horizontal sidebar previews were positioned from the inner link rect instead of the complete session row, allowing the hovercard to overlap trailing actions.
Anchor preview placement to the full row while preserving the link trigger and ownership flow so the session menu remains clickable.
* perf(ui): keep hovercard fix within startup budget
Use the sidebar direct-row invariant to anchor the hovercard outside trailing actions without a selector lookup. This preserves the fix without increasing the startup budget.
* fix(qa): preserve Matrix CLI timeout precedence
Keep the primary timeout operator-visible when descendant settlement also reports secondary cleanup trouble. Preserve the complete AggregateError as the public wrapper cause so settlement evidence remains inspectable.
* test(ui): isolate Markdown table clipboard boundary
With isolate:false, the shared module registry can retain clipboard imports before this test installs its module mock, leaving the spy disconnected from the production call.
Stub navigator.clipboard.writeText instead so the test exercises the real copyToClipboard boundary regardless of module load order, then restore the original descriptor.
* fix: show sessions waiting for concurrency slots
* test: align queued session integration fixtures
* test: distinguish queued and reactivated followups
* fix: preserve queued state in workboard and android
* fix: project queued status through chat history
* test(ui): keep queued sidebar case under line cap
* improve(ui): standardize fenced code collapse and wrapping
Every Markdown code fence now shares one behavior instead of the JSON-only
40-line disclosure: blocks longer than seven lines show a seven-line preview
with the exact hidden-line count, horizontal overflow stays inside the fence,
and a reversible wrap control appears only once a line measurably overflows.
User fences keep the bubble's own typography and carry no code chrome.
The collapsible markup is emitted only for hosts that own the code-block
lifecycle (codeBlockInteraction: interactive), and that lifecycle collapsed to
one click owner plus one initializer so a host cannot wire half of it. The chat
thread, detail panel, and caretaker surface all run it; the skills preview gets
the click owner its copy button had never been given. The scan is deferred and
coalesced because a Lit element ref commits before that render's children, and
detached ResizeObserver targets are released before each pass.
Also adds the interaction mode to the markdown cache key so a static host cannot
serve interactive markup from a sibling render, and drops the stylesheet rules
and selector guards the removed json-collapse markup owned.
* fix(ui): satisfy fenced code checks
* feat(ui): show session info in the sidebar hovercard
The sidebar session hovercard now always opens and shows owner, created and updated times, PR chips with CI state and diff stats, plus a branch and changed-files fallback with “No PR yet”. The progress card renders below when present, and the gateway exposes changedFiles on session PR and branch snapshots.
* fix(ui): scope session hovercards to sidebar rows
Keep Markdown session links owned by their dedicated preview hovercard by limiting both lazy registration and runtime activation to sidebar session rows. Add browser coverage that fails when the competing sidebar hovercard steals the link trigger.
* perf(ui): keep session hovercard data out of startup
Reuse the sidebar’s existing session projection from the lazy hovercard runtime instead of publishing a separate startup-loaded registry. This removes the registry lifecycle and its implementation-only tests while keeping the browser ownership regression and restoring the Control UI startup bundle below budget.