solid 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.
Is the solid skill safe?
Clean: nothing in its files matched our rules. We read 11 files in the folder on 2026-11-08.
No findings.
Install the solid skill
A skill is a folder. Copy it into your agent's skills folder and the agent loads it when the task matches its description.
git clone --depth 1 https://github.com/openclaw/openclaw.git /tmp/openclaw mkdir -p ~/.claude/skills cp -r /tmp/openclaw/.agents/skills/solid ~/.claude/skills/solid
In the Claude apps, zip the folder and upload it from the Skills settings. The folder on GitHub
The instructions your agent would load
SKILL.md as published, without the frontmatter. Read it on GitHub
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.
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.18, and @solidjs/vite-plugin is 3.0.0-next.51. @solidjs/testing-library remains 1.0.0-beta.3. solid-js and @solidjs/* are exempt from the repository's seven-day release-age gate (Peter, 2026-10-11). Peter also approved exact-version exceptions for seroval@1.7.0 and seroval-plugins@1.7.0 on 2026-10-24: rc.18 requires them for SSR/hydration serialization. Other third-party Solid ecosystem packages remain subject to the gate.
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.18 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.18 has no createAsync: asynchronous values are ordinary computations returning promises or async iterables. createDeferred explicitly permits stale answers during refetch; use it only when that lag is the intended presentation contract.
For async reads, mutations, keyed projections, lazy components, or coordinated loading, read the async and action recipes. Cron is the worked reference: plain owners admit requests and writes; Solid presents their readiness and optimistic transitions.
For lists, derivations, copies, or anything on a hot path, read performance.
- 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) and useApplication() 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. Solid-rendered native hosts share their renderer through defineSolidBridge in ui/src/lib/solid-bridge.ts; use its returned component in JSX. Specialized native hosts such as Tooltip keep their existing wrapper and lifecycle owner. → components
- One renderer per DOM region, and one lifecycle owner per overlay surface. → overlays
- Derive, don't synchronize. createMemo before createSignal; no copies of owner state; no defensive guards for states the owner already excludes. Prefer smaller code with fewer owners.
- Keep rendering controller lifecycles out. Production Control UI rendering controllers and host-update contracts have been removed and are guarded. The controller-to-Solid recipe explains the boundary for new integrations: plain domain owners retain authority; Solid owns presentation and lifecycle.
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, oncapture:, attr:x, bool:x, classList, and use: are compile errors on native elements in rc.18. Spreads can still hide them from the compiler; keep the lint guard. prop: and XML namespaces remain supported.
- Events are camelCase (onClick). Dashed custom events need the exact dash, such as onSession-change, or a listen(...) 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 await in an async memo are untracked, and async memos get no AbortSignal.
- Ref callbacks run unowned: onCleanup inside 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. untrack doesn'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: use class={["base", { active: active() }]}.
- createRenderEffect is for render-phase runtime bindings, and flush() in production code usually hides a write-then-read: feature code uses createEffect and derivations.
Enforced wave-zero guard
pnpm check:control-ui-controllers runs in lint and the changed-file checks. For production ui/src and the bundled Workboard, Memory Core, Nostr, WhatsApp, ClawRouter, and X view directories, it rejects createRenderEffect outside lib/reactive/, all direct addEventListener calls in TSX, DOM queries outside the explicit foreign-DOM owner list, and option-free memos whose callback only returns a literal. Property/equality, async, and option-bearing memos retain their contracts. Tests and test helpers are excluded. The native AST scan counts generic and optional calls; flush, getOwner, and runWithOwner resolve actual Solid imports, including aliases and namespaces, without counting owner methods or shadowed locals.
createEffect, untrack, addEventListener, querySelector(All), flush, getOwner, and runWithOwner also have per-file shrink-only baselines in config/control-ui--baseline.txt and config/plugin-ui--baseline.txt. New calls cannot borrow another file's allowance, including allowances in another plugin or the host. Deleting calls requires pnpm check:control-ui-controllers --prune; moving an allowance requires a verified Git rename. Never raise a baseline to admit new debt. See the refactor checklist for replacements and the permitted survivors.
The guard also bans JSX classList and hand-written createComponent outside the explicit context-adapter allowlist. Component/view onCleanup and manual stored/chained import loaders have shrink-only baselines; computation cleanup and reactive primitives remain allowed. Saving flags require recipe review: trace the co-written createOptimistic(false) rather than infer busy state from isPending or a nearby optimistic declaration.
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
More skills from openclaw/openclaw
- A1passwordSet up and use 1Password CLI for sign-in, desktop integration, and reading or injecting secrets.
- Aacp-routerRoute plain-language requests for Claude Code, Cursor, Copilot, OpenClaw ACP, OpenCode, Gemini CLI, Grok Build, Qwen, Kiro, Kimi, iFlow, Factory Droid, Kilocode, or explicit ACP harness work into either OpenClaw ACP runtime sessions or direct acpx-driven sessions ("telephone game" flow). For coding-agent thread requests, read this skill first, then use only `sessions_spawn` for thread creation. Codex chat binding defaults to the native Codex app-server plugin unless ACP is explicit or background spawn needs ACP.
- Aadd-model-providerAdd and live-prove a model provider with non-interactive config one-liners, without exposing credentials.
- Aagent-transcriptRequested GitHub PR/issue agent transcripts: redact, trim, preview, and insert safely.
- Aapple-notesCreate, view, edit, delete, search, move, or export Apple Notes via the memo CLI on macOS.
- Aapple-remindersList, add, edit, complete, or delete Apple Reminders and reminder lists via remindctl.
- Aauto-qaRun OpenClaw-wide autonomous QA and live/stress campaigns across independent subsystem lanes, with verified fixes and a resumable evidence report.
- AautoreviewStructured code review through the shared OpenClaw agent-skills installation.
- Abear-notesCreate, search, and manage Bear notes via grizzly CLI.
- Ablock-kitUse proactively for structured or interactive Slack replies, and when asked to author or validate native Slack Block Kit JSON.
- AblogwatcherMonitor blogs and RSS/Atom feeds for updates using the blogwatcher CLI.
- AblucliBluOS CLI (blu) for discovery, playback, grouping, and volume.