Solid
Write, refactor, review, or test Solid 2 UI in OpenClaw (Control UI, plugin views, Canvas hosts),…
by openclaw · MIT
The skill
Write, refactor, review, or test Solid 2 UI in OpenClaw (Control UI, plugin views, Canvas hosts), including state ownership, native custom elements, overlays, testing, performance, and lessons learned.
Solid 2 in OpenClaw
The Control UI and plugin views (extensions/x, extensions/workboard) render with Solid 2 (solid-js, @solidjs/web, @solidjs/signals, compiled by @solidjs/vite-plugin/@solidjs/compiler). Lit and Web Awesome are removed from these surfaces; new code is Solid only. The Canvas hosts also use Solid, but the sandboxed upstream A2UI v0.8 catalog retains its separate @a2ui/lit dependency.
Read ui/AGENTS.md first; its state-ownership rules apply to Solid unchanged. Use the exact installed Solid pins from the owning package.json: the runtime and compiler are 2.0.0-rc.14, and @solidjs/vite-plugin is 3.0.0-next.49. solid-js and @solidjs/* are exempt from the repository's seven-day release-age gate (Peter, 2026-10-11); third-party Solid ecosystem packages remain subject to it.
Rules that keep the UI correct
Before your first edit, read the installed ui/node_modules/solid-js/CHEATSHEET.md, especially its "What changed from 1.x" list. It is the rc.14 API contract, written as a guard against React and Solid 1 priors, the two dominant bug sources. For new code or a Solid audit, then read idioms and lessons; to refactor or review existing code, also use the refactor checklist. Check solid-js/types/client/flow.d.ts and the installed @solidjs/signals / @solidjs/web types when choosing an API. rc.14 has no createAsync: asynchronous values are ordinary computations returning promises or async iterables.
- Domain owners stay plain TypeScript. The Gateway store, capabilities,
lib/sessions/*, caches, persistence, and mutation authority keep synchronous mutate-then-read semantics. Solid 2 writes become visible only after a microtask flush, so authoritative state never lives in a signal. → state and ownership - Components consume projections from
ui/src/lib/reactive/(one per owner and scope, never a second store) anduseApplication()for the application context. Add a thin adapter only with its first production caller; subscribe to ordered events directly. - Host tags and classes survive. A component renders its existing
openclaw-*tag as a plain host element in light DOM; CSS and E2E select it. Never rename a tag or a class. → components - One implementation per registered tag. Native custom elements and Solid callers share one renderer through
defineSolidBridgeinui/src/lib/solid-bridge.ts. Use its returned component in JSX so the host has one lifecycle owner. → components - One renderer per DOM region, and one lifecycle owner per overlay surface. → overlays
- Derive, don't synchronize.
createMemobeforecreateSignal; no copies of owner state; no defensive guards for states the owner already excludes. Prefer smaller code with fewer owners. - Replace controller lifecycles at their owner. Follow the controller-to-Solid recipe, including its worked example. Delete the class and host adapter wiring together; do not wrap a controller in revision signals as the final design.
Component skeleton
An illustrative component (the tag is an example): it renders its host tag, reads an owner through a projection, and derives instead of copying state.
import { createMemo, Show } from "solid-js";
import { useApplication } from "../lib/reactive/context.ts";
import { projectAgents } from "../lib/reactive/domain-capabilities.ts";
export function AgentCount(props: { compact?: boolean }) {
const app = useApplication();
// Reads through to the synchronous owner; disposed with this component's owner.
const agents = projectAgents(app.agents);
// Never destructure props: reads must stay reactive.
const count = createMemo(() => agents.read().agentsList?.agents.length ?? 0);
return (
<openclaw-agent-count class={["agent-count", { "agent-count--compact": props.compact }]}>
<Show when={count() > 0} fallback={<span class="muted">—</span>}>
<span>{count()}</span>
</Show>
</openclaw-agent-count>
);
}
Use jsxImportSource: "@solidjs/web" and its JSX types, not Solid 1's solid-js/web.
Traps that compile but break
on:x,attr:x,bool:x,classList, anduse:are gone in Solid 2; they compile to literal attributes or no-ops (lint flags them).prop:is the only namespace left.- Events are camelCase (
onClick). Dashed custom events need the exact dash, such asonSession-change, or alisten(...)ref factory. - Native event bindings bind once: dispatch through a stable callback that reads the current handler prop. Runtime spreads, including dynamic elements, can update handlers.
- A read right after a write sees the old value until
flush(). Don't write-then-read in handlers; derive. - Reactive reads after the first
awaitin an async memo are untracked, and async memos get noAbortSignal. - Ref callbacks run unowned:
onCleanupinside them does nothing. - ARIA enumerations are typed strings:
aria-pressed={pressed() ? "true" : "false"}, never a boolean. - Solid 1 APIs (
createResource,onMount,mergeProps,Context.Provider) don't carry over: use async computations,onSettled,merge, and the context component itself. - Writing a signal or store from a memo, an effect compute phase, or a component body throws in dev.
untrackdoesn't exempt it (the guard checks the owner); derive instead, or write from a handler,onSettled, or effect apply. - Class strings built with template literals or
.filter(Boolean).join(" ")lose composable reactivity: useclass={["base", { active: active() }]}. createRenderEffectis for render-phase runtime bindings, andflush()in production code usually hides a write-then-read: feature code usescreateEffectand derivations.
More, each with its root cause: lessons learned.
Testing
mountSolid, waitForSolid/flush, renderSolidRef, and the Solid application-context provider live in ui/src/test-helpers/. .test.tsx files are discovered. Keep assertions when you replace a harness. Visual changes need inspected, sanitized before/after screenshots. Follow the assigned campaign's compute budget. → testing
References
| Topic | Read when |
|---|---|
| idioms | choosing a primitive, derivations, props, lifecycle, stores, keys, async, context, diagnostics |
| refactor checklist | refactoring or reviewing existing Solid: measurable smells, replacements, before/after |
| components | writing components, native custom-element hosts, JSX bindings, events, refs, styles |
| state and ownership | touching owners, projections, async reads, lifetimes, parking, chat render lifecycle |
| overlays | menus, popovers, tooltips, dialogs, focus, positioning, the platform floor |
| testing | unit, browser, E2E, WebKit, parity, retention suites |
| performance | startup bundle, lazy loading, render budgets |
| plugins | building a plugin's Control UI view in Solid |
| lessons learned | before a new kind of change; most entries cost a failed CI run to learn |
Use this skill
Connect your agent to the Skill Store MCP server and ask for it by ref: openclaw/solid-56105533668.
Details
- Publisher
- openclaw
- Category
- Software Development › Frontend & UI
- License
- MIT
- Updated
- 2026-10-18
References
More from openclaw
- Video Frames Extract frames or clips with ffmpeg
- Agent Transcript Requested GitHub PR/issue agent transcripts: redact, trim, preview, and insert safely.
- Auto Qa Run OpenClaw-wide autonomous QA and live/stress campaigns across independent subsystem lanes, wit…
- Autoreview Structured code review through the shared OpenClaw agent-skills installation.