* feat(ui): move settings save feedback to sidebar
* test(ui): support stateful config writes in mock gateway
* test(ui): add save indicator to memory sidebar test
* feat(config): require docs decisions for root settings
* feat(ui): link settings pages to guides
* refactor(ui): keep model providers page compact
* fix(ui): style page subtitles and carry base hints in mock dev
* feat(talk): emit realtime models and voices in talk.catalog and mirror create-time readiness
* feat(ui): add curated Talk settings page with catalog-driven pickers
* docs(talk): correct stale claims and add one-page GPT-Live setup path
* fix(ui): refresh Talk catalog on config-hash advance and neutralize GPT-Live badge
* fix(ui): provider-aware Talk selection, atomic provider switch, focus refresh
* fix(ui): resolve Talk provider fallbacks via catalog and make Default a true reset
* fix(ui): provider-compatible transports and race-free Talk catalog loads
* fix(ui): never resolve an unknown explicit Talk provider to the active one
* docs(talk): note Android relay readiness caveat for browser-only models
* fix(ui): keep the relay transport when switching Talk provider to Auto
* fix(ui): align section-ownership test and drop unused export after rebase
* feat(config): give shared channel settings help text
* fix(ui): space the settings tiers and stop repeating help on list items
* feat(ui): link each channel detail to its docs page
* test(ui): mirror production channel hints in the mock dev server
* docs: note the shared channel help inheritance for plugin authors
* fix(config): let a channel suppress shared help with an empty string
* chore(config): regenerate bundled channel metadata for the new WhatsApp hints
* fix(ci): check bundled channel metadata locally
* chore(release): keep changelog release-owned
* chore(release): keep changelog release-owned
* fix(ui): keep shared channel help contract-neutral
* test(ui): make channel settings renderable in the mock dev server
* fix(ui): hide advanced channel settings behind the shared advanced tier
* fix(ui): widen the tier renderer callback to lit's nothing
* fix(ui): keep the advanced collapse control on advanced-only channel forms
* docs: note the shared advanced tier on channel settings
* feat(ui): serve DM pairing fixtures from the mock dev harness
* refactor(ui): align channels DM access requests with the settings design language and drop the channel-health JSON card
* fix(ui): keep channel action labels on one line and surface QR next to actions
* fix(test): drop duplicate prewarm test from attempt-extra shard config
* feat(ui): drag custom sidebar groups between built-in session zones
* test(ui): remove stale split ratio fixture
* perf(ui): raise startup budget baseline for session-section ordering pref
* refactor(gateway): remove dead sessions.observer.ask rpc
* docs: record btw and companion contract split
* fix(gateway): unexport observer model sanitizer after ask removal
* feat(ui): merge creator avatar into sidebar leading slot and move creator filter into Threads menu
The per-row creator chip previously rendered next to the leading state
indicator, so rows with a known creator started their titles at a
different x-offset than rows without one. The avatar now occupies the
single fixed leading slot: unread renders as a corner badge on it,
running as a spinner ring, and open/merged PR state as a colored corner
badge, while attention and pinned icons keep the slot. Child rows keep
their status badges and no longer render owner chips.
The standalone 'Filter by creator' select above the session list is
gone; the Threads funnel menu gains a People radio section with owner
chips, and the funnel trigger shows an accent dot while a creator
filter is active. The empty Threads header stays visible when ownership
chrome is active so the filter can always be cleared. Mock dev fixtures
gain two creator identities so the ownership chrome is demonstrable.
* test(ui): guard parent row spread in child-avatar ownership case
* feat(gateway,ui): ask-the-observer card input over sessions.observer.ask
* refactor(ui): single home for observer run-identity helper after restack
* test(ui): drop duplicated observer hud test after restack
* test(ui): give the observer ask flow its own colocated suite
* refactor(gateway): leaf observer contract and ask module split for ci gates
* refactor(gateway): drop observer contract re-export shims
* feat(ui): session observer HUD, subtitle integration, and settings
* test(ui): observer demo fixtures for the mock control-ui harness
* fix(ui): satisfy lint and deadcode gates for observer surfaces
* test(ui): adopt renamed pull-request summary api after rebase
* fix(ui): clean rebase artifacts in observer test files
Places rows now carry destination icons: monitor for the Gateway and
Mac-family nodes, a phone glyph for phone-family devices, and a server
glyph for cloud workers. The platform prettifier moves from the devices
inventory page into ui/src/lib/platform-label.ts so place-picker
tooltips and Settings/Devices render identical platform names (macOS,
not darwin).
* improve(ui): show real machine identity in the place picker
The place picker labeled the gateway destination with a hardcoded
'Gateway · local' and let duplicate device names collide. The gateway
destination now shows the host's machine name via the existing
system.info method (falling back when unavailable), node rows carry
platform/model/IP facts as tooltips, and duplicate labels — same-named
devices and same-basename recent folders — get a deterministic muted
suffix chosen collision-only (model, then IP, then id; parent folder,
then path, then node facts for recents). IP addresses never render
inline by default. Phone-family nodes get a phone chip icon.
* fix(ui): appease prod-type and no-shadow gates in place identity helpers
* improve(ui): unify new-thread folder and where pickers into one place picker
The new-thread draft asked for one decision (where does this thread run)
through two chips with separate popovers, a dead-end target-list panel
with a disabled back button, and a machine menu that rendered even when
the Gateway was the only choice. One chip now carries the (destination,
folder) pair; its popover stacks a root menu (workspace, recent places
derived from the session list, browse, places, worktree) over the folder
browser, back and Escape pop one level, and the destination axis only
appears when there is a real choice or an existing selection.
* fix(ui): give recentPlaces a production consumer module for the knip gate
Knip's production scan rejects test-only exports; the recents derivation
moves to recent-places.ts, imported by the place picker, and the unit
test follows it.
* fix(ui): drop stale place-picker.test.ts left behind by the rename
The About page commit row now embeds the commit's committer timestamp
(commitAt, advisory like branch/dirty) and renders it as an always-relative
age with the exact localized date-time on hover. The copy button becomes a
compact borderless icon revealed on hover/focus only where hover exists;
touch keeps it visible.
* feat(ui): live agent activity subtitles on running sidebar sessions
* refactor(ui): lazy-load narration controller off the startup chunk, unexport test-only symbols
* chore(ui): raise startup JS budget to 312 KiB for the lazy narration feature
* test(ui): restore real timers after narration controller cases
* feat(ui): custom session icons with agent-drawable SVG and nav-parity pinned rows
* wip: slice-2 interleaved sidebar zone (pre-review)
* fix(ui): trim icon exports, bypass protocol barrel in startup bundle, regen Swift protocol client
* fix(ui): preserve unknown-agent zone entries on writes, prune entries on any unpin
* fix(ui): reset keeps unloaded session slots; enforce SVG byte cap on canonical form
* fix(ui): archiving a pinned session retires its sidebar zone slot
* fix(ui): persist drag-pinned zone slot only after the pin patch lands
* fix(ui): recompute drop insertion against freshest zone order on pin ack
* fix(ui): consume self-drop events before the zone bailout
* docs(ui): note the non-sidebar unpin pruning contract on pruneSidebarSessionEntry
* refactor(ui): extract pure session-tree projection; drop unused zone type export
* feat(ui): manage MCP servers directly on the Settings MCP page
* refactor(ui): mcp config section owned by the MCP settings page only
* chore(ui): drop test-only McpServersCard export for knip
* feat(ui): five-zone sidebar with agent identity card, threads/groups/coding zones, and footer bar
* fix(ui): promote main-session children via the gateway row's literal key
* fix(ui): filter and sort promoted main-session threads; hide empty Coding count
* fix(ui): collapsed zones no longer consume the visible session page budget
* fix(ui): a11y — offline card label and catalog lists outside the Coding list container
* fix(ui): zone headers use pre-pagination counts so paged-out zones stay visible
* chore(ui): document catalog-adoption dedup invariant on promoted threads
* chore(ui): seed five-zone sidebar fixtures in the mock-dev harness
* fix(ui): resolve the canonical global main key for the identity card under global scope
* fix(ui): global scope resolves the global sentinel as canonical main even pre-hello
* fix(ui): drop dead channelDisplayLabel, avoid map-spread, type zone test helper
* feat(ui): redesign Channels page with guided channel setup wizard
Adds wizard.start flow=channels gateway RPC (additive protocol change) that
drives the shared channel-setup wizard (openclaw channels add) over the
session step protocol. Control UI Channels page becomes a card hub with
plugin-art covers, guided per-channel setup modal (WhatsApp QR pairing via
web.login.*, BotFather/Slack/Discord helper links), and a detail overlay
hosting the full schema config form.
* test(ui): cover channel wizard controller; refresh channels view test props
* chore(mock): use non-token-shaped wizard placeholder
* test(gateway): scanner-safe auth local in channels wizard test
* fix(ui): cancel gateway wizard session on channels page disconnect
* fix(ui): adopt browse-all channel pick and guard dirty config before setup
* fix(ui): cancel gateway session created by a stale wizard.start
* fix(ui): adopt multiselect channel picks; drop redundant String()
* fix(ui): track all wizard-adopted channels so WhatsApp QR runs regardless of pick order
* feat(gateway): report configured channels on terminal channel-wizard result
Replaces the Control UI's channel-adoption heuristic with the authoritative
outcome: the channels flow reports its actual selection after config commit,
the wizard session surfaces it as an additive channels field on the terminal
wizard.next result, and the UI keys WhatsApp QR linking off that.
* refactor(ui): align channels hub with the unified settings design language
Hub becomes settings-language rows (44px art tiles, status dots, uppercase
section headings, hairline groups) instead of a card gallery; the detail
overlay hosts the migrated per-channel settings sections with Run setup in
the header. Wizard dialog and tile/cover styles move to spacing tokens.
* chore(i18n): translate channels hub/setup strings; refresh raw-copy baseline
* refactor: satisfy LOC ratchet and dead-export gates
Split mock-dev channel/plugin fixtures into their own modules, move wizard
runner types/defaults to server-methods/wizard.ts, extract the channels page
wizard host and nostr profile HTTP ops, and unexport internal-only types
(incl. the unused config-form SECTION_META barrel re-export).
* chore(i18n): retranslate channels strings on rebased locale bundles
* fix(test): drop redundant String() in channels wizard e2e
* fix: address channel-wizard review findings
- lock wizard cancellation before durable installs/config writes
- report configured channel accounts on the terminal wizard result and
start WhatsApp QR pairing for that account (web.login accountId)
- forward request options through the browser gateway client so wizard
RPC timeouts apply
- render skipped setup as a no-change completion
- keep detail/advanced config reachable for unconfigured channels
- surface the dirty-config warning inside the detail overlay
- adopt the picked channel for wizard title/links in browse-all flows
* fix(ui): local wizard RPC timeout; translate no-change completion strings
Control UI chat now keeps still-running background tasks visible after the agent turn settles: a status row above the composer shows the claw glyph parked nose-down (static, opacity breath only), a live elapsed time with tabular digits so the row never shifts, and a "1 running task"/"N running tasks" link that opens the background-tasks rail. The row hides while the agent is working, while disconnected, and once the last task finishes. Reuses the pane's existing tasks.list snapshot and live task events — no new polling or RPC. Includes a mock-dev-harness running-task fixture and generated locale translations for the two new strings.
Closes#106107
Evidence: focused suites green on Blacksmith Testbox (chat-background-tasks 16 tests, chat-view 185 tests); live mock-gateway verification with screenshots on the PR; autoreview (gpt-5.6-sol, xhigh) clean after fixing its two findings (aria-hidden ticking timer, disconnected suppression).
* feat(ui): collapse completed turn work behind a worked-for rollup
* test(ui): add a completed work turn to the mock chat history
* fix(ui): keep search matches visible instead of folding them into the work rollup
* fix(ui): keep attachment-only final replies outside the work rollup
* chore(ui): translate workRun locale keys and cover renderWorkGroupSummary mock
* fix(ui): satisfy strict index access in the work rollup pass
* feat(tooling): enforce noUncheckedIndexedAccess in the scripts lane
Burns down all 153 scripts-lane errors (bench aggregation, release
checks, i18n inventories, argv indexing) and flips the flag in
tsconfig.scripts.json. Direct-Node-executed release harness scripts use
local narrowing instead of workspace imports, which do not resolve
under plain node execution. Benchmark measured loops untouched.
* fix(scripts): import expect helpers via relative package sources
tsconfig path aliases resolve from cwd under tsx, so release wrapper
scripts running against old release target cwds could not resolve
@openclaw/normalization-core (not a linked root dependency). Relative
package-source imports match the established pattern on the adjacent
lines and are cwd-independent; old-target planning verified directly.
* feat(ui): fold Skills and Skill Workshop into a Plugins hub
One sidebar entry (Plugins) now covers plugins, skills, and skill-workshop
routes via a shared hub tab strip (Installed / Discover / Skills / Workshop).
Routes and URLs are unchanged; /settings/plugins gains ?tab= deep links and
the sidebar highlights Plugins for all hub routes.
Also latches Skill Workshop proposal loading so updated() no longer re-kicks
loads while one is pending or after one fails - the early-return finally
used to spin the page at 100% CPU whenever skills.proposals.list stalled.
* feat(mock): plugin catalog and skill-workshop fixtures for the mock harness
* docs(web): describe the Plugins hub tabs in the Control UI guide
* test(ui): backfill initialTab in plugins route-data fixtures
* fix(ui): review fixes for the plugins hub
- plugins route declares loaderDeps on the ?tab= param so query-only
navigation and back/forward re-run the loader instead of reusing the
cached match with the previous tab
- catalog tab clicks navigate so the URL and history stay in sync with
the documented ?tab=discover deep link (local switch stays instant)
- skill-workshop error banner gains a Try again button so a transient
proposals-list failure is recoverable without remounting the route
* fix(ui): restore the Installed tab on bare plugins URLs in history navigation
* fix(ui): manual-activation hub tablist with cross-route focus hand-off
Arrow keys only move focus now; activation stays on click/Enter so arrowing
can never unmount the strip under the user's focus. Keyboard activation of a
cross-route tab hands focus to the destination strip's active tab, and the
hub-route set uses a Set per lint.
* fix(ui): defer hub-tab focus reclaim until the strip is connected
* fix(ui): skip focus recovery for same-tab hub activation
* docs(css): note the hub-panel flex ownership for the workshop board
* fix(ui): adapt hub test to retired Overview route
* chore(i18n): sync locale bundles for the plugins hub keys
* docs(map): regenerate for the plugins hub section
* feat(gateway): add sessions.files.set workspace file write with hash CAS (#104698)
* feat(ui): CodeMirror file editor panel with CAS save + clickable tool-card paths (#104698)
* fix(gateway): issue the file CAS hash only for strict-UTF-8 text (#104698)
* docs(web): document the editable file detail panel (#104698)
* fix(gateway,ui): round-trip BOM and CRLF bytes through the file editor (#104698)
* fix(ui): keep mixed-line-ending files read-only in the file editor (#104698)
* fix(ui): re-gate editability when a conflict reload returns non-editable content (#104698)
* fix(gateway): reject NUL bytes in sessions.files.set replacement content (#104698)
* fix(gateway,ui): regenerate protocol bindings, fix prod types and lint for file editor (#104698)
Adds a git-backed session diff panel to the Control UI, complementing the existing PR status chips.
New sessions.diff gateway method (operator.read): resolves the session checkout and returns structured per-file diffs (status, renames, +/- counts, capped unified patch) of branch + uncommitted + untracked work against the default-branch merge base. Hardened against git textconv execution and hardlinked out-of-tree content leaks; handles repos before their first commit via the empty-tree base.
Control UI renders a "Changes" panel in the chat detail sidebar with collapsible per-file diffs, hunk-gap markers, stat chips, and untracked/binary badges, gated on gateway method advertisement. Schemas additive (Swift models regenerated), 20 locales translated.
Closes#104182