Commit Graph

34 Commits

Author SHA1 Message Date
Peter Steinberger 4fac990321 fix(test): run the shared Control UI lane on the cross-file cleanup runner (#126071)
* fix(test): run the shared Control UI lane on the cross-file cleanup runner

ui/vitest.config.ts drives CI's checks-ui job (pnpm --dir ui test). Its
unit project sets isolate:false but never wired
runner: nonIsolatedRunnerPath, so the per-file cleanup in
test/non-isolated-runner.ts — module-graph reset, repo-owned custom
element dropping, DOM body reset, timer and spy restoration — never ran
in the lane CI actually uses. Only the repo-root lane behind
scripts/run-vitest.mjs loaded it.

Files sharing a worker therefore kept the previous file's evaluated
modules, so whichever file imported a component first pinned it to the
real dependency and a later file's vi.mock factory never reached
production code, surfacing as "expected 0 to be 1" in whichever sibling
the size sequencer happened to pack alongside it. This is the class
PR #123512 diagnosed and fixed at the runner; the fix never reached this
lane, so the repo kept absorbing it one uiIsolatedTestFiles entry at a
time.

browser stays exempt (the runner imports node:fs and server modules that
cannot load in browser mode) and unit-node stays exempt (it carries the
Playwright-driven layout tests whose browser lives in module scope, which
per-file module resets churn). The config test asserted runner was
undefined for every project, pinning the broken wiring; it now asserts
the invariant and fails on the pre-fix config.

* fix(agents): stop passing an ignored resolver to instance-bound announce dispatch

check-prod-types is red on main: #126062 threaded resolveGatewayContext
into the announce dispatch call, but that call now goes through
dispatchGatewayLifecycleMethod, whose options type does not carry the
field.

The type checker is right that it does not belong there. That dispatcher
hands work to runtime.dispatchAgent, which resolves context from the
Gateway instance it is bound to and forwards a fixed option allowlist, so
a caller-supplied resolver was already being ignored. Dropping it is
behavior-preserving.

The delivery test asserted the resolver was forwarded, but production now
binds to the instance dispatcher while the test injects a mock, so that
assertion only proved the mock. It now asserts the resolver is
deliberately not forwarded.

Left for the owner of #126062: sendSubagentAnnounceDirectly and its
callers still accept and thread resolveGatewayContext, which is now
vestigial on this path. Deleting that chain or teaching the instance
runtime to honor the resolver is a design call on a just-landed change.
2026-08-18 17:42:00 -07:00
Vyctor H. Brzezowski 292506566b improve(control-ui): continuous corner curvature with a radius scale (#124129)
* improve(control-ui): continuous corner curvature with a radius scale

Route every step of the Control UI radius scale through one knob and, on
engines that can draw a superellipse, widen it by 1.25 while switching the
app's surface families to continuous corner curvature. A corner that keeps
curving into the edge reads as a shorter arc than a circular corner of the
same radius, so the scale gives that visual weight back.

The whole refinement sits behind @supports (corner-shape: superellipse(1.5)),
so engines without it keep today's radii and today's arcs unchanged. The shape
is opt-in per surface: a superellipse flattens the ends of anything fully
rounded, and pills, circles, avatars and status dots are a third of this app's
corners.

Menu item radius is now derived from the menu panel radius minus its padding,
which is the optical relation its comment already documented and which now
holds at any corner scale.

* test(control-ui): assert chat radii against the live corner scale

* fix(ui): scope the corner-curvature radius scale to opted-in surfaces

The @supports block scaled --openclaw-corner-radius-scale at :root, so
every --radius-* consumer inherited the 1.25 multiplier — including
surfaces that never opted into corner-shape (e.g. .run-inspector__panel,
which stayed `round` but drew an inflated 12.5px radius) and the radius
tokens mcp-app-theme.ts exports to embedded MCP apps.

The canonical --radius-sm/md/lg/xl/--radius tokens at :root are back to
their pre-refinement literal values. The scaled radii now live only on
the selector list that also receives corner-shape, redeclaring the
tokens locally so the widened radius stays contained to opted-in
surfaces and their descendants. Menu panels needed their derived
--menu-radius/--menu-item-radius redeclared too (a custom property's
nested var() resolves relative to where it's declared, not where it's
read, so redeclaring only --radius-md wouldn't reach them), and
wa-dropdown/wa-popover/wa-select scale on the host rather than the
::part() panel since slotted menu items inherit from the light-DOM host,
not from the shadow part they render inside.

Adds regression coverage for the excluded consumer and the exported MCP
theme tokens to corner-shape.browser.test.ts.

* fix(ui): stop redeclared corner-curvature radius tokens from leaking to nested descendants

Round 1 fixed the :root-level leak by redeclaring --radius-* as an
inheritable custom property on each opted-in container instead. That
still leaks: any non-opted descendant nested inside an opted surface
inherits the container's redeclared token too. Proof: .settings-segmented
nested inside .settings-group rendered 12.5px round instead of its
canonical 10px, and .agent-chat__search-bar's own --radius-lg-derived
corners doubled up incorrectly depending on ancestor nesting.

Every opted selector now sets border-radius as a direct property computed
from the scale, not an inheritable token — border-radius does not
inherit, so nested non-opted descendants keep reading the untouched
canonical --radius-* from :root. Each selector is specificity-doubled
(`.card.card`) rather than !important, so it reliably outranks its own
bare-class declaration in a later-loaded file without silently clobbering
a genuinely more specific existing override elsewhere in the app (e.g.
`.chat:has(> .agent-chat__search-bar)`, `.chat-task-detail__transcript >
.chat-thread`) — both surfaced as regressions during this fix and are
preserved by design now. The one exception is the generic
wa-dropdown/wa-popover/wa-select ::part() panel selector, which by design
must outrank dozens of concrete per-feature panel classes app-wide; a bare
type selector can never out-specificity a class selector, so that rule
keeps !important.

--menu-item-radius at :root also serializes back to the byte-identical
`var(--radius-sm)` from base f21d34d71d in the unscaled fallback path;
the derived panel-minus-padding formula now lives only in the scaled
wa-dropdown/wa-popover/wa-select host redeclaration where it's needed.

Adds a nested-inheritance regression case to
corner-shape.browser.test.ts (.settings-segmented inside .settings-group)
and a .settings-group entry to prove the container itself still scales.

* fix(ui): pair scale and corner-shape on every corner-curvature selector

ClawSweeper caught two more instances of scale and shape traveling apart:

1. .option-card__choice wasn't in the corner-curvature list at all, despite
   being a real nested member of the .option-card family (a button inside
   .option-card__choices inside .option-card, drawn with the same 14px
   --radius-lg step as .card/.settings-group). It kept its canonical 14px
   round corner even under the scale.
2. .slash-menu-item / .chat-reply-context-menu button / .chat-selection-popup
   button picked up the scaled radius from round 2 but never got
   corner-shape, so their corners grew without curving into a squircle —
   scale without shape, the same incoherence class as (1) in reverse.

Decision on (2): items get corner-shape added (not scale removed). The
panel they sit in is already squircle; an item whose radius grows but stays
circular reads as an unrelated circle poking out of the panel's superellipse,
not a coherent nested row.

A final invariant pass over the full @supports block (every selector that
sets a live border-radius must also set corner-shape, and vice versa) found
one more forgotten member beyond the two reported: .agent-chat__search-bar
scaled its own (bottom-only) radius but never carried corner-shape, same gap
as the menu items. Fixed alongside the reported two. Every remaining
selector in the block already pairs the two; the wa-dropdown/wa-popover/
wa-select host rule is the one correct exception, since it only carries
custom properties for descendants and draws no border of its own.

Test coverage now exercises each surface's real nested DOM instead of a
synthetic flat selector: option-card.ts's actual button-inside-choices-
inside-card structure, chat-composer-slash-menu.ts's item three levels
below the panel, and chat-thread-interactions.ts's plain <button> item
matched via descendant tag selector. Added a corner field to CornerCase so
the harness can probe the correct physical corner (search-bar's bottom-left
is its only non-zero corner) instead of universally reading top-left. All
four new/changed cases are proven to fail on pre-fix base.css with the
exact "radius scaled, shape stuck at round" (or, for option-card__choice,
"not opted in at all") signature before the corresponding fix.
2026-08-15 14:48:56 -03:00
Vyctor H. Brzezowski 9837ad59fa fix(ui): keep the GitHub mark on its label's line when chat wraps (#123355)
* fix(ui): keep the GitHub mark on its label's line when chat wraps

The mark is an in-flow atomic inline, which carries a soft wrap opportunity
after it, so a chat line could end on the octocat and start the next one with
the reference it labels. Painting the mark out of flow and reserving its space
with the anchor's own padding removes that opportunity: nothing inside the
anchor precedes the label any more.

The anchor stays inline, so a long bare URL still fills the line it starts on
instead of moving down whole -- the sibling file-link chip's atomic treatment
could not be reused here.

* test(ui): route GitHub link layout test through Node
2026-08-15 01:26:30 -03:00
Peter Steinberger cef071582e feat: let limited browsers request admin access (#121459)
* feat(gateway): add live device scope upgrades

* feat(ui): add limited-access upgrade flow

* fix(protocol): refresh Swift scope upgrade models

* perf(ui): lazy-load device scope upgrades

* fix(ci): complete scope upgrade generated surfaces

* perf(ui): lazy-load GitHub link hovercards

* fix(ui): keep admin repair guidance focusable

* fix(ui): gate and refresh scope upgrade banner

* refactor(ui): keep gateway client within line budget

* fix(ci): align rebased scope upgrade checks

* fix(ui): resolve scope upgrade in browser tests

* fix(gateway): honor refreshed scope upgrade deadline

* fix(gateway): honor refreshed scope upgrade deadline

* fix(gateway): coalesce scope upgrade waiters

* fix(ui): gate scope upgrade actions

* chore(plugin-sdk): refresh rebased API baseline

* fix(scope-upgrade): return canonical request ids

* fix(ui): preserve gateway event type binding

* fix(protocol): generate scope upgrade result models

* fix(ui): preserve scope upgrade recovery guidance

* chore(plugin-sdk): refresh rebased API baseline

* test(ui): avoid scope upgrade navigation race

* docs(control-ui): clarify scope upgrade approver

* test(gateway): align appended method counts

* chore(plugin-sdk): refresh rebased API baseline

* refactor(ui): keep place picker within line budget

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* chore(plugin-sdk): refresh rebased API baseline

* fix(gateway): preserve scope-upgrade browser origin
2026-08-12 18:32:00 -07:00
Vyctor H. Brzezowski 17e11e4341 fix(ui): hide screen-reader-only status text (#121847) 2026-08-11 13:00:44 -03:00
Vyctor H. Brzezowski 619f3c9ab1 feat(ui): identify project files in Control UI chat (#121775)
Recognized workspace paths in chat now read as their basename behind a
file-type glyph, so a file reference is identifiable before it is read.
Markdown, package manifests, TypeScript and other code, TSX/JSX components,
config/data, shell scripts, and images each get their own mark; anything else
falls back to a plain document. Paths sharing a basename keep the smallest
trailing suffix that tells them apart, and Windows paths keep their own
separator.

Classification wins over authoring syntax: a path written in backticks is a
file link first, so it drops the inline-code chip and renders exactly like a
bare path beside it. Code spans that are not file links keep the chip.

The full path stays addressable: it drives the file panel, the new tooltip,
and the message Copy action (which returns the original Markdown).
Author-written labels in [label](path) links are never rewritten, and text
that is not a recognizable path stays plain prose.

The glyph is painted as a masked ::before like the sibling GitHub mark, so it
stays out of the accessibility tree and out of copied text and follows the
link color in every theme. Extension classification moves to one shared
resolver that the file preview modal now uses too, replacing its own
code/text extension set.
2026-08-11 00:48:43 -03:00
Peter Steinberger 8a5978154f fix(ui): handle keyboard context menu shortcuts (#121628)
* fix(ui): handle keyboard context menu shortcuts

* fix(ci): repair shared validation regressions

* fix(ci): repair setup admission lint

* fix(ci): settle setup admission tests
2026-08-10 12:39:22 -07:00
Shakker 98b89aaa00 test: isolate attachment rebound mock registry (#121577) 2026-08-10 16:10:19 +02: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
Peter Steinberger 7e78de747b refactor(ui): replace duplicated locale bundles with canonical translation memory (#117157)
* refactor(ui): generate locale bundles from canonical translation memory

* fix(ui): keep server-only locale config out of browser test helpers
2026-07-31 19:40:27 -07:00
Peter Steinberger b7ab8aa3da refactor(chat): consolidate browser and terminal history ownership (#115511)
* refactor(chat): consolidate session history lifecycle ownership

* fix(ui): share canonical isolated test registry
2026-07-29 00:00:13 -04:00
Peter Steinberger cca5b14785 feat(ui): path-based session and dashboard URLs (#113883)
* feat(ui): path-based session and dashboard URLs

* docs(ui): document control UI URLs

* fix(ui): finalize session path routing

* feat(ui): anchor session URLs on stable keys

* docs(ui): clarify stable session URL identity

* fix(ui): resolve session prefixes with full prefix and pagination

Query sessions.list with the full supplied prefix instead of an eight-char
truncation, and paginate up to a bounded page count so longer disambiguation
links resolve instead of being reported ambiguous. Zero strict-prefix matches
now fall through to literal-key resolution rather than rendering an empty
ambiguity view.

Also document the ~dot/~dotdot segment escape: peer ids reach session keys
trimmed and lowercased only (src/routing/session-key.ts), so a literal '.' or
'..' segment is reachable and browsers would normalize it away.

* fix(ui): synchronize committed session routes

* test(ui): split native shell host coverage

* fix(ui): thread configured mainKey through session URL builders

Reserved-set disambiguation needs the operator-configured mainKey at runtime, so
thread it from agentsList through every session path builder and the ClickClack
control URL. Unambiguous non-hex single-segment rests now resolve literally while
short-id-shaped rests still fail closed, which restores ClickClack channel
compatibility detection and control-link reconciliation.

Also bound prefix-resolution retries, preserve catalog thread identity, and keep
draft state on ambiguous candidate links.

* fix(ui): repair session URL CI integration

* perf(ui): lazy-load session route resolution

* perf(ui): isolate session prefix resolution

* perf(ui): defer session path parsing

* perf(ui): defer session navigation startup

* fix(ui): preserve first-run and literal session navigation

* fix(ui): satisfy session routing type and export gates

* fix(clickclack): preserve unscoped control link agent

* style(ui): satisfy chat page line limit

* refactor(ui): move chat page helpers to owning modules

* fix(ui): preserve destination session route identity

* fix(ui): preserve agent identity in session routes

* fix(ui): escape dots in literal session path segments

encodeURIComponent leaves periods intact, so a literal key segment like
channel:release.js produced /chat/main/channel/release.js. In-app navigation is
intercepted by the SPA, but a refresh, an external link, or a ClickClack link
would be served as a static asset request and never reach the app.

pathForWorkboardBoard already escapes dots for this reason; mirror it in both the
session URL contract and the ClickClack encoder, route the agent id through the
same segment encoder, and pin the case in both shared vector tables.

* fix(ui): stop bootstrap after teardown race

* refactor(ui): centralize session navigation targets

* fix(ui): consume bootstrap teardown abort

* fix(ui): canonicalize configured main session routes

* fix(ui): preserve distinct session references

* fix(ui): redirect released session query links

* fix(ui): make bootstrap teardown abort-safe
2026-07-26 12:46:04 -04:00
Peter Steinberger 3247a56d15 fix(ui): prevent cross-file mock leakage in Control UI tests (#111554)
* test(ui): stop shared mock registry leakage

* ci: add three-worker UI leakage canary
2026-07-19 15:38:50 -07:00
Dallin Romney 561cf56c53 refactor(workboard): share control ui contract (#102271)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-14 02:33:22 -07:00
Peter Steinberger 7c9b8baa2b refactor(gateway): consolidate TypeScript WebSocket clients (#106113)
* refactor(gateway): unify websocket clients

* fix(gateway): preserve adapter lifecycle behavior

* test(gateway): keep reconnect proof black-box

* chore(gateway): satisfy shared client lint

* refactor(gateway): keep protocol decisions internal

* refactor(gateway): preserve shared reconnect supervision

* test(gateway): use canonical browser barrel

* fix(gateway): resolve browser retry workspace source

* refactor(gateway): tighten shared protocol types
2026-07-13 03:08:21 -07:00
Peter Steinberger 6268eeef15 test: wire Control UI suite into CI, fix its broken tests, drop dead harness surface (#104361)
* test: wire Control UI suite into CI, fix its broken tests, drop dead harness surface

- fix 7 Control UI tests broken on main: module-mock factories are unreliable
  under isolate:false shared workers; use RealtimeTalkSession prototype spies
  and real jsdom storage instead (chat-realtime, chat-pull-requests)
- add gated checks-ui CI job (runUiTests changed-scope output): chromium
  provisioning, lint:ui:no-raw-window-open, pnpm --dir ui test; ~30s on a
  4vcpu runner, runs only when ui-affecting paths change
- add weekly node22-compat workflow exercising the supported lower Node bound
  with the same command set as the dispatch-only ci.yml job
- delete the unreachable channels entry in EXTENSION_TEST_CONFIG_ROUTES and
  the never-populated extensionRoutedChannelTestFiles override machinery
  (born empty in 2ccb5cff22); resolved globs are provably identical
- guard run-vitest.mjs hardcoded path maps with existence tests so renames
  cannot silently drop extended stall watchdogs
- remove unenforced coverage thresholds; test:coverage is informational, docs
  updated (reference/test, help/testing, plugins/sdk-testing)

Closes #104321

* test: pin OPENCLAW_CI_RUN_UI_TESTS in the manifest env expectation

* test(ui): capture the realtime start spy instead of referencing the unbound prototype method

* test(ui): raise ui vitest timeouts for real-browser layout tests on small CI runners
2026-07-11 03:04:11 -07:00
Vincent Koc 3d64efbd3d test(ui): stabilize Control UI suite routing
Keep moved Control UI browser layout tests routed in both UI Vitest configs and stabilize chat suite-order assertions. Verified with Testbox UI proof and changed gate; final PR CI remains the merge gate.
2026-07-04 16:38:36 -07:00
Shakker 65e12328aa feat: refactor the Control UI architecture
Refactor the Control UI around route-owned page lifecycle and state while preserving existing behavior and design.

Prepared head SHA: bd51b6fa76
Co-authored-by: Shakker <165377636+shakkernerd@users.noreply.github.com>
Reviewed-by: @shakkernerd
2026-07-04 23:19:38 +01:00
Vincent Koc 66a8d0a7ec fix(ui): harden chromium test runner 2026-06-17 05:39:06 +02:00
Peter Steinberger 12a56d4d46 docs: document control ui sources 2026-06-04 20:57:09 -04:00
Vincent Koc 71bda851d1 test(ui): harden control ui vitest setup 2026-06-04 07:35:02 -07:00
Val Alexander 529282dcff fix(ui): harden Workboard dialog accessibility
Harden Workboard modal and drawer accessibility.

Summary:
- Add Workboard dialog focus lifecycle handling for initial focus, Tab/Shift+Tab containment, Escape close, and opener restore.
- Mark Workboard background content inert/aria-hidden while modal or drawer dialogs are active.
- Add focused unit and Chromium browser smoke coverage for the audited modal/drawer accessibility requirements.
- Keep UI browser test aliases able to resolve shared workspace packages used by the Workboard view.

Verification:
- node scripts/run-vitest.mjs ui/src/ui/views/workboard.test.ts
- node scripts/run-vitest.mjs ui/src/ui/views/workboard.browser.test.ts
- (cd ui && pnpm exec vitest run --config vitest.config.ts --project browser src/ui/views/workboard.browser.test.ts)
- GitHub checks green at 6557012430
2026-06-03 06:14:40 -05:00
Dallin Romney 632447d66d test(ui): remove stylesheet grep tests (#88847) 2026-05-31 19:05:02 -07:00
Peter Steinberger 77f1359612 refactor: extract media and ACP core packages (#88534)
* refactor: extract media and acp core packages

* refactor: remove relocated media and acp sources

* build: wire new core packages into dependency checks

* test: alias new core packages in vitest

* build: keep media sniffer runtime dependency

* docs: refresh plugin sdk api baseline

* fix: keep normalized proposal queries non-empty

* test: keep channel timer tests isolated

* fix: keep rebased plugin checks green

* fix: preserve sms numeric allowlist entries

* test: harden exec foreground timeout failure

* test: remove duplicate skill workshop assertion

* fix: remove channel config lint suppression

* test: refresh lint suppression allowlist
2026-05-31 11:30:33 +01:00
Peter Steinberger 00d8d7ead0 refactor: extract normalization core package
Extract shared normalization/coercion helpers into private @openclaw/normalization-core workspace package while preserving existing plugin SDK helper subpaths.\n\nAlso keeps direct normalization-core imports internal, wires UI/build/loader resolution, and replaces the slow PR network CodeQL lane with a fast added-line boundary scan while retaining full CodeQL for scheduled/manual runs.\n\nVerification: local moved tests, plugin SDK boundary tests, extension loader tests, agents-support shard, UI build/test, build artifacts, lint, workflow guards, autoreview, and GitHub CI passed on PR head 963d893715.
2026-05-31 01:33:00 +01:00
Alex Knight e2f82d4d30 test: add mocked Control UI E2E tests and playwright for local verification and development (#85278)
* test: add control ui mocked e2e
2026-05-22 19:36:38 +10:00
Peter Steinberger 1ef85c7d4c test: make suites safe without isolation (#78834)
* test: make suites safe without isolation

* fix: narrow auth profile credential types

* test: inject channel module loader factory locally
2026-05-07 08:43:29 +01:00
Peter Steinberger 2ccb5cff22 test: move Vitest configs under test 2026-04-10 13:44:51 +01:00
Peter Steinberger 9d315cdf42 test: default vitest lanes to isolated forks 2026-04-05 10:11:33 +01:00
Peter Steinberger 8b5672bda4 test: align ui vitest configs with thread policy 2026-04-04 06:00:15 +01:00
Peter Steinberger efefa5560d perf: optimize vitest jsdom and isolated lanes 2026-04-04 04:45:01 +01:00
Peter Steinberger e999f2aae3 test: silence lit dev-mode warnings in ui suite 2026-03-28 13:13:02 +00:00
Peter Steinberger 4dd4e36450 build: update deps and fix vitest 4 regressions 2026-03-13 01:02:00 +00:00
Peter Steinberger 3aefe375c1 chore: update deps and add control ui routing tests 2025-12-30 14:30:46 +01:00