-
Notifications
You must be signed in to change notification settings - Fork 11
Update documentation to make a particular pitfall less likely. #1998
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1 issue found across 2 files
Prompt for AI agents (all 1 issues)
Understand the root cause of the following 1 issues and fix them.
<file name="docs/common/PATTERNS.md">
<violation number="1" location="docs/common/PATTERNS.md:688">
The "correct" example references `randomId` without defining it, so the snippet will throw a reference error if copied. Please declare `randomId` (e.g., `const randomId = Math.random().toString(36).substring(2, 10);`) before using it.</violation>
</file>
React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.
docs/common/PATTERNS.md
Outdated
| undefined, | ||
| ({ charm, chatsList, isInitialized }) => { | ||
| if (!isInitialized.get()) { | ||
| chatsList.push({ [ID]: randomId, local_id: randomId, charm }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "correct" example references randomId without defining it, so the snippet will throw a reference error if copied. Please declare randomId (e.g., const randomId = Math.random().toString(36).substring(2, 10);) before using it.
Prompt for AI agents
Address the following comment on docs/common/PATTERNS.md at line 688:
<comment>The "correct" example references `randomId` without defining it, so the snippet will throw a reference error if copied. Please declare `randomId` (e.g., `const randomId = Math.random().toString(36).substring(2, 10);`) before using it.</comment>
<file context>
@@ -662,6 +662,52 @@ const addItem = handler<
+ undefined,
+ ({ charm, chatsList, isInitialized }) => {
+ if (!isInitialized.get()) {
+ chatsList.push({ [ID]: randomId, local_id: randomId, charm });
+ isInitialized.set(true);
+ }
</file context>
docs/common/HANDLERS.md
Outdated
| if (!isInitialized.get()) { | ||
| // ✅ CORRECT: .push() happens inside lift, not handler | ||
| patternsList.push({ | ||
| [ID]: Math.random().toString(36).substring(2, 10), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This way of using [ID] is generally not needed here (and the Math.random() specifically an anti-pattern once we go for actual causal ids). We should fix that in the upstream example as well. @bfollington
docs/common/HANDLERS.md
Outdated
|
|
||
| // ✅ CORRECT: Wrap the push operation in a lift function | ||
| const storePattern = lift( | ||
| toSchema<{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might no longer be necessary since schemas are now derived for lift as well.
Would lift<{ charm: any, patternsList: Cell<Entry[]>, isInitialized: Cell<boolean>}, undefined>(({ charm, patternsList, isInitialized }) => { ....work?
docs/common/HANDLERS.md
Outdated
| // ✅ CORRECT: Return the lift function call | ||
| return storePattern({ | ||
| charm: pattern, | ||
| patternsList: patternsList as unknown as OpaqueRef<Entry[]>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this casting is necessary anymore either, and if it is, the big type refactor that is pending ought to fix this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed changes from recent commits (found 14 issues).
14 issues found across 81 files
Prompt for AI agents (all 14 issues)
Understand the root cause of the following 14 issues and fix them.
<file name="packages/ts-transformers/test/fixtures/ast-transform/builder-conditional.expected.tsx">
<violation number="1" location="packages/ts-transformers/test/fixtures/ast-transform/builder-conditional.expected.tsx:24">
The derive callback destructures a non-existent `state` property from the recipe state object, so the predicate becomes undefined and the UI will always take the Non-positive branch. Replace the callback to accept the state object directly.</violation>
</file>
<file name="packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-operations.expected.tsx">
<violation number="1" location="packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-operations.expected.tsx:9">
`__ctHelpers.derive` invokes the callback with the raw cell value, so destructuring `({ count })` leaves `count` undefined and produces wrong results (e.g., NaN). Please keep the parameter as the bare value; the same fix is needed for the Double/Total lines.</violation>
</file>
<file name="packages/ts-transformers/src/utils/identifiers.ts">
<violation number="1" location="packages/ts-transformers/src/utils/identifiers.ts:21">
`isSafeIdentifierText` walks `name` one UTF-16 code unit at a time, so astral-plane identifiers (e.g. 𠮷) fail the `ts.isIdentifierPart` check and the transformer needlessly renames them. Iterate by Unicode code point instead of code unit to preserve valid identifiers.</violation>
</file>
<file name="packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-cell-map.expected.tsx">
<violation number="1" location="packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-cell-map.expected.tsx:103">
Deriving `typedCellRef` now destructures from an object, but the derive helper passes the array itself, so the empty-state condition always reads `true` and hides the charm list. Please keep the original `typedCellRef => ...` callback.</violation>
<violation number="2" location="packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-cell-map.expected.tsx:106">
The derived index callback now destructures an object, but `index` is a number. This makes the `Go to Charm` label render `NaN`. Please revert to the simple `index => index + 1` callback.</violation>
<violation number="3" location="packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-cell-map.expected.tsx:108">
Deriving both the list index and charm now destructures from objects even though the derive helper passes the value itself. This makes the charm label render `NaN` and throws when evaluating `charm[NAME]`. Please restore the original callbacks.</violation>
</file>
<file name="packages/ts-transformers/test/fixtures/closures/map-outer-element.input.tsx">
<violation number="1" location="packages/ts-transformers/test/fixtures/closures/map-outer-element.input.tsx:15">
Each JSX element returned from state.items.map needs a stable key so React can reconcile list updates correctly.</violation>
</file>
<file name="packages/ts-transformers/test/fixtures/jsx-expressions/map-single-capture.expected.tsx">
<violation number="1" location="packages/ts-transformers/test/fixtures/jsx-expressions/map-single-capture.expected.tsx:10">
`derive` receives the array emitted by the `people` cell. Destructuring it as `{ people }` makes the inner `people` reference `undefined`, so `people.length` and `people.map(...)` will throw at runtime. Please keep the callback parameter as the array value.</violation>
</file>
<file name="packages/ts-transformers/test/fixtures/closures/map-computed-alias-side-effect.expected.tsx">
<violation number="1" location="packages/ts-transformers/test/fixtures/closures/map-computed-alias-side-effect.expected.tsx:45">
Using `nextKey()` inside the destructuring pattern causes subsequent renders to search for ever-changing property names (e.g. `value-1`, `value-2`), so `amount` becomes `undefined` after the first pass. Please keep the key stable across renders (e.g. restore the previous derive-based lookup).</violation>
</file>
<file name="packages/deno-web-test/utils.ts">
<violation number="1" location="packages/deno-web-test/utils.ts:84">
`Deno.copyFile` will throw if `dest`'s parent directory is missing. The previous `@std/fs` copy helper created the directory tree, so this regression will break includes that target nested paths. Please ensure the destination directory exists before copying.</violation>
</file>
<file name="packages/ts-transformers/test/fixtures/jsx-expressions/method-chains.expected.tsx">
<violation number="1" location="packages/ts-transformers/test/fixtures/jsx-expressions/method-chains.expected.tsx:237">
__ctHelpers.derive receives the primitive string value for name, but the callback now destructures ({ name }) and then calls name.trim(). Because the input isn’t wrapped in an object with a name property, name is undefined at runtime and this path throws. Please keep providing name as an object so the callback gets a defined string.</violation>
</file>
<file name="packages/ts-transformers/test/fixtures/jsx-expressions/optional-chain-predicate.expected.tsx">
<violation number="1" location="packages/ts-transformers/test/fixtures/jsx-expressions/optional-chain-predicate.expected.tsx:8">
Destructuring `{ items }` here breaks the predicate: the callback now receives `undefined`, so the "No items" message renders even when items exist. Revert to using the array parameter directly.</violation>
</file>
<file name="packages/ts-transformers/test/fixtures/jsx-expressions/map-array-length-conditional.expected.tsx">
<violation number="1" location="packages/ts-transformers/test/fixtures/jsx-expressions/map-array-length-conditional.expected.tsx:7">
`derive` passes the array value from the `list` cell into the callback. Destructuring `{ list }` from that array will throw at runtime, breaking the fixture. Please keep the callback parameter as the array itself.</violation>
</file>
<file name="packages/ts-transformers/src/transformers/builtins/derive.ts">
<violation number="1" location="packages/ts-transformers/src/transformers/builtins/derive.ts:212">
When only capture expressions are present, this returns the raw expression, so derive is invoked as `derive(pattern, ({ pattern }) => ...)`. The parameter still destructures an object, but we now pass a plain value, so the binding becomes undefined and breaks the transform.</violation>
</file>
React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.
| [NAME]: state.label, | ||
| [UI]: (<section> | ||
| {__ctHelpers.ifElse(__ctHelpers.derive({ state, state_count: state.count }, ({ state: state, state_count: _v2 }) => state && _v2 > 0), <p>Positive</p>, <p>Non-positive</p>)} | ||
| {__ctHelpers.ifElse(__ctHelpers.derive(state, ({ state }) => state && state.count > 0), <p>Positive</p>, <p>Non-positive</p>)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The derive callback destructures a non-existent state property from the recipe state object, so the predicate becomes undefined and the UI will always take the Non-positive branch. Replace the callback to accept the state object directly.
Prompt for AI agents
Address the following comment on packages/ts-transformers/test/fixtures/ast-transform/builder-conditional.expected.tsx at line 24:
<comment>The derive callback destructures a non-existent `state` property from the recipe state object, so the predicate becomes undefined and the UI will always take the Non-positive branch. Replace the callback to accept the state object directly.</comment>
<file context>
@@ -21,7 +21,7 @@ export default recipe({
[NAME]: state.label,
[UI]: (<section>
- {__ctHelpers.ifElse(__ctHelpers.derive({ state, state_count: state.count }, ({ state: state, state_count: _v2 }) => state && _v2 > 0), <p>Positive</p>, <p>Non-positive</p>)}
+ {__ctHelpers.ifElse(__ctHelpers.derive(state, ({ state }) => state && state.count > 0), <p>Positive</p>, <p>Non-positive</p>)}
</section>),
};
</file context>
| {__ctHelpers.ifElse(__ctHelpers.derive(state, ({ state }) => state && state.count > 0), <p>Positive</p>, <p>Non-positive</p>)} | |
| {__ctHelpers.ifElse(__ctHelpers.derive(state, (state) => state && state.count > 0), <p>Positive</p>, <p>Non-positive</p>)} |
| <p>Next: {__ctHelpers.derive(count, count => count + 1)}</p> | ||
| <p>Double: {__ctHelpers.derive(count, count => count * 2)}</p> | ||
| <p>Total: {__ctHelpers.derive(price, price => price * 1.1)}</p> | ||
| <p>Next: {__ctHelpers.derive(count, ({ count }) => count + 1)}</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__ctHelpers.derive invokes the callback with the raw cell value, so destructuring ({ count }) leaves count undefined and produces wrong results (e.g., NaN). Please keep the parameter as the bare value; the same fix is needed for the Double/Total lines.
Prompt for AI agents
Address the following comment on packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-operations.expected.tsx at line 9:
<comment>`__ctHelpers.derive` invokes the callback with the raw cell value, so destructuring `({ count })` leaves `count` undefined and produces wrong results (e.g., NaN). Please keep the parameter as the bare value; the same fix is needed for the Double/Total lines.</comment>
<file context>
@@ -6,9 +6,9 @@ export default recipe("OpaqueRefOperations", (_state) => {
- <p>Next: {__ctHelpers.derive(count, count => count + 1)}</p>
- <p>Double: {__ctHelpers.derive(count, count => count * 2)}</p>
- <p>Total: {__ctHelpers.derive(price, price => price * 1.1)}</p>
+ <p>Next: {__ctHelpers.derive(count, ({ count }) => count + 1)}</p>
+ <p>Double: {__ctHelpers.derive(count, ({ count }) => count * 2)}</p>
+ <p>Total: {__ctHelpers.derive(price, ({ price }) => price * 1.1)}</p>
</file context>
| <p>Next: {__ctHelpers.derive(count, ({ count }) => count + 1)}</p> | |
| <p>Next: {__ctHelpers.derive(count, count => count + 1)}</p> |
| return false; | ||
| } | ||
| for (let i = 1; i < name.length; i++) { | ||
| const code = name.codePointAt(i)!; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isSafeIdentifierText walks name one UTF-16 code unit at a time, so astral-plane identifiers (e.g. 𠮷) fail the ts.isIdentifierPart check and the transformer needlessly renames them. Iterate by Unicode code point instead of code unit to preserve valid identifiers.
Prompt for AI agents
Address the following comment on packages/ts-transformers/src/utils/identifiers.ts at line 21:
<comment>`isSafeIdentifierText` walks `name` one UTF-16 code unit at a time, so astral-plane identifiers (e.g. 𠮷) fail the `ts.isIdentifierPart` check and the transformer needlessly renames them. Iterate by Unicode code point instead of code unit to preserve valid identifiers.</comment>
<file context>
@@ -0,0 +1,152 @@
+ return false;
+ }
+ for (let i = 1; i < name.length; i++) {
+ const code = name.codePointAt(i)!;
+ if (!ts.isIdentifierPart(code, ts.ScriptTarget.ESNext)) {
+ return false;
</file context>
| Go to Charm {__ctHelpers.derive(index, ({ index }) => index + 1)} | ||
| </ct-button> | ||
| <span>Charm {__ctHelpers.derive(index, index => index + 1)}: {__ctHelpers.derive(charm, charm => charm[NAME] || "Unnamed")}</span> | ||
| <span>Charm {__ctHelpers.derive(index, ({ index }) => index + 1)}: {__ctHelpers.derive(charm, ({ charm }) => charm[NAME] || "Unnamed")}</span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deriving both the list index and charm now destructures from objects even though the derive helper passes the value itself. This makes the charm label render NaN and throws when evaluating charm[NAME]. Please restore the original callbacks.
Prompt for AI agents
Address the following comment on packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-cell-map.expected.tsx at line 108:
<comment>Deriving both the list index and charm now destructures from objects even though the derive helper passes the value itself. This makes the charm label render `NaN` and throws when evaluating `charm[NAME]`. Please restore the original callbacks.</comment>
<file context>
@@ -100,12 +100,12 @@ export default recipe("Charms Launcher", () => {
+ Go to Charm {__ctHelpers.derive(index, ({ index }) => index + 1)}
</ct-button>
- <span>Charm {__ctHelpers.derive(index, index => index + 1)}: {__ctHelpers.derive(charm, charm => charm[NAME] || "Unnamed")}</span>
+ <span>Charm {__ctHelpers.derive(index, ({ index }) => index + 1)}: {__ctHelpers.derive(charm, ({ charm }) => charm[NAME] || "Unnamed")}</span>
</li>))}
</ul>)}
</file context>
| <span>Charm {__ctHelpers.derive(index, ({ index }) => index + 1)}: {__ctHelpers.derive(charm, ({ charm }) => charm[NAME] || "Unnamed")}</span> | |
| <span>Charm {__ctHelpers.derive(index, index => index + 1)}: {__ctHelpers.derive(charm, charm => charm[NAME] || "Unnamed")}</span> |
| {typedCellRef.map((charm: any, index: number) => (<li> | ||
| <ct-button onClick={goToCharm({ charm })}> | ||
| Go to Charm {__ctHelpers.derive(index, index => index + 1)} | ||
| Go to Charm {__ctHelpers.derive(index, ({ index }) => index + 1)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The derived index callback now destructures an object, but index is a number. This makes the Go to Charm label render NaN. Please revert to the simple index => index + 1 callback.
Prompt for AI agents
Address the following comment on packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-cell-map.expected.tsx at line 106:
<comment>The derived index callback now destructures an object, but `index` is a number. This makes the `Go to Charm` label render `NaN`. Please revert to the simple `index => index + 1` callback.</comment>
<file context>
@@ -100,12 +100,12 @@ export default recipe("Charms Launcher", () => {
{typedCellRef.map((charm: any, index: number) => (<li>
<ct-button onClick={goToCharm({ charm })}>
- Go to Charm {__ctHelpers.derive(index, index => index + 1)}
+ Go to Charm {__ctHelpers.derive(index, ({ index }) => index + 1)}
</ct-button>
- <span>Charm {__ctHelpers.derive(index, index => index + 1)}: {__ctHelpers.derive(charm, charm => charm[NAME] || "Unnamed")}</span>
</file context>
| Go to Charm {__ctHelpers.derive(index, ({ index }) => index + 1)} | |
| Go to Charm {__ctHelpers.derive(index, index => index + 1)} |
| } else if (stat.isDirectory) { | ||
| await copyDir(src, dest); | ||
| } else { | ||
| await Deno.copyFile(src, dest); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deno.copyFile will throw if dest's parent directory is missing. The previous @std/fs copy helper created the directory tree, so this regression will break includes that target nested paths. Please ensure the destination directory exists before copying.
Prompt for AI agents
Address the following comment on packages/deno-web-test/utils.ts at line 84:
<comment>`Deno.copyFile` will throw if `dest`'s parent directory is missing. The previous `@std/fs` copy helper created the directory tree, so this regression will break includes that target nested paths. Please ensure the destination directory exists before copying.</comment>
<file context>
@@ -65,3 +64,33 @@ export function summarize(results: TestFileResults[]): Summary {
+ } else if (stat.isDirectory) {
+ await copyDir(src, dest);
+ } else {
+ await Deno.copyFile(src, dest);
+ }
+}
</file context>
| }, | ||
| required: ["element", "params"] | ||
| } as const satisfies __ctHelpers.JSONSchema, ({ element, params: {} }) => (<li>{__ctHelpers.derive(element, element => element.trim().toLowerCase().replace(" ", "-"))}</li>)), {})} | ||
| } as const satisfies __ctHelpers.JSONSchema, ({ element: name, params: {} }) => (<li>{__ctHelpers.derive(name, ({ name }) => name.trim().toLowerCase().replace(" ", "-"))}</li>)), {})} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__ctHelpers.derive receives the primitive string value for name, but the callback now destructures ({ name }) and then calls name.trim(). Because the input isn’t wrapped in an object with a name property, name is undefined at runtime and this path throws. Please keep providing name as an object so the callback gets a defined string.
Prompt for AI agents
Address the following comment on packages/ts-transformers/test/fixtures/jsx-expressions/method-chains.expected.tsx at line 237:
<comment>__ctHelpers.derive receives the primitive string value for name, but the callback now destructures ({ name }) and then calls name.trim(). Because the input isn’t wrapped in an object with a name property, name is undefined at runtime and this path throws. Please keep providing name as an object so the callback gets a defined string.</comment>
<file context>
@@ -192,44 +234,63 @@ export default recipe({
},
required: ["element", "params"]
- } as const satisfies __ctHelpers.JSONSchema, ({ element, params: {} }) => (<li>{__ctHelpers.derive(element, element => element.trim().toLowerCase().replace(" ", "-"))}</li>)), {})}
+ } as const satisfies __ctHelpers.JSONSchema, ({ element: name, params: {} }) => (<li>{__ctHelpers.derive(name, ({ name }) => name.trim().toLowerCase().replace(" ", "-"))}</li>)), {})}
</ul>
</file context>
| } as const satisfies __ctHelpers.JSONSchema, ({ element: name, params: {} }) => (<li>{__ctHelpers.derive(name, ({ name }) => name.trim().toLowerCase().replace(" ", "-"))}</li>)), {})} | |
| } as const satisfies __ctHelpers.JSONSchema, ({ element: name, params: {} }) => (<li>{__ctHelpers.derive({ state: { name } }, ({ state }) => state.name.trim().toLowerCase().replace(" ", "-"))}</li>)), {})} |
| [NAME]: "Optional chain predicate", | ||
| [UI]: (<div> | ||
| {__ctHelpers.derive(items, items => !items?.length && <span>No items</span>)} | ||
| {__ctHelpers.derive(items, ({ items }) => !items?.length && <span>No items</span>)} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Destructuring { items } here breaks the predicate: the callback now receives undefined, so the "No items" message renders even when items exist. Revert to using the array parameter directly.
Prompt for AI agents
Address the following comment on packages/ts-transformers/test/fixtures/jsx-expressions/optional-chain-predicate.expected.tsx at line 8:
<comment>Destructuring `{ items }` here breaks the predicate: the callback now receives `undefined`, so the "No items" message renders even when items exist. Revert to using the array parameter directly.</comment>
<file context>
@@ -5,7 +5,7 @@ export default recipe("Optional Chain Predicate", () => {
[NAME]: "Optional chain predicate",
[UI]: (<div>
- {__ctHelpers.derive(items, items => !items?.length && <span>No items</span>)}
+ {__ctHelpers.derive(items, ({ items }) => !items?.length && <span>No items</span>)}
</div>),
};
</file context>
| {__ctHelpers.derive(items, ({ items }) => !items?.length && <span>No items</span>)} | |
| {__ctHelpers.derive(items, items => !items?.length && <span>No items</span>)} |
| return { | ||
| [UI]: (<div> | ||
| {__ctHelpers.derive(list, list => list.length > 0 && (<div> | ||
| {__ctHelpers.derive(list, ({ list }) => list.length > 0 && (<div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
derive passes the array value from the list cell into the callback. Destructuring { list } from that array will throw at runtime, breaking the fixture. Please keep the callback parameter as the array itself.
Prompt for AI agents
Address the following comment on packages/ts-transformers/test/fixtures/jsx-expressions/map-array-length-conditional.expected.tsx at line 7:
<comment>`derive` passes the array value from the `list` cell into the callback. Destructuring `{ list }` from that array will throw at runtime, breaking the fixture. Please keep the callback parameter as the array itself.</comment>
<file context>
@@ -4,7 +4,7 @@ export default recipe("MapArrayLengthConditional", (_state) => {
return {
[UI]: (<div>
- {__ctHelpers.derive(list, list => list.length > 0 && (<div>
+ {__ctHelpers.derive(list, ({ list }) => list.length > 0 && (<div>
{list.map((name) => (<span>{name}</span>))}
</div>))}
</file context>
| {__ctHelpers.derive(list, ({ list }) => list.length > 0 && (<div> | |
| {__ctHelpers.derive(list, (list) => list.length > 0 && (<div> |
| if (!first.done) { | ||
| const node = first.value; | ||
| if (node.expression && node.properties.size === 0) { | ||
| return [node.expression]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When only capture expressions are present, this returns the raw expression, so derive is invoked as derive(pattern, ({ pattern }) => ...). The parameter still destructures an object, but we now pass a plain value, so the binding becomes undefined and breaks the transform.
Prompt for AI agents
Address the following comment on packages/ts-transformers/src/transformers/builtins/derive.ts at line 212:
<comment>When only capture expressions are present, this returns the raw expression, so derive is invoked as `derive(pattern, ({ pattern }) => ...)`. The parameter still destructures an object, but we now pass a plain value, so the binding becomes undefined and breaks the transform.</comment>
<file context>
@@ -116,23 +175,48 @@ function createParameterForEntries(
+ if (!first.done) {
+ const node = first.value;
+ if (node.expression && node.properties.size === 0) {
+ return [node.expression];
+ }
+ }
</file context>
| return [node.expression]; | |
| return [factory.createObjectLiteralExpression(properties, false)]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed changes from recent commits (found 18 issues).
18 issues found across 85 files
Prompt for AI agents (all 18 issues)
Understand the root cause of the following 18 issues and fix them.
<file name="packages/ts-transformers/test/opaque-ref/map-callbacks.test.ts">
<violation number="1" location="packages/ts-transformers/test/opaque-ref/map-callbacks.test.ts:74">
This assertion no longer verifies that the negated `ifElse` condition is wrapped in `__ctHelpers.derive`, so the test could pass even if the derive call disappears. Please assert the entire `__ctHelpers.derive(state.charms.length, ({ state }) => !state.charms.length)` snippet to keep coverage intact.</violation>
</file>
<file name="packages/ts-transformers/test/fixtures/jsx-expressions/optional-chain-captures.input.tsx">
<violation number="1" location="packages/ts-transformers/test/fixtures/jsx-expressions/optional-chain-captures.input.tsx:19">
Each element returned from state.items.map should have a stable key prop so React can reconcile the list correctly. Please add an appropriate key to the <span>.</violation>
</file>
<file name="packages/ts-transformers/docs/hierarchical-params-spec.md">
<violation number="1" location="packages/ts-transformers/docs/hierarchical-params-spec.md:120">
This doc claims the transformer now caches computed property aliases via `derive(...)`, but the actual fixtures still destructure `[nextKey()]` directly, so the side-effectful call runs on every invocation. Please update the documentation (or land the caching change) so it matches current behavior.</violation>
</file>
<file name="packages/ts-transformers/test/fixtures/jsx-expressions/map-array-length-conditional.expected.tsx">
<violation number="1" location="packages/ts-transformers/test/fixtures/jsx-expressions/map-array-length-conditional.expected.tsx:7">
The derive callback now destructures `{ list }`, but the input provided is the array from the `list` cell. That destructuring makes `list` undefined, so `list.length` throws and the UI breaks. Please keep the callback receiving the array itself.</violation>
</file>
<file name="packages/ts-transformers/test/fixtures/jsx-expressions/method-chains.expected.tsx">
<violation number="1" location="packages/ts-transformers/test/fixtures/jsx-expressions/method-chains.expected.tsx:237">
`__ctHelpers.derive` receives the primitive `name`, but the callback now destructures `{ name }`, so at runtime it throws because strings aren’t objects. Change the argument to pass an object if you need to destructure.</violation>
</file>
<file name="packages/ts-transformers/test/fixtures/jsx-expressions/optional-chain-predicate.expected.tsx">
<violation number="1" location="packages/ts-transformers/test/fixtures/jsx-expressions/optional-chain-predicate.expected.tsx:8">
The derive callback should receive the cell value (the items array). Destructuring into `{ items }` makes `items` undefined, so the predicate always returns true and the UI incorrectly shows “No items”.</violation>
</file>
<file name="packages/ts-transformers/test/fixtures/jsx-expressions/map-single-capture.expected.tsx">
<violation number="1" location="packages/ts-transformers/test/fixtures/jsx-expressions/map-single-capture.expected.tsx:10">
The derive callback now destructures `{ people }`, but derive passes the people array itself, so the destructured `people` becomes undefined and `people.length` throws. Please keep the callback parameter as the array.</violation>
</file>
<file name="packages/ts-transformers/test/fixtures/jsx-expressions/optional-element-access.expected.tsx">
<violation number="1" location="packages/ts-transformers/test/fixtures/jsx-expressions/optional-element-access.expected.tsx:8">
The derive callback now destructures `{ list }`, but derive(list, f) passes the array value itself, so `list` inside the callback becomes `undefined` and the condition is wrong. Restore the direct parameter so the array contents are checked correctly.</violation>
</file>
<file name="packages/ts-transformers/test/derive/create-derive-call.test.ts">
<violation number="1" location="packages/ts-transformers/test/derive/create-derive-call.test.ts:58">
The derive call prints the fallback parameter alias (e.g. `=> _v2`), not the renamed property `_v1_1`, so this assertion will fail. Update the expected substring to match the actual alias.</violation>
</file>
<file name="packages/ts-transformers/src/utils/identifiers.ts">
<violation number="1" location="packages/ts-transformers/src/utils/identifiers.ts:21">
Iterating with `i++` causes surrogate pairs to be rechecked as separate code units, so astral-plane identifiers (e.g., “𝑥”) are incorrectly rejected; adjust the loop to advance past both units when a code point uses a surrogate pair.</violation>
</file>
<file name="packages/ts-transformers/test/fixtures/ast-transform/builder-conditional.expected.tsx">
<violation number="1" location="packages/ts-transformers/test/fixtures/ast-transform/builder-conditional.expected.tsx:24">
The derive callback destructures `{ state }`, but the argument is the state object itself, so the local `state` becomes `undefined` and the condition always fails.</violation>
</file>
<file name="packages/deno-web-test/utils.ts">
<violation number="1" location="packages/deno-web-test/utils.ts:84">
`Deno.copyFile` will throw if `dirname(dest)` does not already exist, so copying includes to nested destinations will now fail (previously `@std/fs.copy` created those directories). Please ensure the parent directory exists before calling `Deno.copyFile` (in both the plain-file and resolved-symlink branches).</violation>
</file>
<file name="packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-operations.expected.tsx">
<violation number="1" location="packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-operations.expected.tsx:9">
`derive` invokes the callback with the raw cell value, not an object. Destructuring `{ count }` here makes `count` undefined, so the rendered value becomes `NaN`. Please keep the positional parameter.</violation>
<violation number="2" location="packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-operations.expected.tsx:10">
`derive` gives this callback the numeric cell value, so destructuring `{ count }` returns undefined and the output becomes `NaN`. Please keep the simple numeric parameter.</violation>
<violation number="3" location="packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-operations.expected.tsx:11">
`derive` hands this callback the numeric value from `price`, not an object. Destructuring `{ price }` yields undefined, so the total renders as `NaN`. Please keep the positional argument.</violation>
</file>
<file name="packages/ts-transformers/src/transformers/builtins/derive.ts">
<violation number="1" location="packages/ts-transformers/src/transformers/builtins/derive.ts:148">
When there is only a single capture reference, the callback parameter should remain a plain identifier; otherwise derive(foo, ({ foo }) => ...) destructures the value incorrectly and returns undefined.</violation>
</file>
<file name="packages/ts-transformers/src/closures/transformer.ts">
<violation number="1" location="packages/ts-transformers/src/closures/transformer.ts:1017">
Binding each capture root to the object returned by buildHierarchicalParamsValue means methods like state.someMethod() now execute with `this` bound to a synthetic clone rather than the original closure object, breaking instance-dependent logic. Please bind roots to the original expression (or otherwise preserve the original receiver) instead of cloning partial objects.</violation>
</file>
<file name="packages/patterns/on-demand-pattern-creation.tsx">
<violation number="1" location="packages/patterns/on-demand-pattern-creation.tsx:84">
Use `<ct-render $cell={currentView} />` (or lift/derive) to render the selected recipe; inserting the Cell itself just stringifies the object so the chosen view never appears.</violation>
</file>
React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.
| assertStringIncludes( | ||
| output, | ||
| "ifElse(__ctHelpers.derive(state.charms.length, _v1 => !_v1)", | ||
| "({ state }) => !state.charms.length", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assertion no longer verifies that the negated ifElse condition is wrapped in __ctHelpers.derive, so the test could pass even if the derive call disappears. Please assert the entire __ctHelpers.derive(state.charms.length, ({ state }) => !state.charms.length) snippet to keep coverage intact.
Prompt for AI agents
Address the following comment on packages/ts-transformers/test/opaque-ref/map-callbacks.test.ts at line 74:
<comment>This assertion no longer verifies that the negated `ifElse` condition is wrapped in `__ctHelpers.derive`, so the test could pass even if the derive call disappears. Please assert the entire `__ctHelpers.derive(state.charms.length, ({ state }) => !state.charms.length)` snippet to keep coverage intact.</comment>
<file context>
@@ -52,26 +52,26 @@ describe("OpaqueRef map callbacks", () => {
assertStringIncludes(
output,
- "ifElse(__ctHelpers.derive(state.charms.length, _v1 => !_v1)",
+ "({ state }) => !state.charms.length",
);
});
</file context>
| "({ state }) => !state.charms.length", | |
| "__ctHelpers.derive(state.charms.length, ({ state }) => !state.charms.length)", |
| <div> | ||
| <span>{state.maybe?.value}</span> | ||
| {state.items.map((item) => ( | ||
| <span>{item.maybe?.value ?? 0}</span> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Each element returned from state.items.map should have a stable key prop so React can reconcile the list correctly. Please add an appropriate key to the .
Prompt for AI agents
Address the following comment on packages/ts-transformers/test/fixtures/jsx-expressions/optional-chain-captures.input.tsx at line 19:
<comment>Each element returned from state.items.map should have a stable key prop so React can reconcile the list correctly. Please add an appropriate key to the <span>.</comment>
<file context>
@@ -0,0 +1,24 @@
+ <div>
+ <span>{state.maybe?.value}</span>
+ {state.items.map((item) => (
+ <span>{item.maybe?.value ?? 0}</span>
+ ))}
+ </div>
</file context>
| } | ||
| } | ||
| // New output caches once and threads the key through derive | ||
| const __ct_val_key = nextKey(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doc claims the transformer now caches computed property aliases via derive(...), but the actual fixtures still destructure [nextKey()] directly, so the side-effectful call runs on every invocation. Please update the documentation (or land the caching change) so it matches current behavior.
Prompt for AI agents
Address the following comment on packages/ts-transformers/docs/hierarchical-params-spec.md at line 120:
<comment>This doc claims the transformer now caches computed property aliases via `derive(...)`, but the actual fixtures still destructure `[nextKey()]` directly, so the side-effectful call runs on every invocation. Please update the documentation (or land the caching change) so it matches current behavior.</comment>
<file context>
@@ -1,460 +1,205 @@
- }
-}
+// New output caches once and threads the key through derive
+const __ct_val_key = nextKey();
+({ element }) => derive({ element, __ct_val_key }, ({ element, __ct_val_key: key }) => element[key])
```
</file context>
| return { | ||
| [UI]: (<div> | ||
| {__ctHelpers.derive(list, list => list.length > 0 && (<div> | ||
| {__ctHelpers.derive(list, ({ list }) => list.length > 0 && (<div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The derive callback now destructures { list }, but the input provided is the array from the list cell. That destructuring makes list undefined, so list.length throws and the UI breaks. Please keep the callback receiving the array itself.
Prompt for AI agents
Address the following comment on packages/ts-transformers/test/fixtures/jsx-expressions/map-array-length-conditional.expected.tsx at line 7:
<comment>The derive callback now destructures `{ list }`, but the input provided is the array from the `list` cell. That destructuring makes `list` undefined, so `list.length` throws and the UI breaks. Please keep the callback receiving the array itself.</comment>
<file context>
@@ -4,7 +4,7 @@ export default recipe("MapArrayLengthConditional", (_state) => {
return {
[UI]: (<div>
- {__ctHelpers.derive(list, list => list.length > 0 && (<div>
+ {__ctHelpers.derive(list, ({ list }) => list.length > 0 && (<div>
{list.map((name) => (<span>{name}</span>))}
</div>))}
</file context>
| {__ctHelpers.derive(list, ({ list }) => list.length > 0 && (<div> | |
| {__ctHelpers.derive(list, (list) => list.length > 0 && (<div> |
| }, | ||
| required: ["element", "params"] | ||
| } as const satisfies __ctHelpers.JSONSchema, ({ element, params: {} }) => (<li>{__ctHelpers.derive(element, element => element.trim().toLowerCase().replace(" ", "-"))}</li>)), {})} | ||
| } as const satisfies __ctHelpers.JSONSchema, ({ element: name, params: {} }) => (<li>{__ctHelpers.derive(name, ({ name }) => name.trim().toLowerCase().replace(" ", "-"))}</li>)), {})} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__ctHelpers.derive receives the primitive name, but the callback now destructures { name }, so at runtime it throws because strings aren’t objects. Change the argument to pass an object if you need to destructure.
Prompt for AI agents
Address the following comment on packages/ts-transformers/test/fixtures/jsx-expressions/method-chains.expected.tsx at line 237:
<comment>`__ctHelpers.derive` receives the primitive `name`, but the callback now destructures `{ name }`, so at runtime it throws because strings aren’t objects. Change the argument to pass an object if you need to destructure.</comment>
<file context>
@@ -192,44 +234,63 @@ export default recipe({
},
required: ["element", "params"]
- } as const satisfies __ctHelpers.JSONSchema, ({ element, params: {} }) => (<li>{__ctHelpers.derive(element, element => element.trim().toLowerCase().replace(" ", "-"))}</li>)), {})}
+ } as const satisfies __ctHelpers.JSONSchema, ({ element: name, params: {} }) => (<li>{__ctHelpers.derive(name, ({ name }) => name.trim().toLowerCase().replace(" ", "-"))}</li>)), {})}
</ul>
</file context>
| } as const satisfies __ctHelpers.JSONSchema, ({ element: name, params: {} }) => (<li>{__ctHelpers.derive(name, ({ name }) => name.trim().toLowerCase().replace(" ", "-"))}</li>)), {})} | |
| } as const satisfies __ctHelpers.JSONSchema, ({ element: name, params: {} }) => (<li>{__ctHelpers.derive({ name }, ({ name }) => name.trim().toLowerCase().replace(" ", "-"))}</li>)), {})} |
| <p>Double: {__ctHelpers.derive(count, count => count * 2)}</p> | ||
| <p>Total: {__ctHelpers.derive(price, price => price * 1.1)}</p> | ||
| <p>Next: {__ctHelpers.derive(count, ({ count }) => count + 1)}</p> | ||
| <p>Double: {__ctHelpers.derive(count, ({ count }) => count * 2)}</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
derive gives this callback the numeric cell value, so destructuring { count } returns undefined and the output becomes NaN. Please keep the simple numeric parameter.
Prompt for AI agents
Address the following comment on packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-operations.expected.tsx at line 10:
<comment>`derive` gives this callback the numeric cell value, so destructuring `{ count }` returns undefined and the output becomes `NaN`. Please keep the simple numeric parameter.</comment>
<file context>
@@ -6,9 +6,9 @@ export default recipe("OpaqueRefOperations", (_state) => {
- <p>Double: {__ctHelpers.derive(count, count => count * 2)}</p>
- <p>Total: {__ctHelpers.derive(price, price => price * 1.1)}</p>
+ <p>Next: {__ctHelpers.derive(count, ({ count }) => count + 1)}</p>
+ <p>Double: {__ctHelpers.derive(count, ({ count }) => count * 2)}</p>
+ <p>Total: {__ctHelpers.derive(price, ({ price }) => price * 1.1)}</p>
</div>),
</file context>
| <p>Double: {__ctHelpers.derive(count, ({ count }) => count * 2)}</p> | |
| <p>Double: {__ctHelpers.derive(count, count => count * 2)}</p> |
| <p>Next: {__ctHelpers.derive(count, count => count + 1)}</p> | ||
| <p>Double: {__ctHelpers.derive(count, count => count * 2)}</p> | ||
| <p>Total: {__ctHelpers.derive(price, price => price * 1.1)}</p> | ||
| <p>Next: {__ctHelpers.derive(count, ({ count }) => count + 1)}</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
derive invokes the callback with the raw cell value, not an object. Destructuring { count } here makes count undefined, so the rendered value becomes NaN. Please keep the positional parameter.
Prompt for AI agents
Address the following comment on packages/ts-transformers/test/fixtures/jsx-expressions/opaque-ref-operations.expected.tsx at line 9:
<comment>`derive` invokes the callback with the raw cell value, not an object. Destructuring `{ count }` here makes `count` undefined, so the rendered value becomes `NaN`. Please keep the positional parameter.</comment>
<file context>
@@ -6,9 +6,9 @@ export default recipe("OpaqueRefOperations", (_state) => {
- <p>Next: {__ctHelpers.derive(count, count => count + 1)}</p>
- <p>Double: {__ctHelpers.derive(count, count => count * 2)}</p>
- <p>Total: {__ctHelpers.derive(price, price => price * 1.1)}</p>
+ <p>Next: {__ctHelpers.derive(count, ({ count }) => count + 1)}</p>
+ <p>Double: {__ctHelpers.derive(count, ({ count }) => count * 2)}</p>
+ <p>Total: {__ctHelpers.derive(price, ({ price }) => price * 1.1)}</p>
</file context>
| <p>Next: {__ctHelpers.derive(count, ({ count }) => count + 1)}</p> | |
| <p>Next: {__ctHelpers.derive(count, count => count + 1)}</p> |
| ); | ||
| } | ||
|
|
||
| const shouldInlineSoleBinding = bindings.length === 1 && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When there is only a single capture reference, the callback parameter should remain a plain identifier; otherwise derive(foo, ({ foo }) => ...) destructures the value incorrectly and returns undefined.
Prompt for AI agents
Address the following comment on packages/ts-transformers/src/transformers/builtins/derive.ts at line 148:
<comment>When there is only a single capture reference, the callback parameter should remain a plain identifier; otherwise derive(foo, ({ foo }) => ...) destructures the value incorrectly and returns undefined.</comment>
<file context>
@@ -35,75 +40,129 @@ export interface DeriveCallOptions {
+ );
+ }
+
+ const shouldInlineSoleBinding = bindings.length === 1 &&
+ captureTree.size === 0 &&
+ fallbackEntries.length === 1 &&
</file context>
| factory.createIdentifier(varName), | ||
| expr, | ||
| createSafePropertyName(rootName, factory), | ||
| buildHierarchicalParamsValue(rootNode, rootName, factory), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Binding each capture root to the object returned by buildHierarchicalParamsValue means methods like state.someMethod() now execute with this bound to a synthetic clone rather than the original closure object, breaking instance-dependent logic. Please bind roots to the original expression (or otherwise preserve the original receiver) instead of cloning partial objects.
Prompt for AI agents
Address the following comment on packages/ts-transformers/src/closures/transformer.ts at line 1017:
<comment>Binding each capture root to the object returned by buildHierarchicalParamsValue means methods like state.someMethod() now execute with `this` bound to a synthetic clone rather than the original closure object, breaking instance-dependent logic. Please bind roots to the original expression (or otherwise preserve the original receiver) instead of cloning partial objects.</comment>
<file context>
@@ -1201,52 +991,49 @@ function createRecipeCallWithParams(
- factory.createIdentifier(varName),
- expr,
+ createSafePropertyName(rootName, factory),
+ buildHierarchicalParamsValue(rootNode, rootName, factory),
),
);
</file context>
| <div style={{ marginTop: "16px", border: "1px solid #e0e0e0", borderRadius: "4px" }}> | ||
| {ifElse( | ||
| hasView, | ||
| <div>{currentView}</div>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use <ct-render $cell={currentView} /> (or lift/derive) to render the selected recipe; inserting the Cell itself just stringifies the object so the chosen view never appears.
Prompt for AI agents
Address the following comment on packages/patterns/on-demand-pattern-creation.tsx at line 84:
<comment>Use `<ct-render $cell={currentView} />` (or lift/derive) to render the selected recipe; inserting the Cell itself just stringifies the object so the chosen view never appears.</comment>
<file context>
@@ -0,0 +1,95 @@
+ <div style={{ marginTop: "16px", border: "1px solid #e0e0e0", borderRadius: "4px" }}>
+ {ifElse(
+ hasView,
+ <div>{currentView}</div>,
+ <div style={{ padding: "2rem", textAlign: "center", color: "#666" }}>
+ Choose a view above
</file context>
8440153 to
364bf82
Compare
This was the outcome of the whole saga documented in CT-1022, where Claude got confused for a whole day.
Based on real-world experience building shopping list launcher, document the pattern of creating child patterns on-demand in handlers when they share parent cell references. Key learnings: - Dynamic pattern creation IS supported via handlers - "Shadow ref alias" error occurs when creating shared-cell patterns during recipe init instead of on-demand - Solution: Create patterns in handlers for conditional/user-selected views Proposed documentation updates: 1. PATTERNS.md: New section on upfront vs on-demand pattern creation 2. RECIPES.md: Add "Shadow ref alias" error to pitfalls section 3. New example: packages/patterns/on-demand-pattern-creation.tsx 4. INDEX.md: Add entry for new example This addresses a common confusion where developers assume dynamic pattern creation isn't supported, when in fact it's fully supported and preferred for conditional pattern instantiation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Based on real-world experience building shopping list launcher, add comprehensive documentation for creating child patterns on-demand in handlers when they share parent cell references. Changes: 1. PATTERNS.md: Add "Upfront vs On-Demand Creation" section to Level 4 - Explains when to create patterns upfront vs in handlers - Shows working examples of both approaches - Documents the "Shadow ref alias" error and how to avoid it 2. RECIPES.md: Add item 22 on conditional pattern composition - Shows wrong (upfront) vs correct (on-demand) patterns - Explains why cell tracking requires on-demand creation - Lists when each approach applies 3. New example: packages/patterns/on-demand-pattern-creation.tsx - Working view selector that creates ListView/GridView on demand - Demonstrates handler-based pattern instantiation - Shows proper use of ifElse for conditional rendering 4. INDEX.md: Add entry for new example pattern Key insight: Dynamic pattern creation IS fully supported - the framework just requires that patterns sharing parent cells be created on-demand in handlers (not during recipe init) to ensure proper cell reference tracking. This documentation prevents the common confusion where developers assume dynamic pattern creation isn't supported when encountering "Shadow ref alias" errors. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Added two important pattern discoveries to documentation: 1. **Boxing Pattern for Sorting Cell Arrays** (PATTERNS.md) - Wrap items in objects before sorting to preserve cell references - Pattern: box → sort → unbox - Maintains bidirectional binding through transformations - Use case: Sort by derived values (LLM results, calculations) - Real example: Shopping list sorted by aisle while keeping checkboxes reactive 2. **Frame Mismatch Error Guidance** (COMMON_ISSUES.md) - Explains what Frame mismatch errors are - When to worry vs when to ignore - Diagnostic steps - Key insight: Errors don't always mean broken functionality Both patterns discovered during shopping list aisle sorting work. Boxing approach suggested by Berni - works perfectly! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Add missing randomId declaration in the ❌ WRONG example. Without this, the code snippet would throw a reference error if copied. Addresses cubic-dev-ai bot feedback on PR #1998. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Replace {currentView} with <ct-render $cell={currentView} /> to properly
render the selected pattern. Directly inserting a Cell into JSX just
stringifies the object and doesn't render the pattern's UI.
Addresses cubic-dev-ai bot feedback on PR #1998.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <[email protected]>
Simplify pattern instance storage examples: - Remove toSchema() wrapper - use simpler lift<Type, Return> syntax - Remove [ID] field with Math.random() (anti-pattern for causal IDs) - Remove unnecessary type casting (modern types handle it) - Add note about proper ID generation in production Addresses Berni's review comments on PR #1998: - Line 505: toSchema not needed with current framework - Line 515: [ID] + Math.random() is anti-pattern - Line 536: Type casting unnecessary Examples now show modern, simplified approach while maintaining the core lesson (use lift() when storing pattern instances). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
364bf82 to
f6c255f
Compare
* Add `link-tool.tsx` pattern * Create `link` built-in and use it for linkTool * Fix import * Fix handler * Fix link built-in to match CLI implementation Key changes to resolve runtime crash: 1. Link creation: Use `.set()` instead of `getAsWriteRedirectLink()` + `setRaw()` - This matches CharmManager.link() approach in packages/cli/lib/charm.ts - Previous approach caused stack overflow at runtime 2. Result type pattern: Follow generateObject/generateText pattern - Create result Cells outside action with runtime.getCell() - Send Cell objects (not plain values) via sendResult() on first run - Update cell values inside action with .withTx(tx).set(value) - Fixes TypeScript type mismatch errors 3. Path handling: Pop last segment as key - Navigate to parent cell with remaining path - Set link on parent: parentCell.key(targetKey).set(sourceCell) - Matches CLI implementation pattern Also: - Fix syntax error in default-app.tsx (missing closing paren) - Remove outdated link-tool.test.ts file 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * It's alive! And in need of much code review! * Fix link built-in concurrency issue Move cell creation inside transaction to prevent race conditions when multiple browser tabs try to use the link tool simultaneously. Changes: - Create result cells INSIDE transaction (not outside) - Use structured cause: { link: { success: cause } } - Matches generateObject/generateText pattern exactly This fixes crashes when two tabs both invoke the link tool at once. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Clean up PR: remove noise and unused code - Remove cosmetic changes to note.tsx (import reordering, trailing comma) - Simplify link-tool.tsx to only export the used handler - Remove unused createLinkTool (patternTool version) - Remove unused default recipe export - Keep only linkTool handler used by chatbot.tsx 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Revert accidental cosmetic changes to note.tsx * Fix lint: use Record<PropertyKey, never> instead of {} * Skip link-tool.tsx in pattern integration tests link-tool.tsx is a utility file that exports handlers, not a standalone pattern with a default recipe export. Add it to skippedPatterns to prevent test failures. * Fix three issues identified in code review 1. INDEX.md documentation: Fix misleading description - Changed from "array of charms, string (path specifications)" - To "string (source path), string (target path)" - link-tool.tsx only takes two string arguments, not arrays 2. LinkFunction type: Make properties required instead of optional - Changed from { success?: string; error?: string } - To { success: string | undefined; error: string | undefined } - The built-in always returns both cells, so properties should be required 3. Error messages: Use NAME constant instead of literal string - Changed c?.["[NAME]"] to c?.[NAME] in two error messages - Now displays actual charm names when source/target not found - Previously showed "none" because literal string didn't match symbol key 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Refactor: eliminate link built-in, implement in pattern-space With getArgumentCell() now available in the public API, we can implement link tool entirely in pattern-space without needing a built-in. Changes: - Rewrote packages/patterns/link-tool.tsx to use public Cell API - Uses wish("#mentionable") to get charms - Uses getArgumentCell() to navigate to charm inputs - All path parsing, charm lookup, and linking in pure pattern code - Removed packages/runner/src/builtins/link.ts (256 lines) - Removed all built-in plumbing: - packages/api/index.ts: LinkFunction type and declaration - packages/runner/src/builder/built-in.ts: link factory - packages/runner/src/builder/factory.ts: link export - packages/runner/src/builder/types.ts: LinkFunction from interface - packages/runner/src/builtins/index.ts: link registration Result: ~300 lines removed, simpler architecture, same functionality. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * Pattern-space link tool works * Only update `flattenedTools` if something changed Prevents churn/conflict on racing tool calls across tabs * Format + lint --------- Co-authored-by: Claude <[email protected]>
Update the header label in the default app charm list from "Patterns" to "Pages" to better reflect the purpose of the listed items. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Tony Espinoza <[email protected]> Co-authored-by: Claude <[email protected]>
Key features: - Parameter aliasing to preserve user's parameter names - Hierarchical capture organization - Object literal input merging - Type annotation handling via inference - Comprehensive edge case coverage - 19 test fixtures planned Reuses ~80% of existing closure transformation infrastructure: - collectCaptures() for detection - groupCapturesByRoot() for organization - buildTypeElementsFromCaptureTree() for schemas - buildHierarchicalParamsValue() for params objects Co-authored-by: Claude <[email protected]>
Add static Cell.of(), Cell.for(), and Cell.equals() methods
Introduces static factory methods on Cell types (Cell, OpaqueCell, Stream,
ComparableCell, ReadonlyCell, WriteonlyCell) for more ergonomic cell creation
and comparison.
## New Static Methods
### Cell.of(value, schema?)
Creates a cell with an initial/default value and optional JSONSchema.
- Sets the value as the default in the schema
- Only sets default when value is defined (handles undefined properly)
- Supports schema type inference via overloads
- Example: `const cell = Cell.of(42)` or `Cell.of("hello", { minLength: 3 })`
### Cell.for(cause)
Creates a cell with an explicit cause, useful in lift/recipe contexts.
- Can be chained with .set() or .of() to set values
- Can be chained with .asSchema() to add schema
- Example: `const cell = Cell.for("user-action").set(value)`
### Cell.equals(a, b)
Compares two cells or values for equality based on their links.
- Returns true if cells have the same link
- Example: `Cell.equals(cell1, cell2)`
## Implementation Details
- **Factory pattern**: Created `CellConstructorFactory(kind: CellKind)` in cell.ts
that generates constructor objects for different cell kinds
- **Schema handling**: Uses `ContextualFlowControl.toSchemaObj()` to properly
convert JSONSchema (which can be boolean or object) before merging
- **Context awareness**: Methods require runtime frame context (from recipe/
handler/lift) and will throw helpful errors if called outside valid contexts
- **Type safety**: Added proper TypeScript overloads for schema type inference
## API Changes
packages/api/index.ts:
- Added `CellTypeConstructor<C>` interface with static method signatures
- Exported constructor declarations for all cell types
packages/runner/src/cell.ts:
- Implemented `CellConstructorFactory(kind: CellKind)` function
- Returns object with .of(), .for(), and .equals() methods
- Each method validates frame context and creates cells with proper kind
packages/runner/src/builder/built-in.ts:
- Export cell constructors using factory: `Cell = CellConstructorFactory("cell")`
- Similar for OpaqueCell, Stream, ComparableCell, ReadonlyCell, WriteonlyCell
packages/runner/src/builder/factory.ts:
- Added Cell constructors to commontools object for builder pattern
packages/runner/src/builder/types.ts:
- Updated BuilderFunctionsAndConstants interface with Cell constructor types
- Added schema parameter support with proper overloads
## Testing
packages/runner/test/cell-static-methods.test.ts:
- Comprehensive test suite with 45 passing test steps
- Tests cover Cell.of(), Cell.for(), Cell.equals()
- Tests both handler context (inHandler: true) and lift context (inHandler: false)
- Validates schema support, type inference, and edge cases
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <[email protected]>
* Add maxSizeBytes auto-compression to ct-image-input
Add optional client-side image compression for LLM API size limits:
**New Property**:
- `maxSizeBytes?: number` - Optional max size before compression
- Only compresses if set and file exceeds limit
- Falls back to original if compression fails
**Compression Strategy**:
- Uses Canvas API (OffscreenCanvas for performance)
- Tries 9 progressive size/quality combinations:
- 2048px @ 85% quality → 800px @ 50% quality
- Stops when under size limit
- Logs compression results for debugging
**Implementation**:
- `_compressImage()`: Robust compression with fallback
- Preserves original filename
- Updates size metadata to reflect compressed size
- JPEG output for broad compatibility
**Use Case**:
Anthropic vision API has 5MB limit per image. Setting maxSizeBytes={4_500_000} ensures images compress automatically before upload.
**Example**:
```typescript
<ct-image-input
maxSizeBytes={4500000}
multiple
maxImages={5}
buttonText="📷 Scan Signs"
/>
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <[email protected]>
* Add maxSizeBytes to CTImageInputAttributes types
Update JSX type definitions for new compression property
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <[email protected]>
* Optimize image compression with binary search algorithm
Replace linear scan through 9 predefined quality/dimension combinations
with an intelligent binary search approach:
- Binary search on quality values (0.5-0.95) for each dimension level
- Tries dimensions in descending order: 2048, 1600, 1200, 800
- Returns as soon as optimal compression is found
- More efficient: typically 3-4 compressions per dimension vs 9 total
- Better quality: finds optimal quality dynamically instead of using
predefined values
This reduces compression time while maintaining or improving output quality.
* Refactor: Extract image compression logic into utility module
Move the binary search image compression algorithm from ct-image-input
component into a reusable utility module:
- Created packages/ui/src/v2/utils/image-compression.ts with:
- compressImage() function with configurable options
- formatFileSize() helper function
- CompressionResult interface with detailed metadata
- CompressionOptions interface for customization
Benefits:
- Reusability: Other components can now use image compression
- Testability: Logic can be tested independently
- Separation of concerns: UI component focuses on presentation
- Maintainability: Algorithm improvements benefit all consumers
- Type safety: Proper TypeScript interfaces and return types
The ct-image-input component now delegates to the utility while
maintaining the same compression behavior and logging.
* Fix lint
* Fix logic so it works at runtime
* Trim console.log output
* Clarify intended logic
* Fix logic
* Respond to feedback
---------
Co-authored-by: Alex Komoroske <[email protected]>
Co-authored-by: Claude <[email protected]>
* Implement fetchProgram builtin for URL-based program execution
Add new fetchProgram builtin that enables patterns to fetch and resolve
programs from URLs, composing cleanly with the existing compileAndRun builtin.
Implementation:
- Created fetch-program.ts builtin following fetchData pattern
- Uses HttpProgramResolver to fetch main file and resolve dependencies
- Uses resolveProgram to handle multi-file imports
- Returns { pending, result: { files, main }, error } structure
- Registered in builtins/index.ts
- Added type definitions to api/index.ts
- Added builder API in builder/built-in.ts
- Created test pattern in patterns/fetch-program-test.tsx
Usage example:
const { result: program } = fetchProgram({ url });
const { result } = compileAndRun({ ...program, input: data });
This enables loading and executing patterns from GitHub URLs or any
HTTP-accessible location, supporting the goal of distributing patterns
as public, reusable components.
* Refactor: Extract shared fetch utilities to eliminate duplication
Extracted common fetch-related utilities (tryClaimMutex, tryWriteResult,
computeInputHash, internalSchema) into a shared fetch-utils.ts module.
Changes:
- Created packages/runner/src/builtins/fetch-utils.ts with shared utilities
- Updated fetch-data.ts to import from fetch-utils.ts
- Updated fetch-program.ts to import from fetch-utils.ts
- Eliminated ~100 lines of duplicated code
Benefits:
- DRY principle - single source of truth for fetch patterns
- Easier maintenance - changes only need to be made once
- Consistent behavior across all fetch-style builtins
- Makes fetch-program.ts configurable with longer timeout (10s vs 5s)
The shared utilities handle:
- Input hashing for change detection
- Mutex claiming for multi-tab coordination
- Result writing with input validation
* Simplify fetchProgram - remove unnecessary complexity
Stripped down fetchProgram from ~300 lines to ~115 lines by removing:
- Mutex/multi-tab coordination (over-engineered for initial version)
- Input hashing and change detection
- AbortController logic
- All the shared fetch-utils machinery
Core logic is now clear:
1. Initialize cells for pending/result/error
2. Fetch URL and resolve program dependencies
3. Update cells with result or error
The builtin now does exactly what it needs to:
- Fetch a program from a URL using HttpProgramResolver
- Resolve all dependencies via resolveProgram
- Return { files, main } structure for compileAndRun
Can always add optimizations later if needed.
* Use shared fetch utilities in fetchProgram for consistency
Updated fetchProgram to use the same fetch-utils.ts patterns as fetchData:
- Multi-tab coordination via tryClaimMutex (prevents duplicate fetches)
- Input hashing via computeInputHash (detects URL changes)
- Safe result writing via tryWriteResult (any tab can write if inputs match)
- AbortController support for cancellation
- Proper cleanup on recipe stop
This ensures both fetch-style builtins follow the same pattern for:
- Avoiding duplicate network requests across tabs
- Handling race conditions properly
- Consistent timeout and retry behavior
fetchProgram uses 10s timeout (vs fetchData's 5s) since program
resolution with dependencies can take longer than simple HTTP fetches.
* It's alive!
* Format pass
* Fix lint + format
* Exclude result from hash computation
* Tighten retry and de-duplication logic
* Clear result if input to fetchProgram cleared
* Format + lint
* Fix tests
* Address code review
* Action code review feedback
* One more pass over the state machine
---------
Co-authored-by: Claude <[email protected]>
Refactor `fetch-program` to a state machine
* fix transformers w.r.t. map over opaqueref inside derive
* Add `fetchAndRun` tool to omnibot * Move linkTool to just omnibot * Format pass * Fix lint errors * Fix lint
* actually fix name collisions in derive closures * fix shorthand property assignments; also remove $schema everywhere * fix: update test expectations after rebase - Remove $schema from all test fixtures (main already had this change) - Update asOpaque to asCell for cell types (API change in main) - Fix derive-nested-callback to show proper array schema (typeRegistry fix working correctly)
* docs: update API to use computed(), Cell.of(), and inline handlers
Updated all documentation in docs/common/ to reflect recent API changes:
API Changes:
- Replace cell() with Cell.of() for creating reactive state
- Replace derive() with computed() for reactive transformations
- Add Cell.equals(a, b) for convenient cell comparison
- Add Cell.for(cause) for explicit cell creation in lift contexts
- Support inline handlers without handler() wrapper
Key Updates:
- Inline handlers preferred for simple cases: onClick={() => count.set(count.get() + 1)}
- handler() now optional, only for complex/reusable logic
- Emphasized: declare cells as Cell<T> in recipe inputs for inline handlers
- computed() closes over variables, not needed in JSX (automatic reactivity)
- Updated all code examples to use new API
Decision Hierarchy:
1. Bidirectional binding ($checked, $value) for simple UI ↔ data sync
2. Inline handlers for simple operations with custom logic
3. handler() function only for complex or reusable logic
Files updated:
- docs/common/RECIPES.md: Core concepts, handlers, computed(), examples
- docs/common/HANDLERS.md: Inline handler guide, decision matrix
- docs/common/PATTERNS.md: Level 1-3 examples, pitfalls, performance
- docs/common/COMPONENTS.md: ct-button, ct-input examples
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <[email protected]>
* updated core patterns to new syntax
* Add support for `tools` to `generateText` * Fix cache behaviour for tool-call-only response * Fix lint * Format * Format
* Fix model picker display in `ct-prompt-input` * Format * Fix `generateObject` 400 error * Handle `undefined` case
* fix ct-1035 by correctly transforming map inside ifelse
Add test fixture pair for the pattern described in CT-1036: - Grouping items by a property into Record<K, V[]> - Deriving keys from the grouped object - Mapping over derived keys and accessing grouped[key] - Nested mapping over the accessed array This pattern runs successfully and transforms correctly with the current transformer. The JSX transformer properly wraps the property access (groupedByAisle[aisleName]) in a derive with both values as parameters, enabling reactive property access with derived keys. Related: CT-1036
…2071) **Problem:** Errors in DefaultPattern.create() were caught and logged but not re-thrown, causing silent failures. **Why this breaks mentions:** 1. linkDefaultPattern() fails (network glitch, race condition, etc.) 2. Error is swallowed → no indication to user 3. DefaultCharmList charm created successfully 4. But /defaultPattern link in space cell is missing 5. wish("#mentionable") resolves path: /defaultPattern/backlinksIndex/mentionable 6. Path resolution fails because /defaultPattern doesn't exist 7. Returns empty array instead of charm list 8. ct-code-editor receives empty mentionable list 9. [[ dropdown shows no completions **Symptom:** Type [[ in notes field → dropdown is empty (even though backlinksIndex has data) **Timeline:** - Oct 23 (947b471): linkDefaultPattern added - Nov 6 (or earlier): Spaces created with transient link failures - Today: Error swallowing discovered **Fix:** Re-throw errors to surface failures immediately. Future spaces will fail loudly if linking doesn't work, preventing silent corruption. **Workaround for affected spaces:** Delete packages/toolshed/cache/memory/*.sqlite and recreate spaces.
…PI (#2070) * add new `pattern` definition, which is a subset of `recipe` with saner argument order * feat(ts-transformers): add support for pattern() as alternative to recipe() Add comprehensive TypeScript transformer support for the new `pattern()` function, which simplifies the API by removing the optional name parameter and using a cleaner argument order: pattern(fn, argSchema?, resSchema?). Changes: - Update schema-injection.ts to handle pattern() calls with three variants: * pattern<ArgType, ResType>((input) => {...}) * pattern<ArgType>((input) => {...}) * pattern((input: Type) => {...}) - Add "pattern" to BUILDER_SYMBOL_NAMES in call-kind.ts for recognition - Add comprehensive test fixtures across all categories: * ast-transform: counter-pattern, pattern-with-type, pattern-array-map * closures: computed-pattern variants, opaque-ref-map patterns * jsx-expressions: pattern-with-cells, pattern-statements-vs-jsx * schema-transform: pattern-with-types All tests passing (16 passed, 178 steps). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> * refactor(ts-transformers): use TypeScript inference for pattern schemas Improve pattern() transformation to leverage TypeScript's type checker instead of reading type arguments directly, making it more robust and capable of inferring result types automatically. Before: Only handled explicit type arguments, couldn't infer result types After: Uses collectFunctionSchemaTypeNodes to infer both argument and result types from function signatures, with type args as hints Key improvements: - Result schemas now automatically inferred from function return types - More flexible: works with partial type information - Less brittle: doesn't fail on missing type arguments - Leverages existing inference infrastructure used by other builders - Properly registers inferred types with type registry Example transformation: ```typescript // Input pattern((input: MyInput) => { return { result: input.value * 2 }; }) // Output (now includes inferred result schema) pattern((input: MyInput) => {...}, { type: "object", properties: { value: { type: "number" } } }, { type: "object", properties: { result: { type: "number" } } } ) ``` All tests passing with updated expected outputs reflecting inferred schemas. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]>
… yet (#2073) fix(built-in): always return wished cell, even if value isn't defined yet
* Switch from per-charm tools to top-level run/read/inspect tools * Fix tool schemas/parameter declarations * Append schema LUT to the system prompt Makes this a 1:1 comparison against old version * Lint + format * Fix test case
* test: add fixture for Cell.get() in ifElse predicate bug Reproduces issue where transformer generates derive without schema for ifElse predicates, causing Cell parameters to be unwrapped when they should remain as Cells (with asCell: true in schema). Expected: derive with full schema preserving asCell: true Actual: derive without schema, unwrapping all values * change order of transformers
`pattern`, `recipe`, `lift` and `handler` now consistently produce factories that strip any Cell annotations, so that the type the developer declares matches the internal view while external callers can call with or without Cells. --- Also fixed TypeScript types in patterns by: 1. Adding Cell<> wrappers to input schema properties where UI components expect CellLike<T> values: - recipes: bgCounter, email-summarizer, gcal, gmail-importer, input, research-report, rss - patterns: chatbot, ct-checkbox-*, ct-list, ct-select, fetch-data, note 2. Removing extraneous asCell: true from simpleValue.tsx that was causing .length property errors 3. Making recipe input properties optional where they have Default<> values and adding fallback handling in recipe implementations 4. Fixing Cell import from type-only to regular import in note.tsx
…explicit types of stream (#2076)
JSON serialization turns undefined to null in arrays, so ifElse(.., undefined, ..) returned null instead of undefined.
* Add support for listing attachments with schemas * Keep the `of:` prefix on the IDs * Remove deprecated listAttachments tool * Operate on links to arbitrary cells during tool calls * Make read/run always available for arbitrary links * It works! Fixed the `type` field * A single code path to get links and resolve them in `llm-dialog` * Fix link resolution yet again * Rename `legacy` -> `dynamic` tools * Continue to refine tool call flow * Lint + format * Navigate to arbitrary address * Fix final lint
* Server/provider has a single, global namespace. All spaces created are private by default. Tilte `~spacenames` will have no special meaning.
* derived from (public) space name for now representing user owning of
space. Eventually we'll have a look up table to not need publicly
derivable spaces.
* Add a well-known `ACL` cell to each Space. This contains a `Map<DID, CAPABILITY>` that applies to the current space.
* User transactions are signed with the User key directly (`session.as`).
* Each transaction requires the signer to either be the space identity, or an identity located in the `ACL`, or have `*` identity in the `ACL`.
* This changes the previous "personas" indirection in signing, but will simplify the `ACL` stored identities.
* To remedy the shortcomings of [1], only allow the space identity to write if and only if it's writing the initial `ACL` value.
* Add to CLI ability to add identities to a space with a capability.
* Log current user's identity in browser console for sharing.
u
* Prefix link serialized form with `/` * Use `ifElse()` in fetchAndRun * Allow listRecent and listMentionable to return link IDs * Rework 'attachments' concept * Mentions insert the link into the prompt composer * Remove mentions from the attachments bar in ct-prompt-composer * Fix type errors * Fix lint * Return cell directly instead of stringifying it * Format pass * Fix ct-outliner name access * Revert "Return cell directly instead of stringifying it" This reverts commit dec880f. * Format
#2084) fix(llm-builtin): turn json graphs into a cell links where they repeat since our data structure allows graphs, but we need to output json this can happen. we will return everything after the first occurence as a link, which we can get back since this data must have been generated from a Cell.get()
Issue: backlinks-index used lift() which computes once at initialization. When allCharms was empty initially, mentionable stayed empty even after charms were added. Fix: Use derive(allCharms, ...) which reactively tracks when allCharms changes and recomputes mentionable automatically. This fixes the bug where [[ dropdown is empty until page refresh. Fixes timing issue reported by Berni where fresh spaces need refresh for mentions to work.
Update documentation to reflect wish() API changes from commit 6c198d5: - wish() now takes only one parameter (path) - No longer accepts default value as second argument - Always returns a cell, even if value doesn't exist yet - Use derive() with null coalescing to provide defaults Files updated: - RECIPES.md: Updated default value pattern and examples - COMMON_ISSUES.md: Updated wish() examples in auto-discovery section - COMPONENTS.md: Updated mentionable wish() example Related commits: - 6c198d5: fix(built-in): always return wished cell, even if value isn't defined yet - b20f7d173: Fix: Use derive instead of lift for reactive mentionable tracking 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
Documents the critical pattern discovered during store-mapper investigation: ifElse() only re-evaluates when its condition changes, not when data inside branches changes. This causes UI to lock onto stale values when working with async operations that evaluate in phases. Includes broken example, correct solution, detailed timeline, and real-world context from the generateObject + images investigation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <[email protected]> Co-Authored-By: Happy <[email protected]>
This was the outcome of the whole saga documented in CT-1022, where Claude got confused for a whole day.
Summary by cubic
Updated docs and examples to prevent stack overflows when storing pattern instances and to favor on‑demand child pattern creation when patterns share parent cells. Also adds ACLs, a fetchProgram builtin, and modernized APIs and patterns; includes clear wrong vs correct examples and a working view selector tied to CT‑1022.
New Features
Dependencies
Written for commit c423d24. Summary will update automatically on new commits.