Debug Pattern
Use the cf skill, or read skills/cf/SKILL.md, if debugging deployment or
piece issues.
Read First
docs/development/debugging/workflow.md- 5-step debugging processdocs/development/debugging/README.md- Error reference matrixdocs/common/concepts/reactivity.mdanddocs/common/patterns/new-cells.md— as mandated by pattern-dev; re-consult for Cell, Writable, or reactive-value failures
Process
Check TypeScript errors:
deno task cf check pattern.tsx --no-runMatch error to documentation:
- Read the error message carefully
- Check
docs/development/debugging/README.mdfor matching errors
Check gotchas:
docs/development/debugging/gotchas/quick.md- consolidated quick gotchas, one anchor per error (e.g.#filter-map-find-is-not-a-function,#onclick-inside-computed)docs/development/debugging/gotchas/reactive-reference-outside-context.mddocs/development/debugging/gotchas/handler-inside-pattern.mddocs/development/debugging/gotchas/immediate-event-invocation.md
Simplify to minimal reproduction:
- Comment out code until error disappears
- Add back piece by piece to find root cause
Fix and verify:
- Apply fix
- Run tests to confirm
Common Issues
Handler defined inside pattern body:
- Move handler() to module scope
- Only bind it inside pattern:
onClick={myHandler({ state })}
Type errors with Writable/Default:
- Check if field needs write access → use Writable<>
- Check if field could be undefined → use Default<T, value>
new Cell() only accepts static data:
- A reactive value (input prop, mapped field, computed value) was passed into
new Writable()/new Cell(). See the static-only rule and field-decision guidance in the pattern-implement skill, plusdocs/common/patterns/new-cells.md
String helper throws, such as .trim() / .replace() / .includes() is not
a function:
- Treat the value as reactive even if TypeScript's surface type is
string - Render the reactive value directly when no derivation is needed
- Move derived labels or branch conditions into
computed()or another valid reactive expression site - See
docs/common/concepts/reactivity.mdand the debugging matrix
Action not triggering:
- Ensure Output type includes action as Stream
- Use .send() not .get() to trigger
raw:map never settles:
- Inspect
.map()bodies for event props that invoke streams or writes during render, such asonClick={stream.send(index)} - Wrap argument-bearing sends in a callback, such as
onClick={() => stream.send(index)} - See
docs/development/debugging/gotchas/immediate-event-invocation.md
Browser UI stays stale after a write:
- Do not assume a documented primitive like
.push()is broken just because tests/CLI and browser behavior diverge - First inspect the actual cell values in browser/runtime tools to confirm whether state changed
- If state changed but the UI did not, isolate the rendering/reactivity issue with a minimal repro before rewriting the mutation style
computed() wrapping JSX — conditional rendering broken:
- Inside
computed()body, ternaries are plain JS (Writable objects always truthy) - Cell bindings (
$value,$checked) insidecomputed()may get positionally mis-resolved - Fix: use bare JSX ternaries, hoist
computed()for data only - See
docs/common/concepts/computed/computed.md
Transient UI state carries over when it should not:
- Check whether navigation, active tab, selected item, modal, filter, or other
ephemeral UI state is unscoped or space scoped, then apply the PerSession
new-tab test from the pattern-dev skill (
PerSession<>for per-session UI state,PerUser<>for user-owned durable state). - Confirm the generated schema and transformed source with
deno task cf check pattern.tsx --show-transformed. - If a value unexpectedly becomes
undefined, check for schema-scope traversal restrictions: a narrower linked value may be unavailable to a broader declared schema. - Scope is data scoping, not authorization; do not use it as a replacement for CFC/IFC policy.
PerAny<> used to fix a scope issue:
- Treat
PerAny<>as a rare inner override under an outerPer*declaration, not as a fourth default scope. - Prefer the concrete inner scope when known:
PerSession<{ item: PerUser<Item>; attachment: PerAny<Attachment> }>is valid when attachments intentionally may come from any scope. - If the inner scope is known, use
PerSpace<>,PerUser<>, orPerSession<>instead.
Runtime Debugging (browser)
When the pattern compiles but behaves wrong at runtime, use the browser console
utilities. Full reference: docs/development/debugging/console-commands.md.
With agent-browser (for automated testing):
# Read piece cell values
agent-browser eval "(async () => {
const v = await commonfabric.readCell();
return JSON.stringify(v).slice(0, 500);
})()"
# Inspect VDOM tree
agent-browser eval "(async () => {
await commonfabric.vdom.dump();
return 'dumped';
})()"
# Detect non-idempotent computations (UI churning)
agent-browser eval "(async () => {
const r = await commonfabric.detectNonIdempotent(5000);
return JSON.stringify({ nonIdempotent: r.nonIdempotent.length, cycles: r.cycles.length });
})()"
# Check for action schema mismatches (handlers doing nothing)
agent-browser eval "JSON.stringify(commonfabric.getLoggerFlagsBreakdown())"
When tests or CLI calls succeed but browser-visible UI stays stale:
- inspect the live piece state first
- confirm whether the handler actually wrote the expected value
- inspect raw links or transformed schema when the bug could be a scope mismatch
- only then decide whether the bug is in mutation semantics, rendering, or browser harness behavior
In browser console (for interactive debugging):
// Read cell values
await commonfabric.readCell();
await commonfabric.readArgumentCell({ path: ["items"] });
// Watch values change during interaction
const cancel = commonfabric.subscribeToCell();
// ... interact ... then cancel()
// VDOM tree
await commonfabric.vdom.dump();
commonfabric.vdom.stats();
// Logger counts and timing
commonfabric.getLoggerCountsBreakdown();
commonfabric.getTimingStatsBreakdown();
// Non-idempotent detection
await commonfabric.detectNonIdempotent();
Done When
- Root cause identified
- Error fixed
- Tests pass again — or, if no tests exist yet,
deno task cf checksucceeds and the failing repro behaves correctly