/// /** * TEST PATTERN: Pattern Composition - Index-Based Removal * * HYPOTHESIS TEST: Is the aliasing bug causing issues with .equals()? * * This version uses INDEX-BASED removal instead of .equals() to see * if the bug goes away. If it does, it confirms .equals() / cell identity * is the root cause. */ import { Cell, computed, Default, derive, handler, ifElse, NAME, pattern, UI, } from "commontools"; interface ShoppingItem { title: string; done: Default; category: Default; } // Sub-pattern 1: Basic list view interface BasicListInput { items: Cell; } const BasicList = pattern(({ items }) => { // INDEX-BASED removal - no .equals() needed const removeItemByIndex = handler< unknown, { items: Cell; index: number } >( (_event, { items: itemsList, index }) => { const current = itemsList.get(); if (index >= 0 && index < current.length) { itemsList.set(current.toSpliced(index, 1)); } }, ); const addItem = handler< { detail: { message: string } }, { items: Cell } >( ({ detail }, { items: itemsList }) => { const input = detail?.message?.trim(); if (input) { const [title, category = "Uncategorized"] = input.split(":"); itemsList.push({ title: title.trim(), done: false, category: category.trim(), }); } }, ); return { [NAME]: "Basic Shopping List (Index)", [UI]: (

Basic List View (Index-Based)

{items.map((item, index) => (
(item.done ? { textDecoration: "line-through" } : {}), )} > [{index}] {item.title} ({item.category}) x
))}
), items, }; }); // Sub-pattern 2: Categorized view - ALSO index-based interface CategoryListInput { items: Cell; } const CategoryList = pattern(({ items }) => { const categories = derive( { items }, ({ items: itemsArray }: { items: ShoppingItem[] }) => { const cats = new Set(); for (const item of itemsArray) { cats.add(item.category || "Uncategorized"); } return Array.from(cats).sort(); }, ); // INDEX-BASED removal - no .equals() needed const removeItemByIndex = handler< unknown, { items: Cell; index: number } >( (_event, { items: itemsList, index }) => { const current = itemsList.get(); if (index >= 0 && index < current.length) { itemsList.set(current.toSpliced(index, 1)); } }, ); return { [NAME]: "Shopping List by Category (Index)", [UI]: (

Category List View (Index-Based)

{categories.map((category) => (

{category}

{items.map((item, index) => ifElse( computed(() => (item.category || "Uncategorized") === category),
(item.done ? { textDecoration: "line-through" } : {}) )} > [{index}] {item.title} x
, null, ) )}
))}
), items, }; }); // Main pattern interface ComposedInput { items: Default< ShoppingItem[], [ { title: "Milk"; done: false; category: "Dairy" }, { title: "Bread"; done: false; category: "Bakery" }, { title: "Cheese"; done: true; category: "Dairy" }, ] >; } export default pattern(({ items }) => { const basicView = BasicList({ items }); const categoryView = CategoryList({ items }); return { [NAME]: "Test: Index-Based Removal (Aliasing Test)", [UI]: (

Index-Based Removal Test

Hypothesis Test:{" "} Does using index-based removal (instead of .equals()) fix the bug where items disappear from one view but not the other?

  1. Add items, check/uncheck them
  2. Delete items from either view
  3. If both views stay in sync → .equals() / aliasing is the bug
  4. If still broken → deeper reactivity issue
{basicView}
{categoryView}
), items, }; });