Skip to content

Commit b167c84

Browse files
bfollingtonclaude
andauthored
Resurrect compileAndRun example and integrate omnibot (commontoolsinc#2025)
* fix(compiler): Wait for charm initialization before navigating The issue was that navigateTo was being called immediately with the result from compileAndRun, but the result Cell is initially undefined and only gets populated asynchronously after compilation completes. Solution follows the same pattern used in chatbot-list-view.tsx: - Use a lift to monitor the result Cell reactively - Only navigate once the result is populated (not undefined) - Use an isNavigated flag to ensure navigation only happens once Changes: - Add navigateWhenReady lift that waits for result to be ready - Update visit handler to use the lift instead of calling navigateTo directly - Show actual error messages in the UI instead of generic "fix the errors" This fixes the "blackhole" issue where navigateTo appeared to do nothing. * fix(compiler): Reuse compileAndRun result instead of creating new action The previous approach called compileAndRun inside the handler, which: - Created a NEW compileAndRun action on every handler invocation - Caused double invocation due to re-renders from pending state changes - Hit the run index check at compile-and-run.ts:191 and bailed early Solution: - Call compileAndRun once in the recipe body (already done for error display) - Reuse the stable result cell from that single compileAndRun call - Handler just calls navigateTo(result) on the existing cell - navigateTo is an Action, so it re-runs when result populates This is much simpler and avoids the double invocation issue entirely. No need for the navigateWhenReady lift workaround! * Copy across cancellation pattern from `llm.ts` * Move `compiler.tsx` to patterns package * fix(compiler): Clear pending state after cancellation Remove abort signal check from finally block to ensure pending state is always reset to false when compilation completes, whether successfully, with error, or cancelled. This prevents the compile action from getting stuck in pending=true state after cancellation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> --------- Co-authored-by: Claude <[email protected]>
1 parent 7696b91 commit b167c84

File tree

2 files changed

+46
-19
lines changed

2 files changed

+46
-19
lines changed

recipes/compiler.tsx renamed to packages/patterns/compiler.tsx

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,28 @@ const updateCode = handler<
2828
);
2929

3030
const visit = handler<
31-
{ detail: { value: string } },
32-
{ code: string }
31+
unknown,
32+
{ result: Cell<any> }
3333
>(
34-
(_, state) => {
35-
const { result } = compileAndRun({
36-
files: [{ name: "/main.tsx", contents: state.code }],
37-
main: "/main.tsx",
38-
});
39-
40-
console.log("result", result);
41-
34+
(_, { result }) => {
35+
console.log("visit: navigating to compiled result", result);
4236
return navigateTo(result);
4337
},
4438
);
4539

40+
const handleEditContent = handler<
41+
{ code: string },
42+
{ code: Cell<string> }
43+
>(
44+
(event, { code }) => {
45+
code.set(event.code);
46+
},
47+
);
48+
4649
export default recipe<Input>(
4750
"Compiler",
4851
({ code }) => {
49-
const { error, errors } = compileAndRun({
52+
const { result, error, errors } = compileAndRun({
5053
files: [{ name: "/main.tsx", contents: code }],
5154
main: "/main.tsx",
5255
});
@@ -63,16 +66,18 @@ export default recipe<Input>(
6366
/>
6467
{ifElse(
6568
error,
66-
<b>fix the errors</b>,
69+
<b>fix the error: {error}</b>,
6770
<ct-button
68-
onClick={visit({ code })}
71+
onClick={visit({ result })}
6972
>
7073
Navigate To Charm
7174
</ct-button>,
7275
)}
7376
</div>
7477
),
7578
code,
79+
updateCode: handleEditContent({ code }),
80+
visit: visit({ result }),
7681
};
7782
},
7883
);

packages/runner/src/builtins/compile-and-run.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ import { CompilerError } from "@commontools/js-runtime/typescript";
2828
export function compileAndRun(
2929
inputsCell: Cell<BuiltInCompileAndRunParams<any>>,
3030
sendResult: (tx: IExtendedStorageTransaction, result: any) => void,
31-
_addCancel: (cancel: () => void) => void,
31+
addCancel: (cancel: () => void) => void,
3232
cause: any,
3333
parentCell: Cell<any>,
3434
runtime: IRuntime,
3535
): Action {
36-
let currentRun = 0;
36+
let requestId: string | undefined = undefined;
37+
let abortController: AbortController | undefined = undefined;
3738
let previousCallHash: string | undefined = undefined;
3839
let cellsInitialized = false;
3940
let pending: Cell<boolean>;
@@ -52,6 +53,12 @@ export function compileAndRun(
5253
| undefined
5354
>;
5455

56+
// This is called when the recipe containing this node is being stopped.
57+
addCancel(() => {
58+
// Abort any in-flight compilation if it's still pending.
59+
abortController?.abort("Recipe stopped");
60+
});
61+
5562
return (tx: IExtendedStorageTransaction) => {
5663
if (!cellsInitialized) {
5764
pending = runtime.getCell<boolean>(
@@ -97,7 +104,7 @@ export function compileAndRun(
97104
sendResult(tx, { pending, result, error, errors });
98105
cellsInitialized = true;
99106
}
100-
const thisRun = ++currentRun;
107+
101108
const pendingWithLog = pending.withTx(tx);
102109
const resultWithLog = result.withTx(tx);
103110
const errorWithLog = error.withTx(tx);
@@ -135,6 +142,11 @@ export function compileAndRun(
135142
if (hash === previousCallHash) return;
136143
previousCallHash = hash;
137144

145+
// Abort any in-flight compilation before starting a new one
146+
abortController?.abort("New compilation started");
147+
abortController = new AbortController();
148+
requestId = crypto.randomUUID();
149+
138150
runtime.runner.stop(result);
139151
resultWithLog.set(undefined);
140152
errorWithLog.set(undefined);
@@ -156,10 +168,15 @@ export function compileAndRun(
156168
// Now we're sure that we have a new file to compile
157169
pendingWithLog.set(true);
158170

171+
// Capture requestId for this compilation run
172+
const thisRequestId = requestId;
173+
159174
const compilePromise = runtime.harness.run(program)
160175
.catch(
161176
(err) => {
162-
if (thisRun !== currentRun) return;
177+
// Only process this error if the request hasn't been superseded
178+
if (requestId !== thisRequestId) return;
179+
if (abortController?.signal.aborted) return;
163180

164181
runtime.editWithRetry((asyncTx) => {
165182
// Extract structured errors if this is a CompilerError
@@ -180,15 +197,20 @@ export function compileAndRun(
180197
});
181198
},
182199
).finally(() => {
183-
if (thisRun !== currentRun) return;
200+
// Only update pending if this is still the current request
201+
if (requestId !== thisRequestId) return;
202+
// Always clear pending state, even if cancelled, to avoid stuck state
184203

185204
runtime.editWithRetry((asyncTx) => {
186205
pending.withTx(asyncTx).set(false);
187206
});
188207
});
189208

190209
compilePromise.then((recipe) => {
191-
if (thisRun !== currentRun) return;
210+
// Only run the result if this is still the current request
211+
if (requestId !== thisRequestId) return;
212+
if (abortController?.signal.aborted) return;
213+
192214
if (recipe) {
193215
// TODO(ja): to support editting of existing charms / running with
194216
// inputs from other charms, we will need to think more about

0 commit comments

Comments
 (0)