Agent guide - authoring, signals, and lifecycle
Task and reading order
Section titled “Task and reading order”Use this topic when mounting Pibbl, integrating a host framework, debugging state resets, or managing imperative resources. First read installation and authoring, state and lifecycle, and the exact pibbl contract. For an HTML-owned application, also read the native-button integration.
Do not infer compatibility from syntax resembling React. Pibbl components are synchronous functions returning Pibbl nodes. Pibbl and React have different elements, hooks, and JSX runtimes. Use one JSX runtime per file or explicit createElement at a mixed-runtime boundary. The React host guide shows the lifecycle split.
Ownership and identity
Section titled “Ownership and identity”The host owns the canvas DOM node, CSS, and the returned controller. Pibbl owns the live mount and its component/hook resources. Retain the controller and dispose it before the host is removed. Repeated pibbl calls on the same live canvas replace the root on the scheduler and return the same controller; they do not replace mount-time viewport, fit, density, or AbortSignal configuration.
Component identity depends on parent scope, function identity, key, and same-type occurrence. Define stable component functions outside the rendering function that uses them. Keys preserve same-parent reorder; they cannot preserve a component across reparenting. A changed function or key is a lifetime change, not a styling update. Call hooks in the same order and count every successful render.
Choose the state primitive
Section titled “Choose the state primitive”| Requirement | Use | Avoid |
|---|---|---|
| Host-owned writable state | signal |
Recreating it on every host update |
| Component-owned writable state | useSignal |
Writing while rendering |
| Derived state | computed or useComputed |
A reaction that writes another signal |
| Imperative synchronization | useReaction with cleanup |
Treating it as a React effect that may write signals |
| Mount-lifetime construction | useConst |
Assuming it owns cleanup or tracks reads |
| Mutable session bookkeeping | useRef |
Expecting mutation to schedule rendering |
Reads are explicit. Passing a signal to a declared reactive input makes the receiver its consumer. Calling .get() in your component makes that component the consumer. There is no recursive unwrapping of arbitrary application objects. set treats a function as data; use update for functional updates. batch groups synchronous notification waves.
Scheduling and failure
Section titled “Scheduling and failure”The shared scheduler runs Begin, Advance, Commit, Plan, Render, and Complete. Reactions run in Complete after successful rendering. Signal reads in a reaction belong to it; its cleanup runs before replacement or release. Setup and cleanup cannot write signals. Component, primitive, and computed evaluation also forbid writes, including equality no-ops. untracked does not bypass these guards.
Failed evaluation preserves the previous successful dependency generation and releases provisional resources. This does not promise pixel rollback: immediate Canvas operations already performed may remain visible. Do not use a thrown render as an application transaction.
Complete example and adaptation
Section titled “Complete example and adaptation”The source below is the existing Signals lesson. Compile it with jsx: "react-jsx" and jsxImportSource: "@pibbl/core", attach a canvas with width 720 and height 420, then call its default mount(canvas). Keep its returned controller. The lesson’s module-level signals deliberately outlive one mount; move state into useSignal when each instance should start independently.
Open the Signals lesson in the playground. For the underlying API, use signal, useSignal, computed, and useReaction.
Verification
Section titled “Verification”- Mount once, activate the example, and assert its displayed state changes after a scheduled frame.
- Rerender with the same function/key and confirm state is retained. Change the key and confirm a new lifetime.
- Reorder keyed siblings within the same parent and confirm state follows keys. Treat reparenting as a remount.
- Mount, remove, and remount repeatedly. Check owned listeners/observers are released and the old controller cannot affect the replacement.
- Test a failed setup or render and confirm external resources are released. Inspect semantic state before pixels.
When reporting results, distinguish a code typecheck from a browser run, and include package version, source revision, action, and observed state. Do not claim teardown from a screenshot or from heap timing alone.
Complete source
Section titled “Complete source”Host setup for this source: use a canvas with width 720 and height 420, compile with jsx: "react-jsx" and jsxImportSource: "@pibbl/core", import its default mount, and call const controller = mount(canvas) after attaching the canvas. Call controller.dispose() before removing it.
signals.example.tsx
Section titled “signals.example.tsx”import { computed, pibbl, useSignal, Rectangle, Text, signal, type PibblController,} from "@pibbl/core";
const x = signal(144, { debugName: "learning x" });const doubledX = computed(() => x.get() * 2, { debugName: "learning doubled x" });const signalSummary = computed( () => `x ${x.get()} · computed ${doubledX.get()}`, { debugName: "learning signal summary" },);
function SignalCard() { const color = useSignal("#2563eb", { debugName: "learning color" });
return [ <Rectangle style={{ width: 720, height: 420, fill: "#eef2ff" }} />, <Rectangle style={{ left: x, top: 126, width: 432, height: 174, fill: color, stroke: "#17211d", strokeWidth: 6, cursor: "pointer", }} onClick={() => { x.update((value) => (value === 144 ? 196 : 144)); }} />, <Text pointerEvents="none" style={{ left: 360, top: 180, fill: "#ffffff", font: "900 28px sans-serif", textAlign: "center", }} > SIGNALS FLOW TO RECEIVERS </Text>, <Text pointerEvents="none" style={{ left: 360, top: 222, fill: "#f7c948", font: "700 15px monospace", textAlign: "center", }} > {signalSummary} </Text>, <Text pointerEvents="none" style={{ left: 360, top: 264, fill: "#ffffff", font: "600 13px monospace", textAlign: "center", }} > CLICK TO UPDATE THE WRITABLE SIGNAL </Text>, ];}
export default function mount(canvas: HTMLCanvasElement): PibblController { return pibbl(canvas, <SignalCard />);}Documentation version
Section titled “Documentation version”Documentation built with @pibbl/core 0.0.2, revision c321a83. ALPHA — NOT FOR PRODUCTION USE.