* 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): coalesce device refreshes during active loads
Nodes and device list loaders dropped every refresh request received while a
previous list RPC was in flight. A successful approve, reject, removal, token
mutation, or Gateway resolved event could therefore be overwritten by the old
pre-mutation response and remain stale until the next poll.
Record one closed pending refresh mode per list owner. When the current request
settles, immediately run the coalesced refresh, preserving whether any queued
caller requested visible error handling. Add order-faithful node and device
regressions that fail when the follow-up list request is dropped.
* test(ui): initialize queued device refresh state
* 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.
The /new page renders its own composer, which never wired the `$skill` mention menu owned by the chat composer, so typing `$` there did nothing while /chat offered completions. Narrow the skill-menu module to a small host contract shared by both composers, drive it from the new-session textarea, and refresh commands.list on the first `$`. Also moves `paneDomId` to the composer DOM helpers to break a types→skill-menu→slash-menu→state import cycle.
* 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.
* feat(control-ui): move the Ask OpenClaw toggle to the sidebar footer
The sidebar bottom now mirrors its top: big agent selector with inline
chevron + small new-session square above, big account identity card with
inline chevron + small lobster toggle below. The account chevron moves
from the row's far right to directly after the name, matching the top
header treatment; the freed edge hosts the toggle. The old top-left
shell-chrome placement is removed along with its four-button width
reservation (which read-scoped clients paid for without seeing the
button). Scope gating, palette entry, and panel wiring are unchanged;
gating regressions moved to the sidebar cases.
* fix(control-ui): keep Ask OpenClaw reachable while the nav is collapsed
Address the ClawSweeper P1: desktop collapse hides the sidebar entirely
(display:none, no rail), which would have removed the toggle's only
persistent surface. The lobster now joins the chrome strip exactly while
the nav is collapsed — the footer stays its home — and the strip's
collapsed width reserves four controls for that state. Regression covers
collapsed-present/expanded-absent/read-scope-absent.
The panel from #125199 rendered raw wire enums in code tags, used
form-grid/field markup whose styles the agents page never loads (bare
unstyled inputs outside the card), referenced non-existent avatar
classes, and put callouts inside the settings group. Rebuild it on the
canonical settings primitives: status rows with dot status and friendly
source/evidence labels, a System/This Agent segmented scope control,
the settings secret input (gains a disabled prop), a danger status row
for errors, and a quiet-inherit + primary-save action row. Retitle to
"GitHub Identity" and move it below Tool Access and Available Right
Now. Add a .settings-account primitive for the 20px round avatar.
Live-tested on an isolated dev gateway: native-credential verify via
the GitHub API, segmented scope switching, and the empty-token error
row. Controller behavior unchanged.
* feat(ui): preserve composer drafts across restarts
Persist draft text and attachment Blobs in browser-local IndexedDB, scoped to Gateway credentials and session or New Session targets. Clean up durable data after successful send or creation, attachment removal, and session deletion.
* fix(ui): preserve text when draft attachments exceed cap
preserve text-only durable fallback for oversized attachments; serialize visible Incognito transition with its storage fence; document retention and disposal.
* test(ui): make draft retirement proof isolate-safe
The shared isolate:false UI suite exposed an order-dependent module mock; exercise and settle the real IndexedDB boundary instead.
* style(ui): format session mutation imports
* fix(ui): keep incognito drafts memory-only
* perf(ui): lazy-load durable draft storage
* fix(ui): sequence durable draft transitions
* fix(ui): restore text-only new session drafts
Programmatic draft restoration must not advance the user-mutation generation; real user input continues through setMessage.
Move navigation transition ownership into the existing handoff module as a behavior-neutral extraction that keeps the submission owner within max-lines.
* fix(ui): restore text-only drafts without import cycles
Complete the text-only restore fix by moving started-session route transitions into a leaf module. This keeps max-lines ownership clean without reintroducing the Madge cycle.
* fix(ui): reconcile attachment handoffs with durable drafts
Restore programmatic attachment handoffs without recording a user mutation. Cover stale navigation state losing to newer durable drafts across two pages and a fresh page.
* fix(ui): start durable attachment writes before teardown
Start each CAS IndexedDB write and retirement immediately so text and attachment transactions register before page teardown. IndexedDB readwrite ordering and draft revisions keep snapshots serialized.
* fix(ui): persist New Session drafts before teardown
Accept the committed predecessor or a known in-flight local write ID atomically so New Session writes can start before teardown without spurious local-lineage conflicts.
Reset cached lineage when authoritative storage is missing and re-snapshot the still-current edit.
* feat(dashboard): add session:progress board tile rendering the live progress card
Advertise the core-owned widget kind via hello controlUiWidgetKinds at operator.read.
Render it inline without an iframe from the session-progress-cards store.
Pin it with dashboard tool widget_put using pluginKind session:progress and optional props.sessionKey.
Follow up the progress-card unification from #125125.
* fix(dashboard): surface session progress load failures
Record protected progress-card read failures in the shared per-session store.
Render an actionable board-tile error with retry instead of indefinite loading.
Cover the rejected-read and successful-retry flow at the widget boundary.
* fix(dashboard): honor progress tile access and activity
Avoid progress-card reads while a retained board is inactive.
Distinguish sharing denial from transient load failures and show the correct remedy.
Qualify cross-session pinning docs and cover activation plus denial behavior.
waitForRequest(method) is satisfied by ANY prior request of that method
and returns the latest match, so a second same-method wait in one page
context can return a stale earlier request on slow runners. Add the
opt-in { after: priorCount } cursor (same shape as PR #125107's helper
change) and pin the 11 genuinely stale-prone waits: chat-composer-
capability-menu, claude-sessions (3), desktop-panel, model-providers (3,
replacing expect.poll count workarounds), session-management.group-
defaults (3). Skipped: cloud-workers-settings (owned by PR #125107),
chat-attachment-read-lifecycle (ring asserted empty), login-gate (waits
follow app-initiated stale-build reloads, which restart the ring).
* feat(control-ui): persistent Ask OpenClaw companion with global toggle
The custodian surface now behaves like the persistent machine-wide agent it
already is on the Gateway: the session id persists in localStorage so a
reopened surface rebinds to the live engine (wizard and approval state
survive close/reopen), the durable transcript is refetched when a surface
opens or the gateway reconnects (idle-gated so active question/wizard cards
are never clobbered), and the panel toggles from anywhere via the shared
panel-toggle contract, a command-palette action, and an admin-gated lobster
chrome button.
One server-side line: the openclaw.chat owner-mismatch rejection now carries
the existing structured session-invalidated details so persisted clients
re-mint their id from a closed code instead of matching error prose.
No gateway events, no protocol schema changes, no polling. Splits
(session-identity/variant modules, session-lifecycle and panel-toggle test
files) keep the touched files under the max-lines ratchet.
* fix(control-ui): coerce custodian toggle detail without a type assertion
The assertion-safety ratchet holds custodian-panel.ts at zero uncommented
assertions; parse the toggle CustomEvent detail through the canonical
record-coerce guard and literal narrowing instead of casting.
* fix(control-ui): delete unused CustodianPanelToggleDetail export
The record-coerce toggle parsing left the exported type without a
production consumer; the deadcode gate rightly flags it. The palette test
keeps a local shape.
* test(control-ui): select the palette custodian item via keyboard
Async session-search results can reflow the palette list mid-click on slow
CI runners, silently dropping the positional click; keyboard selection of
the asserted-active item is atomic against reflow. Also stage the reopen
wait (panel section, then text) for sharper failure localization.
* fix(control-ui): project live wizard state on rejoin and scope-gate the toggles
Address both ClawSweeper P1 findings. The welcome-only rejoin of an
existing session now routes through engine.decorateRejoinReply (the
existing ChatWizardHost projection), so a reconnecting client re-renders
the live wizard/question controls the session still awaits; the stale
welcome question only fills in when no interaction is live. The chrome
button, palette action, and deferred panel loading now use the
scope-aware canCallGatewayMethod gate (operator.admin) that the session
store already used, so advertised-but-read-scoped clients see nothing.
* test(control-ui): fix the cloud-workers e2e flake at both roots
The mocked config.get stayed frozen at the empty initial config while
patch responses advanced, so a config-store reconciliation refetch could
flap the snapshot to empty and saveProfile silently dropped the next
save; the mock now stays consistent before each patch resolution. Also
give waitForRequest an opt-in after-cursor: it is satisfied by any prior
same-method request and returns the latest match, so a second wait could
assert against the stale earlier request; the cloud-workers waits pin it
(15x green locally, previously failing 1-in-3).
* fix(ci): cover rejoin projection in sibling engine mocks; bump startup baseline
The greeting-welcome and session-ownership suites' engine mocks now
export decorateRejoinReply like the handler requires. The Control UI
startup-JS baseline moves 337511 -> 338920 B via the documented update
command: the shell chrome toggle, palette action, and scope-aware gating
are genuine startup surface (~1.4 KiB gzip, within the committed
ceiling).
* fix(control-ui): settle interrupted structured replies and racing turns on rejoin
Address both ClawSweeper reconnect P1s. A submitted question/wizard reply
with an unknown outcome now triggers a full session rejoin on reconnect
instead of being blocked by its own uncertainty flag: the Gateway projects
whether the answer was consumed and which control is live. A restored
persisted id also arms a one-shot rejoin barrier: the welcome-only request
queues behind any in-flight turn on the Gateway's per-session queue, so a
post-response history refresh deterministically surfaces rows a racing
turn persisted after the initial fetch. The open-agent handoff moved to
custodian-navigation (its owner) to keep the store under the size cap.
Live-Gateway proof (isolated state dir, real gpt-5.6-luna turns): video
and screenshots on the PR.
* test(control-ui): reopen via the chrome toggle in the custodian e2e
The palette click-through composition proved timing-flaky on loaded CI
runners in three different ways while adding no coverage: the palette
action's dispatch is pinned by the palette unit test and the event-opens-
panel path by the chrome-toggle step. Keep the gated palette entry
assertion + screenshot; reopen through the chrome path.
* fix(control-ui): keep the agent-handoff path helper module-local
The store now routes through performCustodianAgentHandoff, leaving the
path builder without external callers; the deadcode gate rightly flags
the export.
* fix(control-ui): run the rejoin barrier even when a live control projects
The racing-history refresh happens before the reply/control message is
appended, so skipping it for projected wizard/question rejoins had no
purpose and lost rows a turn persisted while the page was closed mid-
wizard. Regression covers the live-step rejoin reconciling racing rows.
* feat(gateway): carry machine class through session moves
Profile move targets accept an optional machineClass with new-session
parity: validated in the protocol, persisted on the durable move intent
(bare nullable column, lazy same-version ALTER), replayed exactly by
restart recovery, and selectable from the Control UI move dialog.
Moving to the current profile with a different class resizes the
session's worker.
* fix(gateway): project placement facts on chat startup
chat.startup and chat.history built sessionInfo without the placement
projection sessions.list applies, so clients merging that row erased a
live worker placement and its move intent — the Runs on Cloud chip
disappeared after any turn until a full reload.
Reuse the canonical ensureColumn helper for the move table's additive
column instead of a hand-rolled PRAGMA read.
Keep embedded mobile Side chat inside its tabbed panel so the main composer and shared controls stay tappable.
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
* feat(agents): unify agent status into a durable progress_card
Replace the write-only update_plan to-do tool and the fragmented plan
rendering with one durable status artifact per session: progress_card
({plan?, markdown?}, replace-on-write, 8 KiB markdown / 50-step caps).
Cards persist in a lazy-additive session_progress_cards table in the
per-agent DB (no schema-version bump), broadcast progressCard.changed,
and render from the store with exactly one live placement per view
(session rail when visible, else the composer-adjacent bar); transcripts
collapse to one-line receipts, and the sidebar hovercard shows other
sessions' cards inline (markdown + <progress>, DOMPurify allowlist, no
iframes). The three stream-derived plan renderers and their dedup
heuristics are deleted.
Codex runs disable the native plan tool per thread
(tools.update_plan.enabled=false) and receive progress_card via the
dynamic-tool bridge; compaction restore now reinjects the card (steps +
bounded markdown). Card writes still emit the legacy plan stream event so
native apps and channels keep working until their per-platform
migrations. Policy names map update_plan -> progress_card; the shipped
tools.updatePlan=false kill switch is honored.
Net -277 production LOC; -480 test LOC.
* test(agents): regenerate Codex prompt snapshots for update_plan thread-config disable
* chore(protocol): allowlist progressCard.changed for native apps pending card migration
* fix(ci): repair progress card integration checks
* fix(codex): canonicalize native progress cards
* test(gateway): reconcile progress card method order
* test(codex): stabilize native approval fixture