From plaited-plaited
Build and test Plaited's server-driven UI stack. Use when working on `src/ui`, controller islands, controller protocol schemas, SSR templates, CSS helpers, dynamic controller modules, or UI test fixtures.
npx claudepluginhub joshuarweaver/cascade-code-general-misc-3 --plugin plaited-plaitedThis skill uses the workspace's default tool permissions.
Use this skill when changing Plaited's UI runtime, renderer, CSS helpers, custom
Applies Acme Corporation brand guidelines including colors, fonts, layouts, and messaging to generated PowerPoint, Excel, and PDF documents.
Builds DCF models with sensitivity analysis, Monte Carlo simulations, and scenario planning for investment valuation and risk assessment.
Calculates profitability (ROE, margins), liquidity (current ratio), leverage, efficiency, and valuation (P/E, EV/EBITDA) ratios from financial statements in CSV, JSON, text, or Excel for investment analysis.
Use this skill when changing Plaited's UI runtime, renderer, CSS helpers, custom element controllers, browser/server controller protocol, or UI tests.
The UI model is server-driven and island-scoped:
createSSR() serializes templates into HTML stringsuseController() registers topic-scoped custom elements/ws using its p-topic value as the WebSocket subprotocolrender, attrs, import, and disconnect commandsui_event and error messages back to the serverUse plaited-runtime only when the task depends on behavioral-programming
semantics. The browser controller itself should stay small and protocol-shaped.
Public UI exports are re-exported from src/ui.ts.
| Area | Files | Primary APIs |
|---|---|---|
| Controller runtime | src/ui/controller/use-controller.ts | useController |
| Controller protocol | src/ui/controller/controller.schemas.ts, src/ui/controller/controller.types.ts, src/bridge-events.ts | RenderMessageSchema, AttrsMessageSchema, ImportModuleSchema, ClientMessageSchema, ControllerModuleContext |
| Controller utilities | src/ui/controller/delegated-listener.ts, src/ui/controller/controller.constants.ts | DelegatedListener, delegates, SWAP_MODES |
| Shadow DOM decoration | src/ui/controller/decorate-elements.ts | decorateElements |
| Rendering | src/ui/render/template.ts, src/ui/render/ssr.ts, src/ui/render/template.types.ts, src/ui/render/template.constants.ts | createTemplate, h, Fragment, createSSR, P_TARGET, P_TRIGGER, P_TOPIC |
| CSS | src/ui/css/*.ts | createStyles, createTokens, createHostStyles, createRootStyles, createKeyframes, joinStyles |
| Server bridge | src/modules/ui-websocket-runtime-actor.ts | validates ClientMessageSchema, preserves p-topic source metadata, and forwards client messages |
useController() is the browser runtime boundary. It creates a scoped custom
element registry and returns a function that registers controller element tags.
Controller islands require p-topic. The topic becomes the WebSocket
subprotocol and identifies the server-side conversation for that island.
Use these attributes:
p-topic: placed on the controller island host; selects the WebSocket topic.p-target: placed on descendants that the server can update by target.p-trigger: placed on descendants that should emit BP-shaped browser events.The controller only queries inside the island for render and attrs targets.
Do not make document-global target updates part of this runtime.
Messages sent by the server are parsed as BP events first, then narrowed by controller schemas.
Supported event types:
| Type | Detail | Behavior |
|---|---|---|
render | { target, html, stylesheets, registry, swap? } | Finds [p-target="${target}"] inside the island and applies HTML. Defaults to innerHTML. |
attrs | { target, attr } | Sets, removes, or toggles attributes on the target element. |
import | site-root .js path | Dynamically imports a local controller module and invokes its default export. |
disconnect | optional | Closes the island WebSocket. |
Swap modes are afterbegin, afterend, beforebegin, beforeend,
innerHTML, and outerHTML.
Rendered HTML is parsed through template.setHTMLUnsafe(). Dynamically inserted
<script> tags are inert in browsers; controller modules are the supported
runtime code-loading path.
Unknown server event types should report a controller error message rather
than silently bypassing the protocol.
The browser sends top-level controller client messages:
| Type | Detail | Source |
|---|---|---|
ui_event | BP event | p-trigger handlers and imported controller modules |
error | string | controller parse/import/runtime failures |
p-trigger values serialize as space-separated domEvent:eventType pairs.
When a matching DOM event fires, the island sends:
{
type: 'ui_event',
detail: {
type: eventType,
detail: getAttributes(element),
},
}
The detail is an attribute map from the triggering element, not DOM properties. This gives the server/agent enough context while keeping the protocol serializable.
After a controller module default export finishes, the island emits a BP event
inside ui_event:
{
type: 'ui_event',
detail: {
type: 'import_invoked',
detail: '/modules/example.js',
},
}
Controller modules are loaded through import messages and run for side effects.
The import path must be site-root absolute and end in .js; query strings and
hash fragments are allowed for cache keys and identity changes.
Allowed examples:
/modules/search-panel.js/modules/search-panel.js?v=42/modules/search-panel.js#entry/modules/search-panel.js?v=42#entryRejected examples:
modules/search-panel.jshttps://example.com/search-panel.js//example.com/search-panel.js/modules/search-panel.ts/modules/search-panel.js.mapThe module must export a default function:
import type { ControllerModuleContext } from 'plaited/ui'
export default ({ DelegatedListener, delegates, addDisconnect, trigger }: ControllerModuleContext) => {
const button = document.getElementById('save')
if (!button) return
const listener = new DelegatedListener(() => {
trigger({ type: 'save_clicked', detail: { id: button.id } })
})
delegates.set(button, listener)
button.addEventListener('click', listener)
addDisconnect(() => button.removeEventListener('click', listener))
}
The default export may be synchronous or async. The controller reports
import_invoked after the function resolves.
Imported modules should use addDisconnect for cleanup. They may reuse
DelegatedListener and delegates for listener lifecycle discipline. They
should emit BP-shaped events with trigger, not open their own controller
transport.
JSX creates template objects, not DOM nodes.
Important rules:
p-target for server-addressable update pointsp-trigger instead of inline event handlerson* attributes are rejected by the template renderersrc valuescreateSSR() deduplicates styles per renderer instancedecorateElements() creates declarative Shadow DOM wrappersUse createSSR() per connection or per rendering stream when style
deduplication matters. Call clearStyles() when a connection closes or when the
server must resend all styles.
Use the existing CSS helpers before adding styling infrastructure:
createStyles for class-producing element stylescreateHostStyles for host styles used by Shadow DOM decoratorscreateRootStyles for root-level declarationscreateTokens for design token referencescreateKeyframes for animationsjoinStyles to combine style outputsDo not hand-roll stylesheet concatenation unless the helper APIs cannot express the case. Style deduplication depends on the existing stylesheet arrays.
Choose the smallest test layer that proves the change.
| Layer | Use for | Command |
|---|---|---|
| Schema/pure tests | protocol validators, constants, helper behavior | bun test src/ui/controller/tests/controller.schemas.spec.ts |
| DOM/template tests | decorateElements, DelegatedListener, stable render output | bun test src/ui/controller/tests/decorate-elements.spec.tsx src/ui/controller/tests/delegated-listener.spec.ts |
| Real browser tests | WebSocket lifecycle, swaps, attrs, imports, dynamic DOM behavior | bun test src/ui/controller/tests/controller-browser.spec.ts |
| Typecheck | exported API and cross-module contracts | bun --bun tsc --noEmit |
Real browser tests use @playwright/cli plus
src/ui/controller/tests/fixtures/serve.ts. Prefer that fixture server over
mock WebSocket stacks for controller behavior.
Browser coverage should include:
innerHTML behavior when swap is omittedattrs string, number, boolean, and null removal behaviorp-trigger to ui_event with attribute-map detailimport_invokedKeep schema tests exhaustive for protocol edges:
src/ui/ and src/modules/ui-websocket-runtime-actor.ts are
the source of truth.p-trigger plus server/agent responses over client-local business
logic.bun --bun tsc --noEmit before committing.render or attrs.DelegatedListener,
delegates, and addDisconnect.dist files; fixture builds write under an
ignored dist directory.Open these when you need deeper context, but verify them against code:
references/server-pipeline.md: SSR/template pipeline detailsreferences/css-system.md: CSS helper detailsreferences/controller-modules.md: dynamic controller module import contractreferences/testing.md: UI test layering and browser fixture approachreferences/websocket-decisions.md: transport rationaleplaited-runtime for BP event semantics and server-side behavioral programscode-documentation for TSDoc and public API documentation