# Pattern Debugging Guide Quick error reference and debugging workflows. For detailed explanations, see linked docs. ## Quick Error Reference | Error Message | Cause | Fix | |---------------|-------|-----| | "Property 'set' does not exist" | Missing `Cell<>` in signature | Add `Cell` for write access ([TYPES](TYPES_AND_SCHEMAS.md)) | | "Property X does not exist on type 'OpaqueRef\'" | Missing type in `.map()` | Add `OpaqueRef` annotation ([TYPES](TYPES_AND_SCHEMAS.md)) | | "Type 'string' is not assignable to type 'CSSProperties'" | String style on HTML element | Use object syntax `style={{ ... }}` ([COMPONENTS](COMPONENTS.md)) | | "Type 'OpaqueRef\' is not assignable to 'Cell\'" | Binding whole item, not property | Bind `item.done`, not `item` | | Using `OpaqueRef` in Output for handlers | Should use `Stream` | Use `Stream` for handlers ([TYPES](TYPES_AND_SCHEMAS.md)) | | "ReadOnlyAddressError" | onClick inside computed() | Move button outside, use disabled ([see below](#onclick-inside-computed)) | | Charm hangs, never renders | ifElse with composed pattern cell | Use local computed cell ([see below](#ifelse-with-composed-pattern-cells)) | | Data not updating | Missing `$` prefix or wrong event | Use `$checked`, `$value` ([COMPONENTS](COMPONENTS.md)) | | Filtered list not updating | Need computed() | Wrap in `computed()` ([CELLS](CELLS_AND_REACTIVITY.md)) | | lift() returns 0/empty | Passing cell directly to lift() | Use `computed()` or pass as object param ([see below](#lift-returns-staleempty-data)) | | Handler binding: unknown property | Passing event data at binding time | Use inline handler for test buttons ([see below](#handler-binding-error-unknown-property)) | | Stream.subscribe doesn't exist | Using Stream.of()/subscribe() | Bound handler IS the stream ([see below](#streamof--subscribe-dont-exist)) | | Can't access variable in nested scope | Variable scoping limitation | Pre-compute grouped data or use lift() with explicit params ([see below](#variable-scoping-in-reactive-contexts)) | | "Cannot access cell via closure" | Using lift() with closure | Pass all reactive deps as params to lift() ([CELLS](CELLS_AND_REACTIVITY.md#lift-and-closure-limitations)) | | CLI `get` returns stale computed values | `charm set` doesn't trigger recompute | Run `charm step` after `set` to trigger re-evaluation ([see below](#stale-computed-values-after-charm-set)) | --- ## Common Gotchas These issues compile without errors but fail at runtime. ### onClick Inside computed() **Error:** "ReadOnlyAddressError: Cannot write to read-only address" ```typescript // ❌ Buttons inside computed() fail when clicked {computed(() => showAdd ? Add : null )} // ✅ Move button outside, use disabled attribute !showAdd)}> Add // ✅ Or use ifElse instead of computed {ifElse(showAdd, Add, null)} ``` **Why:** `computed()` creates read-only inline data addresses. Always render buttons at the top level and control visibility with `disabled`. ### ifElse with Composed Pattern Cells **Symptom:** Charm never renders, no errors, blank UI ```typescript // ❌ May hang - cell from composed pattern const showDetails = subPattern.isExpanded; {ifElse(showDetails,
Details
, null)} // ✅ Use local computed cell const showDetails = computed(() => subPattern.isExpanded); {ifElse(showDetails,
Details
, null)} ``` ### Conditional Rendering with Ternaries ```typescript // ❌ Ternaries don't work for elements {show ?
Content
: null} // ✅ Use ifElse() {ifElse(show,
Content
, null)} // ✅ Ternaries ARE fine for attributes {title} ``` ### lift() Returns Stale/Empty Data **Symptom:** `lift()` returns 0, empty object, or stale values even when the source cell has data. ```typescript // ❌ WRONG: Passing cell directly to lift() const calcTotal = lift((expenses: Expense[]): number => { return expenses.reduce((sum, e) => sum + e.amount, 0); }); const total = calcTotal(expenses); // Returns 0! // ✅ CORRECT: Use computed() instead const total = computed(() => { const exp = expenses.get(); return exp.reduce((sum, e) => sum + e.amount, 0); }); // ✅ CORRECT: If using lift(), pass as object parameter const calcTotal = lift((args: { expenses: Expense[] }): number => { return args.expenses.reduce((sum, e) => sum + e.amount, 0); }); const total = calcTotal({ expenses }); ``` **Why:** `lift()` creates a new frame, and cells cannot be accessed via closure across frames. `computed()` gets automatic closure extraction by the CTS transformer; `lift()` does not. Use `computed()` by default in patterns. ### Handler Binding Error: Unknown Property **Error:** `Object literal may only specify known properties, and 'X' does not exist in type 'Opaque<{ state: unknown; }>'` **Symptom:** Trying to pass event data when binding a handler. ```typescript // ❌ WRONG: Passing event data at binding time const addItem = handler< { title: string }, // Event type { items: Cell } // State type >(({ title }, { items }) => { items.push({ title }); });