Commit Graph

34 Commits

Author SHA1 Message Date
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 a868258156 fix(control-ui): darken the Absolutely surface ramp (#130239)
Absolutely shipped as the lightest dark theme in the set by a wide margin: its
--bg #262624 sat well above Dash #1a1210, Claw #0e1015, and Knot #080808, which
left the clay accent and the Lora prose with less separation than the palette
was designed for.

Steps the whole ramp down one notch (--bg #262624 -> #1c1c1a, with the card,
elevated, hover, chrome, and border tiers following) and keeps the warm graphite
cast. Text and accent tokens are untouched, so this is purely a surface change.

Every measured pairing improved rather than regressed:
  worst text pair   4.68 -> 5.42:1 (in-browser, --muted on --bg-muted)
  accent on --card  4.47 -> 5.05:1 (AA where it previously was not)
  accent on --bg    4.86 -> 5.47:1
  danger on its own subtle tint over --bg-muted  3.76 -> 4.32:1

The first-paint background in index.html and the Appearance preview chip mirror
--bg by hand, so both move with it; the pre-paint assertion in mount-fallback
covers that they stay in lockstep.
2026-08-26 12:28:46 -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 80ecc7c8b5 feat(control-ui): add the Absolutely theme with self-hosted typography (#129885)
* chore(control-ui): vendor Space Grotesk and Lora webfont subsets

Adds the woff2 faces the Absolutely theme paints in, plus the stylesheet that
declares them. Both families are SIL Open Font License 1.1 and ship unmodified,
with their upstream license text beside the files as the license requires.

Latin and latin-ext subsets only, 184 KB total. Serving these from the gateway
rather than a font CDN keeps font-src 'self' intact, avoids a third-party
request on every load, and works on an offline or LAN-only gateway.

The stylesheet is a static asset rather than a bundled import: it is linked at
runtime only while a theme that declares faces is active, so it stays out of
the startup CSS budget and out of every other theme's load.

* feat(control-ui): add the Absolutely theme with self-hosted typography

Adds a fourth built-in Control UI theme: terracotta clay on warm graphite in
dark mode, burnt clay on ivory in light mode, with its own typography — Space
Grotesk for chrome and Lora for chat prose.

Typography is theme-owned rather than global. A new --font-chat token defaults
to --font-body, so every other theme is byte-for-byte unchanged, and the face
declarations live in a static stylesheet that the theme applier links only
while a theme that declares fonts is active. Themes without declared faces
issue no font request at all.

The faces themselves are vendored in the preceding commit.

Also fixes two defects found while building it:
- Theme cards overflowed their border on long labels: the grid's 1fr label
  track floors at min-content, so a long name pushed the trailing column past
  the card edge. The track is now minmax(0, 1fr) with an ellipsis, which also
  covers imported tweakcn labels. The selected-state checkmark that made the
  overflow visible is gone; the accent border, tint, and ring already carry
  selection, and aria-pressed owns the semantics.
- index.html's first-paint bootstrap only knew claw/knot/dash, so any new theme
  flashed the default palette before the app corrected it. It now resolves the
  new theme and has a matching pre-paint background, with mount-fallback
  coverage for both modes.

Startup JS grows 198 B for the theme wiring, appearance option, locale
strings, and font loader, which fits the existing ratchet tolerance, so no
budget baseline moves. Startup CSS stays within its 45 KiB ceiling, which is
why the faces are a linked static asset rather than a bundled import.

* style(control-ui): format the vendored font stylesheet

The generated @font-face block kept its upstream single-line unicode-range
declarations, which oxfmt wraps. Repo-wide `pnpm format:check` is clean.

* test(control-ui): type the chat runId in the typography e2e

Reuses the chat-flow requireRecord/requireString helpers instead of asserting
the params shape, so runId is a real string rather than an unknown coerced by
String() (oxlint no-base-to-string).

* fix(control-ui): resolve theme fonts against the configured UI mount

The theme font stylesheet and its faces used root-absolute URLs, so a gateway
served under a configured Control UI base path (gateway.controlUi.basePath,
for example /openclaw) fetched them from the wrong origin path. Both requests
404ed there and Absolutely silently fell back to system fonts while its palette
still applied, which reads as "the theme looks a bit off" rather than as a
broken asset.

The link href now goes through inferControlUiPublicAssetPath, the same helper
plugin art and provider icons already use, and the stylesheet's own url()
references are relative to it, so both levels follow the mount without the
loader knowing the base path. Adds an e2e case under /openclaw asserting the
resolved href and that the browser fetches below the mount; it fails on the
previous code with the root-absolute path.

Reported by ClawSweeper review on #129885.

* test(slack): stop racing the aborted read request in the Web API bound test

`bounds dedicated reads without timing out shared clients` asserted the mock
server received exactly two requests, but the read client aborts at 20ms and
the server records on arrival, so whether the aborted request lands first is a
race. It failed on a loaded CI runner with 1 received.

The bound is still asserted directly (read rejects, shared resolves); the count
now only requires the shared client's arrival, and checks its token so the
dedicated client is still proven not to have carried the call.

Unrelated to this PR's theme work; fixed here rather than left red.
2026-08-26 01:06:41 -07:00
Peter Steinberger 9dbc72a544 fix(ui): keep mount recovery working when the service worker is broken (#128167) 2026-08-23 02:42:54 -07:00
Peter Steinberger 41ed3c3460 fix(ui): buttons use the default cursor instead of the link hand (#126153)
* fix(ui): reserve pointer cursors for links

* test: align cursor and docs contracts
2026-08-18 21:40:52 -07:00
Peter Steinberger eadde4a9d0 fix(ui): show recovery when Control UI does not render (#124861)
* fix(ui): retire bootstrap fallback after render

* test(ui): avoid returning from Promise executor
2026-08-16 15:46:15 -07:00
Peter Steinberger bfe1f33ea0 improve(ui): make Control UI feel native on mobile (#122492)
* improve(ui): make Control UI feel native on mobile

* fix(ui): keep coarse-pointer input floor text-scale aware

* fix(ui): let self-sized controls opt out of the touch input floor

* fix(ui): fold per-control coarse-pointer font floors into the shared touch floor
2026-08-12 00:46:35 -07:00
Vyctor H. Brzezowski 32ced3053d fix(ui): scope the Control UI cursor convention to app-like display modes (#121258)
* fix(ui): scope the Control UI cursor convention to app-like display modes

The app-chrome cursor convention from #103357/#103411 was applied
unconditionally, so an ordinary browser tab lost the pointer hand on
buttons, menus, tabs, rails, selects and accordion summaries — the only
hover affordance a page owns. The convention is correct for the installed
window the manifest declares ("display": "standalone"), not for every
window the same bundle is served into.

base.css now owns one policy token, --cursor-action, selected by display
mode: pointer by default, the desktop arrow under standalone, minimal-ui
and window-controls-overlay. A low-specificity rule maps generic
actionable controls onto that token, restoring the affordance on the
surfaces #103411 stripped bare, while every component rule that owns a
semantic cursor (not-allowed, disabled, grab, resize, zoom-in, text,
wait) keeps winning without !important.

The 92 cursor: pointer declarations that had drifted back into ui/src
since July now consume the token instead of hardcoding the hand, so they
stop contradicting the policy in an installed window. Real hyperlinks
keep the pointer in every mode. The pre-boot mount fallback repeats the
policy locally because it must render when the bundle fails to load.

Closes #121242

* test(ui): run the cursor policy browser test in the node-driven project

ui/vitest.config.ts routes Playwright-from-Node .browser.test.ts files to the
unit-node project; without registering the new cursor policy test there, the
in-browser chromium project tried to import it and failed on node:fs/playwright.

* fix(ui): keep the desktop arrow in the native app hosts

The macOS dashboard embeds the Control UI in a plain web view, which
reports `display-mode: browser`, so the display-mode-only policy would
have handed it the browser-tab pointer. It already announces itself with
`openclaw-native-macos`/`-nav`/`-web-chrome` on `<html>`, the same markers
`ui/src/styles/layout.css` matches on, so the policy reads those too.
2026-08-09 22:23:00 -03:00
batmanscode f3b70d5902 fix(ui): include proxy credentials in manifest requests (#111053)
* fix(ui): include credentials in manifest requests

* test(ui): verify authenticated PWA manifest browser requests

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-29 08:10:36 -04:00
Peter Steinberger e279076010 fix(ui): carapace follow-ups — embed font stacks, data-theme-resolved, undefined status tokens (#113724)
* feat(ui): publish carapace embed font stacks to MCP apps

Adopt the carapace embed contract's sandbox-safe font stacks: publish
--font-sans for the first time and switch --font-mono from the host's
JetBrains-led token to the embed mono stack. Both are static, system-
resolvable values byte-identical to carapace candidate/embed.css, since
the sandbox font-src policy silently drops brand faces.

* feat(ui): emit data-theme-resolved alongside theme attributes

Carapace CSS selects on [data-theme-resolved]; keep it in lockstep with
data-theme-mode at boot (index.html IIFE) and on every runtime theme
change (bootstrap publish path). Rename applyStartupPresentation to
applyThemePresentation: it runs on every theme change, not just startup.

* fix(ui): resolve undefined --success/--warning tokens and drop dead hex fallbacks

--success and --warning were never defined (real tokens: --ok/--warn),
so sites with literal fallbacks rendered off-palette one-off colors and
sites without them silently dropped declarations (invalid color-mix in
the chat sidebar warn banner). Map all uses to the semantic tokens
across board, chat sidebar/layout, layout, and components styles, and
strip the stale dead var() hex fallbacks in these global stylesheets.
Code-syntax palette hexes in the file view are documented as deliberate.
2026-07-25 09:32:56 -07:00
Peter Steinberger d2ff17acc3 fix(ui): prevent Zod eval under strict Content Security Policy (#113617)
Co-authored-by: Peter Steinberger <steipete@golden-gate.local>
2026-07-25 04:16:17 -07:00
Peter Steinberger 29e02e84cd fix(ui): prevent dashboard startup flash (#113558) 2026-07-25 00:49:03 -07:00
Alix-007 5c97107c7a fix(ui): bound mount recovery probes (#108163)
Co-authored-by: Peter Steinberger <peter@steipete.me>
2026-07-15 18:51:42 -07:00
Peter Steinberger 861d1d5d53 fix(ui): lock the app root scroll in every host, not just standalone PWAs (#107102)
* fix(ui): lock the app root scroll in every host, not just standalone PWAs

* fix(ui): size mount fallback scroller to the visible viewport
2026-07-13 21:37:37 -07:00
Peter Steinberger 7d87cedcda fix(ui): use default cursor for mount fallback (#103448)
* fix(ui): use default cursor for mount fallback

* test(ui): focus mount fallback cursor coverage
2026-07-10 11:43:32 +01:00
Jason (Json) 17d3c2eb0a feat(ui): redesign chat composer controls (#100461)
* feat(ui): redesign chat composer controls

* fix(ui): preserve scoped chat model catalogs

* fix(ui): honor configured default agent in chat commands

* fix(ui): align composer translations and tests

* test(ui): expect cached chat commands

* fix(ui): inset composer attachment previews

* fix(ui): add attachment preview top padding

* fix(ui): preserve composer layout after main merge

* fix(ui): keep abort available during voice input

* fix(ui): merge duplicate model provider groups

* feat(ui): finalize chat composer redesign

* fix(ui): refine composer layout and scroll indicators

* fix(ui): align composer with chat column

* fix(ui): restore composer max width

* fix(ui): match composer Codex width

* fix(ui): stabilize chat composer controls

* docs: refresh generated map

* fix(ui): discard rejected model drafts

* fix(ui): clear chat composer CI gates

* test(ui): await rejected model rollback

* fix(ui): address chat composer review findings

* fix(ui): preserve default provider ordering

* fix(ui): stage model picker changes until save

* fix(ui): scope model picker drafts to chat context

* fix(ui): align composer drafts and message actions

* fix(ui): preserve reasoning rendering

* feat(ui): refine chat composer interactions

* fix(ui): repair chat composer CI checks

* fix(ui): repair chat composer integration checks

* fix(ui): translate chat composer labels

* fix(ui): restore mobile camera action

* test(ui): target visible attachment control

* test(ui): locate attachment disclosure directly

* fix(ui): preserve composer control ordering

* test(ui): track current composer controls

* fix(ui): honor terminal active-run state

* fix(ui): guard command metadata ownership

* fix(ui): stage agent model defaults

* fix(ui): preserve chat settings and scroll signals

* fix(ui): reconcile merged chat controls

* fix(ui): localize Talk composer options

* test(ui): infer chat control props

* fix(ui): refresh agent model metadata on session switch

* style(ui): format merged chat controls

* test(ui): avoid shadowed composer geometry names
2026-07-06 16:18:36 -06:00
Wynne668 f36f3f30ea fix(ui): recover from stale Control UI bundles (#99111)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-06 05:26:49 +01:00
Shakker d3025b4007 fix: resolve Control UI public assets from base path 2026-05-31 22:55:03 +01:00
zhang-guiping 2fbddce881 fix(cli): avoid catalog validation in agents add (#88314)
Fixes #76284.

Thanks @zhangguiping-xydt.

Co-authored-by: 张贵萍0668001030 <zhang.guiping@xydigit.com>
2026-05-31 19:22:16 +01:00
Val Alexander 42fc84f4b4 fix(control-ui): add static mount fallback
Summary:
- Add a plain HTML Control UI fallback when the module app never mounts.
- Document blank-page recovery guidance and keep the fallback retry-friendly.
- Cover the timeout path with iframe-isolated regression tests.

Verification:
- pnpm exec oxfmt --check --threads=1 ui/index.html ui/src/ui/mount-fallback.test.ts
- pnpm test ui/src/ui/app.talk.test.ts ui/src/ui/mount-fallback.test.ts
- pnpm ui:build
- pnpm check:changed
- GitHub CI for 8ef18e8bca completed without failures.
2026-05-11 08:03:49 -05:00
Kevin d22da87126 fix: contain iOS standalone viewport (#76072)
Contain the Control UI document in iOS Add-to-Home-Screen standalone mode by enabling viewport-fit=cover, applying safe-area-aware body locking, and constraining the app root so inner panes own scrolling.

Thanks @kvncrw.
2026-05-02 13:46:41 +01:00
Eduardo Cruz 21b7ad5805 feat: add Control UI PWA web push support (#44590)
Adds browser PWA manifest and service worker support for the Control UI, plus gateway RPC methods and persisted Web Push subscription handling.

Maintainer verification:
- OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test src/infra/push-web.test.ts src/gateway/server-methods/push.test.ts src/gateway/control-ui.test.ts src/gateway/protocol/push.test.ts
- pnpm check:changed passed before final GitHub update-branch merge commit
- pnpm build

Source head: 0720024368
2026-04-25 05:03:00 -05:00
Val Alexander df72ca1ece UI: add corner radius slider and appearance polish (#49436)
* Refactor CSS styles: replace hardcoded colors with CSS variables for accent colors and optimize spacing rules in layout files.

* Update CSS styles: streamline selectors, enhance hover effects, and adjust focus states for chat components and layout elements.

* Enhance focus styles for chat components: update border colors and box-shadow effects for improved accessibility and visual consistency.

* Implement theme management in UI: add dynamic theme switching based on user settings, update CSS variables for new themes, and enhance security by preventing prototype pollution in form utilities.

* Implement border radius customization in UI: add settings for corner roundness, update CSS styles for sliders, and integrate border radius adjustments across components.

* Remove border radius property from UI settings and related functions to simplify configuration and enhance consistency across components.

* Enhance responsive design in UI: add media queries for mobile layouts, adjust padding and grid structures, and implement bottom navigation for improved usability on smaller screens.

* UI: add corner radius slider to Appearance settings
2026-03-17 23:06:01 -05:00
Val Alexander 6298698008 revert(ui): remove UI portions of mixed commits from main
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-22 13:01:22 -06:00
Val Alexander 3bbbe33a1b UI: gateway dashboard with glassmorphism theme system
Add a full-featured gateway dashboard UI built on Lit web components.

Shell & plumbing:
- App shell with router, controllers, and dependency wiring
- Login gate, i18n keys, and base layout scaffolding

Styles & theming:
- Base styles, chat styles, and responsive layout CSS
- 6-theme glassmorphism system (Obsidian, Aurora, Solar, etc.)
- Glass card, glass panel, and glass input components
- Favicon logo in expanded sidebar header

Views & features:
- Overview with attention cards, event log, quick actions, and log tail
- Chat view with markdown rendering, tool-call collapse, and delete support
- Command palette with fuzzy search
- Agent overview with config display, slash commands, and sidebar filtering
- Session list navigation and agent selector

Privacy & polish:
- Redact toggle with stream-mode default
- Blur host/IP in Connected Instances with reveal toggle
- Sensitive config value masking with count badge
- Card accent borders, hover lift effects, and responsive grid
2026-02-22 05:24:54 -06:00
Peter Steinberger 02576615cb fix: migrate legacy gateway services 2026-01-30 04:01:31 +01:00
Peter Steinberger 9a7160786a refactor: rename to openclaw 2026-01-30 03:16:21 +01:00
Peter Steinberger 6d16a658e5 refactor: rename clawdbot to moltbot with legacy compat 2026-01-27 12:21:02 +00:00
Peter Steinberger 067c20608c fix(ui): landing cleanup (#475) (thanks @rahthakor) 2026-01-09 19:53:32 +01:00
rahthakor 9624d70187 test(ui): add tests for chat modules and update for icon refactor
- Add 21 tests for message-normalizer.ts (normalizeMessage, normalizeRoleForGrouping, isToolResultMessage)
- Add 17 tests for tool-helpers.ts (formatToolOutputForSidebar, getTruncatedPreview)
- Update navigation.test.ts to test iconClassForTab instead of deprecated iconForTab
- Skip focus-mode.browser.test.ts (toggle button moved to settings)
- Skip chat-markdown.browser.test.ts (tool card rendering refactored to sidebar)
- Skip bash-tools.test.ts line offset tests (shell env pollution issue)
2026-01-09 19:47:19 +01:00
Jefferson Nunn 848f36b670 feat(ui): add favicon.ico from Mac app icon (#305) 2026-01-06 10:41:19 -06:00
Peter Steinberger 246adaa119 chore: rename project to clawdbot 2026-01-04 14:38:51 +00:00
Peter Steinberger df0c51a63b Gateway: add browser control UI 2025-12-18 22:41:06 +00:00