mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-23 23:47:52 +00:00
docs(solid): extend the Solid skill with Solid 2 best practices and make agents read it (#173903)
The idiom wave is replacing Lit-era controllers across 247 files, and agents keep
reaching for React and Solid 1 patterns. This brings the Solid skill up to the
rc.14 contract and makes it mandatory reading.
- SKILL.md: read the installed solid-js CHEATSHEET.md ("What changed from 1.x")
before the first edit; new traps for owned-scope writes (untrack doesn't
exempt them), hand-built class strings, createRenderEffect, and flush() in
production code.
- idioms.md: a primitive-selection table (owner/projection/memo/signal/writable
derivation/store/createProjection/optimistic/Repeat), lazy and unobserved,
default-less context, component types and children(), class arrays, array
refs and directive factories, rc.14 dev diagnostics (STRICT_READ_UNTRACKED,
LOADING_ON_OUTSIDE_HOLD, owned writes), effect options, and test helpers
(createRoot, flush, resolve, snapshot).
- references/refactor-checklist.md: measurable smells with replacements and
allowed exceptions, from a census of main (124 class template literals,
27 createRenderEffect, 28 production flush() calls, 0 Repeat), plus
before/after examples.
- AGENTS.md and ui/AGENTS.md: read the skill before writing, reviewing, or
refactoring any Solid code, not only during the migration.
Sources: the installed solid-js 2.0.0-rc.14 CHEATSHEET.md, the Solid 2.0
MIGRATION.md, and the v2.0.0 beta/RC release notes.
This commit is contained in:
parent
785f2467d8
commit
b19d5542f6
5 changed files with 229 additions and 17 deletions
|
|
@ -5,13 +5,13 @@ description: Write, port, review, or test Solid 2 UI in OpenClaw (Control UI, pl
|
|||
|
||||
# Solid 2 in OpenClaw
|
||||
|
||||
The Control UI, plugin views (`extensions/x`, `extensions/workboard`), and the Canvas A2UI hosts render with **Solid 2** (`solid-js`, `@solidjs/web`, `@solidjs/signals`, compiled by `@solidjs/vite-plugin`/`@solidjs/compiler`). Lit 3 and Web Awesome are being removed. Until the last Lit file is gone, ported Solid code runs next to unported Lit at every commit.
|
||||
The Control UI, plugin views (`extensions/x`, `extensions/workboard`), and the Canvas A2UI hosts render with **Solid 2** (`solid-js`, `@solidjs/web`, `@solidjs/signals`, compiled by `@solidjs/vite-plugin`/`@solidjs/compiler`). Web Awesome is removed, and the last Lit interop modules are retiring; new code is Solid only.
|
||||
|
||||
Read [ui/AGENTS.md](../../../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
|
||||
|
||||
For new code or a Solid audit, read [idioms](references/idioms.md) and [lessons](references/lessons.md). Check `node_modules/solid-js/CHEATSHEET.md`, its `types/client/flow.d.ts`, and the installed `@solidjs/signals` / `@solidjs/web` types when choosing an API. Solid 2 is not Solid 1: rc.14 has no `createAsync`; asynchronous values are ordinary computations returning promises or async iterables.
|
||||
**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](references/idioms.md) and [lessons](references/lessons.md); to refactor or review existing code, also use the [refactor checklist](references/refactor-checklist.md). 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.
|
||||
|
||||
1. **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](references/state-and-ownership.md)
|
||||
2. **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.
|
||||
|
|
@ -58,6 +58,9 @@ Use `jsxImportSource: "@solidjs/web"` and its JSX types, not Solid 1's `solid-js
|
|||
- 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.
|
||||
|
||||
More, each with its root cause: [lessons learned](references/lessons.md).
|
||||
|
||||
|
|
@ -67,15 +70,16 @@ More, each with its root cause: [lessons learned](references/lessons.md).
|
|||
|
||||
## References
|
||||
|
||||
| Topic | Read when |
|
||||
| -------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
||||
| [idioms](references/idioms.md) | derivations, props, lifecycle, local stores, keys, async values, and lint decisions |
|
||||
| [components](references/components.md) | porting or writing components: Lit → Solid table, JSX bindings, events, refs, styles |
|
||||
| [state and ownership](references/state-and-ownership.md) | touching owners, projections, async reads, lifetimes, parking, chat render lifecycle |
|
||||
| [Lit interop](references/lit-interop.md) | a Lit caller uses your component, or your component hosts unported Lit |
|
||||
| [overlays](references/overlays.md) | menus, popovers, tooltips, dialogs, focus, positioning, the platform floor |
|
||||
| [testing](references/testing.md) | unit, browser, E2E, WebKit, parity, retention suites |
|
||||
| [performance](references/performance.md) | startup bundle, lazy loading, render budgets |
|
||||
| [plugins](references/plugins.md) | building a plugin's Control UI view in Solid |
|
||||
| [migration](references/migration.md) | porting the remaining Lit, the final sweep, the inventory |
|
||||
| [lessons learned](references/lessons.md) | before a new kind of change; most entries cost a failed CI run to learn |
|
||||
| Topic | Read when |
|
||||
| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| [idioms](references/idioms.md) | choosing a primitive, derivations, props, lifecycle, stores, keys, async, context, diagnostics |
|
||||
| [refactor checklist](references/refactor-checklist.md) | refactoring or reviewing existing Solid: measurable smells, replacements, before/after |
|
||||
| [components](references/components.md) | porting or writing components: Lit → Solid table, JSX bindings, events, refs, styles |
|
||||
| [state and ownership](references/state-and-ownership.md) | touching owners, projections, async reads, lifetimes, parking, chat render lifecycle |
|
||||
| [Lit interop](references/lit-interop.md) | a Lit caller uses your component, or your component hosts unported Lit |
|
||||
| [overlays](references/overlays.md) | menus, popovers, tooltips, dialogs, focus, positioning, the platform floor |
|
||||
| [testing](references/testing.md) | unit, browser, E2E, WebKit, parity, retention suites |
|
||||
| [performance](references/performance.md) | startup bundle, lazy loading, render budgets |
|
||||
| [plugins](references/plugins.md) | building a plugin's Control UI view in Solid |
|
||||
| [migration](references/migration.md) | porting the remaining Lit, the final sweep, the inventory |
|
||||
| [lessons learned](references/lessons.md) | before a new kind of change; most entries cost a failed CI run to learn |
|
||||
|
|
|
|||
|
|
@ -59,6 +59,30 @@ listener registrations, and LOC for touched production files. The existing Lit
|
|||
ratchet reports controller-pattern growth while legacy callers remain; after
|
||||
the production baseline reaches zero it rejects reintroductions.
|
||||
|
||||
## Choose the primitive
|
||||
|
||||
Start from the narrowest primitive that expresses the data's owner and lifetime:
|
||||
|
||||
| Data | Primitive |
|
||||
| -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
|
||||
| Authoritative domain state (Gateway store, sessions, caches, mutation authority) | Plain TypeScript owner; never a signal. Read it through a projection. |
|
||||
| An owner's snapshot in a view | `projectOwner`/`projectSource` (`lib/reactive/projection.ts`), narrowed with memos |
|
||||
| A cheap value derived from props or signals | An accessor `() => …` or a JSX expression |
|
||||
| An expensive or shared derivation | `createMemo` (with `equals` only for immutable values) |
|
||||
| Local interaction state (open, hovered, active tab) | `createSignal` |
|
||||
| A local draft that resets or reconciles from a source | Writable derivation `createSignal(() => source())`, with the reset policy commented |
|
||||
| Structured or keyed local state | `createStore` with draft setters; `reconcile(rows, "id")` for keyed lists |
|
||||
| A keyed collection derived from async or owner data | `createProjection(fn, initial, { key: "id" })` |
|
||||
| An expected value shown before a mutation settles | `createOptimistic`/`createOptimisticStore` inside `action` |
|
||||
| Count-only repetition | `<Repeat count={n}>` |
|
||||
|
||||
`createMemo(fn, { lazy: true })` defers the first computation until something
|
||||
reads it and disposes it when nothing observes it; `createSignal(v, { unobserved })`
|
||||
fires when the last subscriber leaves. Reach for them in new adapters that must
|
||||
release upstream work with their last reader. Keep the existing `projectSource`
|
||||
contract (read-through, revision equality, scope replacement) rather than
|
||||
reimplementing it piecemeal.
|
||||
|
||||
## Derive values; effects perform work
|
||||
|
||||
A component body runs once. JSX bindings, memos, and effect compute phases track
|
||||
|
|
@ -304,6 +328,74 @@ admission, rollback policy, target capture, or stale-result authority. Keep the
|
|||
owner's visible error/retry outcome and catch async rendering errors with
|
||||
`Errored` rather than an invented `.error` field on a read accessor.
|
||||
|
||||
## Context, components, and children
|
||||
|
||||
`createContext<T>()` without a default is the canonical form: `useContext`
|
||||
returns `T` and throws `ContextNotFoundError` outside a provider, so don't add a
|
||||
`useX()` wrapper whose only job is to rethrow. Use a default only for primitive
|
||||
fallbacks (theme, locale). The context itself is the provider:
|
||||
`<ThemeContext value={theme()}>`. App-wide state is not context: in OpenClaw it
|
||||
lives in plain TypeScript owners reached through `useApplication()`.
|
||||
|
||||
Type components with `Component<P>`, `VoidComponent<P>`, `ParentComponent<P>`,
|
||||
or `FlowComponent<P, C>`; renderer-neutral children are `Element` from
|
||||
`solid-js`. Resolve children once with `children(() => props.children)` when a
|
||||
component must inspect or reuse them; otherwise render `props.children`
|
||||
directly.
|
||||
|
||||
## Classes, attributes, and refs
|
||||
|
||||
Compose classes with the array/object form,
|
||||
`class={["badge", props.class, { "badge--muted": muted() }]}`. Never build
|
||||
class strings with template literals, `.filter(Boolean).join(" ")`, or
|
||||
concatenation: the array form keeps each condition reactive and composable.
|
||||
Built-in attributes are lowercase (`tabindex`, not `tabIndex`); boolean
|
||||
attributes are presence/absence (`muted={false}` removes it).
|
||||
|
||||
Refs are functions. Compose several with an array, `ref={[autofocus, tooltip(opts)]}`;
|
||||
a bare `let el` inside that array is never assigned (that rewrite only works for
|
||||
a single `ref`). Reusable DOM behavior is a two-phase directive factory: create
|
||||
primitives in the owned setup phase, return an unowned callback that only
|
||||
captures the node and writes to it.
|
||||
|
||||
## Dev diagnostics are bug reports
|
||||
|
||||
Treat these rc.14 dev warnings as real defects, not noise:
|
||||
|
||||
- **Top-level reactive read in a component body.** The body runs once. Read in
|
||||
JSX, a memo, or an accessor.
|
||||
- **Write under an owned scope.** Writing a signal or store from a memo, an
|
||||
effect compute phase, or a component body throws in dev. `untrack` doesn't
|
||||
help, because the guard checks the owner, not tracking. Move the write to an
|
||||
event handler, `onSettled`, or effect apply, or derive instead of writing.
|
||||
- **`STRICT_READ_UNTRACKED`.** A value read in an effect apply callback or at
|
||||
the top of a flow callback (`For`, `Show`, `Match`) is frozen. Read it in the
|
||||
compute phase, JSX, or a memo.
|
||||
- **`LOADING_ON_OUTSIDE_HOLD`.** The same pending data is read outside the
|
||||
`Loading` boundary, so its fallback can never show. Move the read under the
|
||||
boundary.
|
||||
|
||||
`onSettled` also works inside an event handler: it defers follow-up work
|
||||
(focus, measurement) until the transition that handler started has settled.
|
||||
|
||||
## Effects: the rare cases
|
||||
|
||||
`createEffect(compute, apply)` is the only effect form. Its options cover the
|
||||
common edge cases without extra primitives: `{ defer: true }` skips the initial
|
||||
run, and the object form `createEffect(compute, { effect, error })` routes
|
||||
async failures to `error`, replacing Solid 1's `onError`/`catchError`.
|
||||
`createRenderEffect` exists for render-phase bindings like the runtime's own;
|
||||
feature code uses `createEffect`. `createTrackedEffect` and `createReaction` are
|
||||
escape hatches for library internals, not for views.
|
||||
|
||||
## Tests
|
||||
|
||||
Reactive primitives need an owner: wrap ad hoc test code in
|
||||
`createRoot((dispose) => …)` and dispose it. Call `flush()` before asserting on
|
||||
a value you just wrote. For an async computation, `await resolve(() => value())`
|
||||
waits for it to settle instead of polling. Assert snapshots of stores with
|
||||
`snapshot(store)`, not the proxy.
|
||||
|
||||
## Audit and lint without fighting the contract
|
||||
|
||||
The V2 configuration lives in `.oxlintrc.json` with the plugin isolated under
|
||||
|
|
|
|||
116
.agents/skills/solid/references/refactor-checklist.md
Normal file
116
.agents/skills/solid/references/refactor-checklist.md
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
# Refactor checklist: idiomatic Solid 2, less code
|
||||
|
||||
Use this when refactoring or reviewing existing Solid code. The migration left
|
||||
working code that still carries Lit-era shapes: controller classes, effects that
|
||||
copy state, imperative listeners, and hand-built class strings. Each row below
|
||||
is a smell with a measurable count, the idiomatic replacement, and the
|
||||
exception that justifies keeping it. Measure the touched production files before
|
||||
and after, and report the counts in the PR. Net production LOC must go down.
|
||||
|
||||
The installed `solid-js/CHEATSHEET.md` (rc.14) is the API contract. Its
|
||||
"What changed from 1.x" list is required reading before the first edit: Solid 2
|
||||
is neither React nor Solid 1, and both priors produce plausible-looking bugs.
|
||||
|
||||
## Measure
|
||||
|
||||
Run from the repository root; scope paths to your area:
|
||||
|
||||
```sh
|
||||
area=ui/src/pages/sessions # your scope
|
||||
prod() { git grep -nE "$1" -- "$area" ':!*.test.*' ':!**/test-helpers/**' ':!**/e2e/**'; }
|
||||
prod 'hostConnected|hostDisconnected|requestUpdate\(|class [A-Za-z]+Controller' | wc -l
|
||||
prod 'createEffect\(' | wc -l
|
||||
prod 'createRenderEffect\(' | wc -l
|
||||
prod 'untrack\(' | wc -l
|
||||
prod 'addEventListener\(' | wc -l
|
||||
prod 'querySelector(All)?\(' | wc -l
|
||||
prod 'class=\{`|filter\(Boolean\)\.join\(" "\)' | wc -l
|
||||
prod '[^a-zA-Z_.]flush\(\)' | wc -l
|
||||
prod 'runWithOwner\(|getOwner\(' | wc -l
|
||||
```
|
||||
|
||||
## Smells and replacements
|
||||
|
||||
| Smell | Replace with | Keep only when |
|
||||
| ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Lit controller (`hostConnected`, `requestUpdate`, `class …Controller` owned by a view) | `createThing(deps)` per the [controller recipe](idioms.md#replace-lit-era-controllers): signals, memos, `createStore`, owner-scoped setup with `onSettled`/`onCleanup` | It is a plain TypeScript domain owner with synchronous non-UI state and no host/update semantics. Name it as an owner, not a controller. |
|
||||
| An effect that writes a signal or store from another reactive value | `createMemo`, or a writable derivation `createSignal(() => source())` with a stated reset policy | Never. An owned-scope write throws in dev; `untrack` does not exempt it, and `ownedWrite: true` hides the design problem. |
|
||||
| A conversion that raises the `createEffect` count | Setup/teardown in the owning function, derived values in memos, event updates in handlers | The effect synchronizes with a real external system (DOM measurement, imperative widget, transport). Comment why. |
|
||||
| `createRenderEffect` in feature code | `createEffect(compute, apply)` | The binding must run during render, like the runtime's own attribute bindings. That's rare outside primitives. |
|
||||
| `untrack` silencing a warning or freezing props | Model the dependency: read in JSX, a memo, or the effect compute phase | A deliberate snapshot of lifetime-stable identity. Comment the reason. |
|
||||
| `addEventListener` without paired owner cleanup | A JSX handler (`onClick={(e) => props.onX(e)}`), or `lib/reactive/listener.ts` returning teardown from `onSettled` / effect apply | Native capture or `passive` options, or `window`/`document` targets, through the owned helper. |
|
||||
| `querySelector` to find your own DOM | A `ref` callback or a two-phase ref directive factory | Querying a deliberate island (xterm, noVNC, PDF, canvas, markdown reconciler) owned by that island's code. |
|
||||
| Class strings: template literals, `.filter(Boolean).join(" ")`, string concatenation | `class={["base", props.class, { active: active() }]}` | Never. |
|
||||
| `flush()` in production code | Derive the value instead of write-then-read; let microtask batching run | An imperative integration that must observe committed DOM synchronously. Name the integration and keep it at the owner. |
|
||||
| `runWithOwner`/`getOwner` capturing owners for nested mounts | Create primitives in the component or factory body; remove once Lit interop is gone | A library adapter mounting Solid content inside a foreign DOM island. |
|
||||
| Manual `subscribe`/notify loops in a view | `projectOwner`/`projectSource` from `lib/reactive/projection.ts` | Event APIs whose ordering, replay, or completion contract a latest-value read would lose. Those stay direct subscriptions. |
|
||||
| `For each={Array.from({ length: n })}` or index-only maps | `<Repeat count={n}>` (plain numeric index, no diffing) | Real entities need keys: use `For keyed={(x) => x.id}`. |
|
||||
| A `useX()` context wrapper that throws on a missing provider | `createContext<T>()` without a default (throws `ContextNotFoundError` itself), read with `useContext` | The wrapper adds real behavior beyond the throw. |
|
||||
| `Provider` components, `mergeProps`, `splitProps`, `onMount`, `createResource`, `Index`, `classList`, `use:` | `<Context value>`, `merge`, `omit`, `onSettled`, async computation + `<Loading>`, `For keyed={false}`, `class` arrays/objects, `ref={factory()}` | Never: these are Solid 1 and either fail to compile or silently no-op. |
|
||||
| Loading flags in local signals around an async read | An async `createMemo`/`createProjection` under `<Loading>`; `isPending(() => x())` for in-flight changes | The flag is an owner's mutation state (saving/admitted/uncertain), which stays at the owner. |
|
||||
| Hand-rolled keyed merge of server lists into local state | `createProjection(async () => owner.list(), [], { key: "id" })`, or `reconcile(rows, "id")` inside a store draft | The list is an owner snapshot; consume it through a projection instead. |
|
||||
|
||||
## Order of work
|
||||
|
||||
1. Read the module, its owner, its callers, and its tests. Name the contract each
|
||||
piece serves before deleting anything.
|
||||
2. Convert the state model first (controller → `createThing`, copies → memos),
|
||||
then lifecycle, then events and refs, then markup cleanups like class arrays.
|
||||
3. Delete the old class, adapter wiring, and test-only exports in the same PR.
|
||||
Tests assert public behavior and cleanup, not internals.
|
||||
4. Measure again. If LOC or the effect count went up, the conversion isn't done.
|
||||
|
||||
## Before and after
|
||||
|
||||
A view controller with manual updates:
|
||||
|
||||
```ts
|
||||
// Before: a Lit-era controller owned by one view.
|
||||
class FilterController {
|
||||
query = "";
|
||||
constructor(private host: { requestUpdate(): void }) {}
|
||||
hostConnected() {
|
||||
window.addEventListener("keydown", this.onKey);
|
||||
}
|
||||
hostDisconnected() {
|
||||
window.removeEventListener("keydown", this.onKey);
|
||||
}
|
||||
setQuery(value: string) {
|
||||
this.query = value;
|
||||
this.host.requestUpdate();
|
||||
}
|
||||
private onKey = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") this.setQuery("");
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
// After: signals drive updates; setup and teardown live together.
|
||||
function createFilter() {
|
||||
const [query, setQuery] = createSignal("");
|
||||
onSettled(() =>
|
||||
listen(window, "keydown", (event) => {
|
||||
if (event instanceof KeyboardEvent && event.key === "Escape") setQuery("");
|
||||
}),
|
||||
);
|
||||
return { query, setQuery };
|
||||
}
|
||||
```
|
||||
|
||||
A copied value becomes a derivation, and a hand-built class string becomes the
|
||||
array form:
|
||||
|
||||
```tsx
|
||||
// Before
|
||||
const [label, setLabel] = createSignal("");
|
||||
createEffect(
|
||||
() => props.count,
|
||||
(count) => setLabel(t("items", { count })),
|
||||
);
|
||||
return <span class={`badge ${props.muted ? "badge--muted" : ""}`}>{label()}</span>;
|
||||
|
||||
// After
|
||||
const label = () => t("items", { count: props.count });
|
||||
return <span class={["badge", { "badge--muted": props.muted }]}>{label()}</span>;
|
||||
```
|
||||
|
|
@ -118,7 +118,7 @@ Read matching guides in full; commands and details stay with them.
|
|||
- **Plugins/SDK:** [plugins](extensions/AGENTS.md), [loader](src/plugins/AGENTS.md), [SDK](src/plugin-sdk/AGENTS.md) (owns public boundary expansion, including outside callers).
|
||||
- **Channels/message actions:** [channel boundary](src/channels/AGENTS.md), [channel responsibilities](docs/plugins/sdk-channel-plugins.md).
|
||||
- **Agent tools, prompts, admission, lifecycle:** [agents](src/agents/AGENTS.md), [Gateway](src/gateway/AGENTS.md).
|
||||
- **Control UI:** [UI guide](ui/AGENTS.md), including state shared with other Gateway clients.
|
||||
- **Control UI:** [UI guide](ui/AGENTS.md), including state shared with other Gateway clients. Before writing, reviewing, or refactoring Solid code (Control UI, plugin views, Canvas hosts), read the [Solid skill](.agents/skills/solid/SKILL.md) and the installed `solid-js` cheatsheet it points to.
|
||||
- **Storage:** [database schemas](docs/reference/database-schemas.md) and subpages; read approval checkpoint before schema, transaction, retention, recovery changes.
|
||||
- **Config migration:** [Doctor transforms](docs/gateway/doctor/config-migrations.md), never new runtime compatibility readers.
|
||||
- **Audit/identity:** [audit doctrine](docs/gateway/audit.md); provenance = opt-in, never authorization; changes to collection, reader scope, retained fields, bounds, contracts need approval.
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
UI ownership rules here; repo-global architecture, proof, Git rules in root `AGENTS.md`.
|
||||
|
||||
## Solid migration
|
||||
## Solid
|
||||
|
||||
- Lit 3/Web Awesome ports + Solid 2 components/projections: follow [Solid skill](../.agents/skills/solid/SKILL.md) + assigned migration order for conversion, lifecycle, interop, proof. State ownership below still applies.
|
||||
- Before writing, reviewing, or refactoring any Solid code here, read the [Solid skill](../.agents/skills/solid/SKILL.md) in full, the installed `node_modules/solid-js/CHEATSHEET.md` it points to, and the references it routes you to; for existing code, the [refactor checklist](../.agents/skills/solid/references/refactor-checklist.md). State ownership below still applies.
|
||||
- Final Lit sweep: remove Lit-specific guidance.
|
||||
|
||||
Solid route modules expose `renderSolid(props)` from the existing lazy page import.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue