Pinned sessions inherited the nav zone's muted colour on the session *title*, so a
pinned row's preview line outshone its own name and the same session read dimmer
pinned than unpinned. A title is content in every zone; only glyphs, meta and the
hover fill follow the zone. The subtitle drops to plain --muted, already proven AA
against every surface by theme-contrast.test.ts.
Rows with no preview text reserved a fixed 18px second line anyway, leaving a dead
band or a lone spinner hanging below-right of the title. They now collapse to one
line via a --single-line class plus a CSS variant, so the endcap rides beside the
title. Rows also gained a little vertical air and reclaimed the right-hand gutter
inside the row, leaving the deliberate scrollbar clearance from #124879 intact.
Adds a localStorage-backed "Show message preview" toggle to the session sort menu
(default on). Operator-actionable state is exempt: attention, the queued
concurrency-slot explanation, and critical observer headlines (stuck /
waiting-on-user) always show, because hiding them behind a display preference is
the silent-failure class.
Also registers extensions/codex/src/app-server/run-attempt-tools.test.ts, which
arrived in #126189 with no shard claim and left main red.
* improve(ui): make online sidebar section collapsible
* improve(ui): show online avatars in compact state
* improve(ui): align online section heading
* fix(ui): satisfy sidebar CI gates
* fix(ui): keep online expanded on first visit
Center sidebar session pin and menu actions across the full two-line row while reserving text width on hover and touch. Add browser geometry regression coverage.
Co-authored-by: RoboClaw <309084314+roboclaw-bot@users.noreply.github.com>
* feat(ui): collapse automation sessions in activity feed
* feat(ui): show live run status on activity feed rows
* feat(ui): make run inspector deep-link only
* fix(ui): only link inspect run to a current active run
Addresses ClawSweeper P1: a stale observer digest could deep-link a live
row to an ended run's audit record; trust digest runId only when it is
still in activeRunIds.
* 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
* 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.
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
* 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
* 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.
* feat(gateway): expose command lane diagnostics
* feat(ui): add live debug busyness overlay
* fix(ui): show newest events in debug overlay and update diagnostics call-list tests
* test(ui): add lane and status fixtures to the mocked dashboard
* feat(ui): add System busyness entry to the account menu
* fix(gateway/ui): bound lane diagnostics, append-only descriptor, fail-visible lanes load
Addresses ClawSweeper review findings on #125591: diagnostics.lanes moves to
the append-only tail of the descriptor table, the Control UI lanes request
fails visibly instead of masking errors, and the RPC exports only static
lane snapshots plus a bounded dynamic-session aggregate composed in the new
command-lane-diagnostics module.
* chore(protocol): regenerate Kotlin gateway methods for diagnostics.lanes
* test(gateway/ui): register diagnostics.lanes in the 2026.8 train and mock it in the debug e2e
* feat(ui): add sidebar Online presence section and Activity session feed
Adds a sidebar "Online" section (visible only when at least one other
identity is connected, idle users dimmed) that links into a new
session-feed default view on the existing Activity page: search, time
window, and per-person facets over day-grouped sessions, with a
per-person identity header (devices, viewing-now) when one person is
selected. Removes the dead footer viewer-facepile roster that the
sidebar-zone refactor (#112936) left unmounted.
Client-only; no gateway/protocol changes.
* test(ui): make activity capture date-boundary safe
* fix(ui): stop the model picker from nagging about refresh failures
A failed background catalog refresh rendered a "Couldn't refresh models"
banner plus a Retry button on top of a complete, working model list, and
replaced the composer trigger's model name with that error text. The
picker already keeps the last-known catalog and re-requests it on every
open, so the operator was being asked to press a button for something the
UI does by itself.
The error stays recorded on the host — it is what drops the stale
availability gate so the composer remains usable — but it is no longer
surfaced while there are models to show. Only a genuinely empty catalog
still says "Models unavailable". The new-session picker gains the chat
picker's open-triggers-revalidate behavior so re-opening it is the retry
there too, instead of dead-ending until a page reload.
Two adjacent simplifications in the same surface:
- Model rows reserve their provider-icon slot as an invisible stem, so
names line up with the provider heading label (same 34px stem grouped
and filtered, nothing shifts while typing).
- The "Using agent default" footer is gone: the default row already
carries a DEFAULT badge and the checkmark. Typing "default" in the
picker search now matches the default model instead.
Proof: scripts/capture-model-picker-proof.mts captures the open picker
against a mocked gateway; alignment delta 0px (was -24px), search
"default" matches the default row (was nothing), and a failed models.list
leaves no catalog-state element with all rows intact.
* fix(ci): register the model-picker proof script and await its picker revalidation
The unused-file scan needs every scripts/ entry point referenced, like the
sibling ui:proof:* recipes. The catalog-reconnect assertion also has to wait
for the picker's own metadata request instead of reading the log the moment
the rows render.
* test(ui): split model-catalog scenarios out of the new-session e2e file
The catalog-reconnect file hit the 1000-line cap. Its model-catalog metadata
failure/recovery pair is a separate surface from CLI-agent targets, terminal
start, and draft reconnect, so it moves to its own file rather than earning a
max-lines suppression.
* test(audit): give the pinned-reader contract test a realistic timeout
It adds a pinned-SHA git worktree and cold-compiles the audit and state
modules under tsx, which takes minutes on a contended runner. The 120s
default made it fail by construction; it timed out on an unrelated PR shard
while passing locally at ~55s.
* improve(ui): flatten transcript tool activity
* improve(ui): complete tool activity redesign
* improve(ui): normalize activity row typography
* improve(ui): unify activity typography contract
* improve(ui): standardize all activity row text
* improve(ui): increase activity text size
* improve(ui): underline activity filenames
* improve(ui): tighten activity row spacing
* improve(ui): standardize activity chevrons
* improve(ui): reveal child chevrons on interaction
* improve(ui): retain active child chevrons
* improve(ui): space expanded child tools
* improve(ui): remove idle activity fade and gap
* fix(ui): preserve tool activity contracts
* fix(ui): simplify activity basename lookup
* fix(ui): preserve canonical tool row actions
* fix(ui): record tool outcomes and repair transcript row regressions
Neutral collapsed rows are the design, but this branch left failures with
nowhere to land and shipped chrome whose styles it had already deleted.
- Expanded standalone tool messages close with the outcome line, so an inline
rows shape still reports the failure instead of dropping it.
- Progress-card receipts drop the badge and the spinner: this branch removed
both styles, leaving a 0x0 invisible running indicator, and the receipt
label already names the state.
- Single delete patches are non-navigable; their workspace target is gone, so
the file action could only ever fail to load.
- Flush command padding derives from the action-button size instead of a stale
42px constant, and the running text wave covers command rows again.
- Activity lane returns to the tool-shell column width the rule claims.
- Drop CSS nothing references: tool-card tabs, empty, plain-output, activity
scroll masks, secondary command text.
- renderToolDataBlock takes an optional label instead of comparing copy.
- Split the oversized tool-card test file and refresh stale row assertions.
* fix(ui): fold the tool action size into the card header rule
Stylelint no-duplicate-selectors rejects the separate .chat-tool-card__header
block; the custom property belongs in the existing rule.
---------
Co-authored-by: RoboClaw <309084314+roboclaw-bot@users.noreply.github.com>
* fix(ui): stop composer mic hover shift and stray red hover
The device-picker chevron collapsed to width 0 while idle and grew to
20px on hover, shoving the right-aligned mic button 20px left under
the cursor on every mouse-over. Detach the picker into its own
fixed-width ghost button beside the mic instead of morphing the mic's
shape into a split pill; hover only fades the picker's opacity, so the
reserved layout never shifts.
Also fixes the mic showing the send button's loud accent-red hover in
dark/openknot themes: those themes overrode .chat-send-btn:hover
directly, which outranked every variant's own hover rule (including
the intentional neutral mic hover and the stop button's danger hover).
Theme overrides now set a --chat-send-hover-background variable that
each variant's hover rule can still take precedence over.
* chore(scripts): register composer mic hover proof script
Missing package.json entry made the deadcode full-tree scan flag the
new capture script as an unused file, same as the existing
ui:proof:workboard sibling.
* fix(ui): show session hovercards for every sidebar row beside the rail
Sidebar session rows were excluded from session-link hovercards, so only
the Home (main) row showed a card, and it overlaid the sidebar itself.
Hovercards now open for all sidebar session rows and position to the
right of the sidebar so navigation stays visible.
* fix(ui): yield sidebar previews to active progress hovercards
Sidebar rows already open a progress hovercard when the session has an
active progress card; the new preview card stacked on top of it. The
preview now probes the shared progress-card store (deduped with the
progress provider's own load) and opens only when the row has none.
* feat(ui): show worktrees only for Git group folders
* feat(ui): enrich group environment picker
* fix(ui): preserve worktree defaults on probe errors
* fix(ui): reject stale group repository probes
* test(ui): type repository inspection helper
Restore the block-axis padding reset lost in #125368 so Web Awesome tab labels remain vertically legible while retaining logical inline spacing. Add LTR and RTL browser geometry coverage for the visible label boundary.