Repository: KittyCAD/modeling-app
Pull request #6910: More consistent error handling in modelingMachine codemods
Author: login: pierremtb
Created: 2025-05-13T18:42:31Z | Merged: 2025-05-15T16:26:20Z

Closes #6514 and fixes #6900 (P0).

Upgraded:
- all sweeps
- all edge treatments and shell
- all booleans
- offset plane and helix
- all assemblies transform
- insert and edit parameter

to have:

- consistent fromPromise typing
- consistent Promise.reject(Error) return behavior
- the existing 'no kcl errors' guard
- onError trigger toastError

Also added the 'no kcl errors' guard to enter sketch.

And @franknoirot made all those buttons and commands disabled based on the `.can` transition check 🙌 

Linked issue #6514: More consistent error handling in modelingMachine codemods
              Alright these are super valid. Just now I specifically tried to mess around with cases that would trigger that it it would fail silently. Turned out I needed two things to make the errors show up:
1. change from `return new Error(...` to `return Promise.reject(new Error(...` as you suggested
2. change `'Applying clone'`'s `onError` from `onDone: ['idle']` to `onError: { target: 'idle', actions: 'toastError' }`, which of course makes sense!
 
Pushed these changes here, and I'll create a follow up issue to make some of the other codmods that are likely to have silent errors have those changes as well

_Originally posted by @pierremtb in https://github.com/KittyCAD/modeling-app/pull/6478#discussion_r2061268005_
            

Diff:
diff --git a/e2e/playwright/code-pane-and-errors.spec.ts b/e2e/playwright/code-pane-and-errors.spec.ts
index 48ebb482569..6d3b258e10b 100644
--- a/e2e/playwright/code-pane-and-errors.spec.ts
+++ b/e2e/playwright/code-pane-and-errors.spec.ts
@@ -134,8 +134,6 @@ extrude001 = extrude(sketch001, length = 5)`
     await page.setBodyDimensions({ width: 1200, height: 500 })
     await homePage.goToModelingScene()
 
-    await page.waitForTimeout(1000)
-
     // Ensure badge is present
     const codePaneButtonHolder = page.locator('#code-button-holder')
     await expect(codePaneButtonHolder).toContainText('notification', {
@@ -179,7 +177,7 @@ extrude001 = extrude(sketch001, length = 5)`
     await page.setBodyDimensions({ width: 1200, height: 500 })
     await homePage.goToModelingScene()
 
-    await scene.settled(cmdBar)
+    // await scene.settled(cmdBar)
 
     // Ensure badge is present
     const codePaneButtonHolder = page.locator('#code-button-holder')
diff --git a/e2e/playwright/editor-tests.spec.ts b/e2e/playwright/editor-tests.spec.ts
index db5777689e5..72776494423 100644
--- a/e2e/playwright/editor-tests.spec.ts
+++ b/e2e/playwright/editor-tests.spec.ts
@@ -1533,7 +1533,6 @@ sketch001 = startSketchOn(XZ)
     await homePage.goToModelingScene()
 
     await scene.connectionEstablished()
-    await scene.settled(cmdBar)
 
     await scene.expectPixelColor(
       TEST_COLORS.DARK_MODE_BKGD,
diff --git a/e2e/playwright/point-click.spec.ts b/e2e/playwright/point-click.spec.ts
index 302ef46c883..de5320ef75a 100644
--- a/e2e/playwright/point-click.spec.ts
+++ b/e2e/playwright/point-click.spec.ts
@@ -4943,4 +4943,34 @@ path001 = startProfile(sketch001, at = [0, 0])
       )
     })
   })
+
+  test(`Point and click codemods can't run on KCL errors`, async ({
+    context,
+    page,
+    homePage,
+    scene,
+    editor,
+    toolbar,
+    cmdBar,
+  }) => {
+    const badCode = `sketch001 = startSketchOn(XZ)
+profile001 = circle(sketch001, center = [0, 0], radius = 1)
+extrude001 = extrude(profile001 length = 1)`
+    await context.addInitScript((initialCode) => {
+      localStorage.setItem('persistCode', initialCode)
+    }, badCode)
+    await page.setBodyDimensions({ width: 1000, height: 500 })
+    await homePage.goToModelingScene()
+    await scene.connectionEstablished()
+
+    await test.step(`Start Sketch is disabled`, async () => {
+      await expect(toolbar.startSketchBtn).not.toBeEnabled()
+      await editor.expectEditor.toContain(badCode, { shouldNormalise: true })
+    })
+
+    await test.step(`Helix is disabled`, async () => {
+      await expect(toolbar.helixButton).not.toBeEnabled()
+      await editor.expectEditor.toContain(badCode, { shouldNormalise: true })
+    })
+  })
 })
diff --git a/e2e/playwright/regression-tests.spec.ts b/e2e/playwright/regression-tests.spec.ts
index a21ac40acd9..cefab4a7551 100644
--- a/e2e/playwright/regression-tests.spec.ts
+++ b/e2e/playwright/regression-tests.spec.ts
@@ -19,11 +19,12 @@ test.describe('Regression tests', () => {
     context,
     page,
     homePage,
+    scene,
   }) => {
     // because the model has `line([0,0]..` it is valid code, but the model is invalid
     // regression test for https://github.com/KittyCAD/modeling-app/issues/3251
     // Since the bad model also found as issue with the artifact graph, which in tern blocked the editor diognostics
-    const u = await getUtils(page)
+    // const u = await getUtils(page)
     await context.addInitScript(async () => {
       localStorage.setItem(
         'persistCode',
@@ -40,7 +41,8 @@ test.describe('Regression tests', () => {
     await page.setBodyDimensions({ width: 1000, height: 500 })
 
     await homePage.goToModelingScene()
-    await u.waitForPageLoad()
+    await scene.connectionEstablished()
+    // await u.waitForPageLoad()
 
     // error in guter
     await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
@@ -185,8 +187,8 @@ extrude001 = extrude(sketch001, length = 50)
       page.locator('.pretty-json-container >> text=myVar:"67')
     ).toBeVisible()
   })
-  test('ProgramMemory can be serialised', async ({ page, homePage }) => {
-    const u = await getUtils(page)
+  test('ProgramMemory can be serialised', async ({ page, homePage, scene }) => {
+    // const u = await getUtils(page)
     await page.addInitScript(async () => {
       localStorage.setItem(
         'persistCode',
@@ -211,11 +213,12 @@ extrude001 = extrude(sketch001, length = 50)
     // Listen for all console events and push the message text to an array
     page.on('console', (message) => messages.push(message.text()))
     await homePage.goToModelingScene()
-    await u.waitForPageLoad()
+    // await u.waitForPageLoad()
+    await scene.connectionEstablished()
 
     // wait for execution done
-    await u.openDebugPanel()
-    await u.expectCmdLog('[data-message-type="execution-done"]')
+    // await u.openDebugPanel()
+    // await u.expectCmdLog('[data-message-type="execution-done"]')
 
     const forbiddenMessages = ['cannot serialize tagged newtype variant']
     forbiddenMessages.forEach((forbiddenMessage) => {
@@ -229,6 +232,7 @@ extrude001 = extrude(sketch001, length = 50)
     context,
     page,
     homePage,
+    scene,
   }) => {
     const u = await getUtils(page)
     // const PUR = 400 / 37.5 //pixeltoUnitRatio
@@ -247,11 +251,10 @@ extrude001 = extrude(sketch001, length = 50)
     shell(exampleSketch, faces = ['end'], thickness = 0.25)`
       )
     })
+    await homePage.goToModelingScene()
+    await scene.connectionEstablished()
 
     await expect(async () => {
-      await homePage.goToModelingScene()
-      await u.waitForPageLoad()
-
       // error in guter
       await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
         timeout: 1_000,
diff --git a/e2e/playwright/sketch-tests.spec.ts b/e2e/playwright/sketch-tests.spec.ts
index d02ddd808d7..e6f256980b1 100644
--- a/e2e/playwright/sketch-tests.spec.ts
+++ b/e2e/playwright/sketch-tests.spec.ts
@@ -1365,18 +1365,18 @@ solid001 = subtract([extrude001], tools = [extrude002])
     await page.addInitScript(async () => {
       localStorage.setItem(
         'persistCode',
-        `fn in2mm = (inches) => {
+        `fn in2mm(@inches) {
     return inches * 25.4
   }
 
-  const railTop = in2mm(.748)
-  const railSide = in2mm(.024)
-  const railBaseWidth = in2mm(.612)
-  const railWideWidth = in2mm(.835)
-  const railBaseLength = in2mm(.200)
-  const railClampable = in2mm(.200)
+  railTop = in2mm(.748)
+  railSide = in2mm(.024)
+  railBaseWidth = in2mm(.612)
+  railWideWidth = in2mm(.835)
+  railBaseLength = in2mm(.200)
+  railClampable = in2mm(.200)
 
-  const rail = startSketchOn(XZ)
+  rail = startSketchOn(XZ)
     |> startProfile(at = [-railTop / 2, railClampable + railBaseLength])
     |> line(endAbsolute = [
      railTop / 2,
@@ -3540,7 +3540,6 @@ profile001 = startProfile(sketch001, at = [127.56, 179.02])
 
     await homePage.openProject('multi-file-sketch-test')
     await scene.connectionEstablished()
-    await scene.settled(cmdBar)
 
     await u.closeDebugPanel()
 
@@ -3555,9 +3554,6 @@ profile001 = startProfile(sketch001, at = [127.56, 179.02])
 
     await toolbar.openFile('error.kcl')
 
-    // Ensure filetree is populated
-    await scene.settled(cmdBar)
-
     await expect(
       toolbar.featureTreePane.getByRole('button', { name: 'Sketch' })
     ).toHaveCount(0)
diff --git a/src/Toolbar.tsx b/src/Toolbar.tsx
index 424f89f5649..2f46a76df45 100644
--- a/src/Toolbar.tsx
+++ b/src/Toolbar.tsx
@@ -158,7 +158,8 @@ export function Toolbar({
       const isDisabled =
         disableAllButtons ||
         !isConfiguredAvailable ||
-        maybeIconConfig.disabled?.(state) === true
+        maybeIconConfig.disabled?.(state) === true ||
+        kclManager.hasErrors()
 
       return {
         ...maybeIconConfig,
@@ -444,6 +445,15 @@ const ToolbarItemTooltip = memo(function ToolbarItemContents({
       contentClassName={contentClassName}
     >
       {children}
+      {kclManager.hasErrors() && (
+        <p className="text-xs p-1 text-chalkboard-70 dark:text-chalkboard-40">
+          <CustomIcon
+            name="exclamationMark"
+            className="w-4 h-4 inline-block mr-1 text-destroy-80 bg-destroy-10"
+          />
+          Fix KCL errors to enable tools
+        </p>
+      )}
     </Tooltip>
   )
 })
diff --git a/src/components/CommandComboBox.tsx b/src/components/CommandComboBox.tsx
index 0eaac283931..f8de2142e6f 100644
--- a/src/components/CommandComboBox.tsx
+++ b/src/components/CommandComboBox.tsx
@@ -136,8 +136,9 @@ function optionIsDisabled(option: Command): boolean {
     option.disabled ||
     ('machineActor' in option &&
       option.machineActor !== undefined &&
-      !getActorNextEvents(option.machineActor.getSnapshot()).includes(
+      (!getActorNextEvents(option.machineActor.getSnapshot()).includes(
         option.name
-      ))
+      ) ||
+        !option.machineActor?.getSnapshot().can({ type: option.name })))
   )
 }
diff --git a/src/components/ModelingMachineProvider.tsx b/src/components/ModelingMachineProvider.tsx
index 422ffbf74a8..3451300e82b 100644
--- a/src/components/ModelingMachineProvider.tsx
+++ b/src/components/ModelingMachineProvider.tsx
@@ -579,24 +579,23 @@ export const ModelingMachineProvider = ({
             selectionRanges
           )
         },
-        'Has exportable geometry': () => {
-          if (!kclManager.hasErrors() && kclManager.ast.body.length > 0)
-            return true
-          else {
-            let errorMessage = 'Unable to Export '
-            if (kclManager.hasErrors()) errorMessage += 'due to KCL Errors'
-            else if (kclManager.ast.body.length === 0)
-              errorMessage += 'due to Empty Scene'
-            console.error(errorMessage)
-            toast.error(errorMessage)
-            return false
-          }
-        },
+        'Has exportable geometry': () =>
+          !kclManager.hasErrors() && kclManager.ast.body.length > 0,
       },
       actors: {
         exportFromEngine: fromPromise(
           async ({ input }: { input?: ModelingCommandSchema['Export'] }) => {
-            if (!input) {
+            if (kclManager.hasErrors() || kclManager.ast.body.length === 0) {
+              let errorMessage = 'Unable to Export '
+              if (kclManager.hasErrors()) {
+                errorMessage += 'due to KCL Errors'
+              } else if (kclManager.ast.body.length === 0) {
+                errorMessage += 'due to Empty Scene'
+              }
+              console.error(errorMessage)
+              toast.error(errorMessage)
+              return new Error(errorMessage)
+            } else if (!input) {
               return new Error('No input provided')
             }
 
diff --git a/src/machines/modelingMachine.ts b/src/machines/modelingMachine.ts
index 2c6a4b36ecf..acaaaa87e5a 100644
--- a/src/machines/modelingMachine.ts
+++ b/src/machines/modelingMachine.ts
@@ -566,6 +566,8 @@ export const modelingMachineDefaultContext: ModelingMachineContext = {
   planesInitialized: false,
 }
 
+const NO_INPUT_PROVIDED_MESSAGE = 'No input provided'
+
 export const modelingMachine = setup({
   types: {
     context: {} as ModelingMachineContext,
@@ -1357,6 +1359,8 @@ export const modelingMachine = setup({
   },
   // end actions
   actors: {
+    /* Below are all the do-constrain sketch actors,
+     * which aren't using updateModelingState and don't have the 'no kcl errors' guard yet */
     'do-constrain-remove-constraint': fromPromise(
       async ({
         input: { selectionRanges, sketchDetails, data },
@@ -1694,6 +1698,9 @@ export const modelingMachine = setup({
         }
       }
     ),
+
+    /* Below are actors being defined in src/components/ModelingMachineProvider.tsx
+     * which aren't using updateModelingState and don't have the 'no kcl errors' guard yet */
     'Get vertical info': fromPromise(
       async (_: {
         input: Pick<ModelingMachineContext, 'selectionRanges' | 'sketchDetails'>
@@ -1775,76 +1782,167 @@ export const modelingMachine = setup({
         return {} as SetSelections
       }
     ),
-    extrudeAstMod: fromPromise<
-      unknown,
-      ModelingCommandSchema['Extrude'] | undefined
-    >(async ({ input }) => {
-      if (!input) return Promise.reject(new Error('No input provided'))
-      const { nodeToEdit, sketches, length } = input
-      const { ast } = kclManager
-      const astResult = addExtrude({
-        ast,
-        sketches,
-        length,
-        nodeToEdit,
-      })
-      if (err(astResult)) {
-        return Promise.reject(new Error("Couldn't add extrude statement"))
+    'set-up-draft-circle': fromPromise(
+      async (_: {
+        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
+          data: [x: number, y: number]
+        }
+      }) => {
+        return {} as SketchDetailsUpdate
       }
+    ),
+    'set-up-draft-circle-three-point': fromPromise(
+      async (_: {
+        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
+          data: { p1: [x: number, y: number]; p2: [x: number, y: number] }
+        }
+      }) => {
+        return {} as SketchDetailsUpdate
+      }
+    ),
+    'set-up-draft-rectangle': fromPromise(
+      async (_: {
+        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
+          data: [x: number, y: number]
+        }
+      }) => {
+        return {} as SketchDetailsUpdate
+      }
+    ),
+    'set-up-draft-center-rectangle': fromPromise(
+      async (_: {
+        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
+          data: [x: number, y: number]
+        }
+      }) => {
+        return {} as SketchDetailsUpdate
+      }
+    ),
+    'set-up-draft-arc': fromPromise(
+      async (_: {
+        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
+          data: [x: number, y: number]
+        }
+      }) => {
+        return {} as SketchDetailsUpdate
+      }
+    ),
+    'set-up-draft-arc-three-point': fromPromise(
+      async (_: {
+        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
+          data: [x: number, y: number]
+        }
+      }) => {
+        return {} as SketchDetailsUpdate
+      }
+    ),
+    'setup-client-side-sketch-segments': fromPromise(
+      async (_: {
+        input: Pick<ModelingMachineContext, 'sketchDetails' | 'selectionRanges'>
+      }) => {
+        return undefined
+      }
+    ),
+    'split-sketch-pipe-if-needed': fromPromise(
+      async (_: { input: Pick<ModelingMachineContext, 'sketchDetails'> }) => {
+        return {} as SketchDetailsUpdate
+      }
+    ),
+    'submit-prompt-edit': fromPromise(
+      async ({
+        input,
+      }: {
+        input: ModelingCommandSchema['Prompt-to-edit']
+      }) => {}
+    ),
 
-      const { modifiedAst, pathToNode } = astResult
-      await updateModelingState(
-        modifiedAst,
-        EXECUTION_TYPE_REAL,
-        {
-          kclManager,
-          editorManager,
-          codeManager,
-        },
-        {
-          focusPath: [pathToNode],
+    /* Below are recent modeling codemods that are using updateModelinState,
+     * trigger toastError on Error, and have the 'no kcl errors' guard yet */
+    extrudeAstMod: fromPromise(
+      async ({
+        input,
+      }: {
+        input: ModelingCommandSchema['Extrude'] | undefined
+      }) => {
+        if (!input) {
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
         }
-      )
-    }),
-    sweepAstMod: fromPromise<
-      unknown,
-      ModelingCommandSchema['Sweep'] | undefined
-    >(async ({ input }) => {
-      if (!input) return Promise.reject(new Error('No input provided'))
-      const { nodeToEdit, sketches, path, sectional } = input
-      const { ast } = kclManager
-      const astResult = addSweep({
-        ast,
-        sketches,
-        path,
-        sectional,
-        nodeToEdit,
-      })
-      if (err(astResult)) {
-        return Promise.reject(astResult)
+
+        const { nodeToEdit, sketches, length } = input
+        const { ast } = kclManager
+        const astResult = addExtrude({
+          ast,
+          sketches,
+          length,
+          nodeToEdit,
+        })
+        if (err(astResult)) {
+          return Promise.reject(new Error("Couldn't add extrude statement"))
+        }
+
+        const { modifiedAst, pathToNode } = astResult
+        await updateModelingState(
+          modifiedAst,
+          EXECUTION_TYPE_REAL,
+          {
+            kclManager,
+            editorManager,
+            codeManager,
+          },
+          {
+            focusPath: [pathToNode],
+          }
+        )
       }
+    ),
+    sweepAstMod: fromPromise(
+      async ({
+        input,
+      }: {
+        input: ModelingCommandSchema['Sweep'] | undefined
+      }) => {
+        if (!input) {
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
+        }
 
-      const { modifiedAst, pathToNode } = astResult
-      await updateModelingState(
-        modifiedAst,
-        EXECUTION_TYPE_REAL,
-        {
-          kclManager,
-          editorManager,
-          codeManager,
-        },
-        {
-          focusPath: [pathToNode],
+        const { nodeToEdit, sketches, path, sectional } = input
+        const { ast } = kclManager
+        const astResult = addSweep({
+          ast,
+          sketches,
+          path,
+          sectional,
+          nodeToEdit,
+        })
+        if (err(astResult)) {
+          return Promise.reject(astResult)
         }
-      )
-    }),
+
+        const { modifiedAst, pathToNode } = astResult
+        await updateModelingState(
+          modifiedAst,
+          EXECUTION_TYPE_REAL,
+          {
+            kclManager,
+            editorManager,
+            codeManager,
+          },
+          {
+            focusPath: [pathToNode],
+          }
+        )
+      }
+    ),
     loftAstMod: fromPromise(
       async ({
         input,
       }: {
         input: ModelingCommandSchema['Loft'] | undefined
       }) => {
-        if (!input) return Promise.reject(new Error('No input provided'))
+        if (!input) {
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
+        }
+
         const { sketches } = input
         const { ast } = kclManager
         const astResult = addLoft({ ast, sketches })
@@ -1867,47 +1965,56 @@ export const modelingMachine = setup({
         )
       }
     ),
-    revolveAstMod: fromPromise<
-      unknown,
-      ModelingCommandSchema['Revolve'] | undefined
-    >(async ({ input }) => {
-      if (!input) return Promise.reject(new Error('No input provided'))
-      const { nodeToEdit, sketches, angle, axis, edge, axisOrEdge } = input
-      const { ast } = kclManager
-      const astResult = addRevolve({
-        ast,
-        sketches,
-        angle,
-        axisOrEdge,
-        axis,
-        edge,
-        nodeToEdit,
-      })
-      if (err(astResult)) {
-        return Promise.reject(astResult)
-      }
+    revolveAstMod: fromPromise(
+      async ({
+        input,
+      }: {
+        input: ModelingCommandSchema['Revolve'] | undefined
+      }) => {
+        if (!input) {
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
+        }
 
-      const { modifiedAst, pathToNode } = astResult
-      await updateModelingState(
-        modifiedAst,
-        EXECUTION_TYPE_REAL,
-        {
-          kclManager,
-          editorManager,
-          codeManager,
-        },
-        {
-          focusPath: [pathToNode],
+        const { nodeToEdit, sketches, angle, axis, edge, axisOrEdge } = input
+        const { ast } = kclManager
+        const astResult = addRevolve({
+          ast,
+          sketches,
+          angle,
+          axisOrEdge,
+          axis,
+          edge,
+          nodeToEdit,
+        })
+        if (err(astResult)) {
+          return Promise.reject(astResult)
         }
-      )
-    }),
+
+        const { modifiedAst, pathToNode } = astResult
+        await updateModelingState(
+          modifiedAst,
+          EXECUTION_TYPE_REAL,
+          {
+            kclManager,
+            editorManager,
+            codeManager,
+          },
+          {
+            focusPath: [pathToNode],
+          }
+        )
+      }
+    ),
     offsetPlaneAstMod: fromPromise(
       async ({
         input,
       }: {
         input: ModelingCommandSchema['Offset plane'] | undefined
       }) => {
-        if (!input) return new Error('No input provided')
+        if (!input) {
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
+        }
+
         // Extract inputs
         const ast = kclManager.ast
         const { plane: selection, distance, nodeToEdit } = input
@@ -1989,9 +2096,10 @@ export const modelingMachine = setup({
       }: {
         input: ModelingCommandSchema['Helix'] | undefined
       }) => {
-        if (!input) return new Error('No input provided')
+        if (!input) {
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
+        }
         // Extract inputs
-        console.log('input', input)
         const ast = kclManager.ast
         const {
           mode,
@@ -2043,7 +2151,7 @@ export const modelingMachine = setup({
               cylinder.graphSelections[0].artifact?.type === 'wall'
             )
           ) {
-            return new Error('Cylinder argument not valid')
+            return Promise.reject(new Error('Cylinder argument not valid'))
           }
           const clonedAstForGetExtrude = structuredClone(ast)
           const extrudeLookupResult = getPathToExtrudeForSegmentSelection(
@@ -2052,7 +2160,7 @@ export const modelingMachine = setup({
             kclManager.artifactGraph
           )
           if (err(extrudeLookupResult)) {
-            return extrudeLookupResult
+            return Promise.reject(extrudeLookupResult)
           }
           const extrudeNode = getNodeFromPath<VariableDeclaration>(
             ast,
@@ -2060,18 +2168,20 @@ export const modelingMachine = setup({
             'VariableDeclaration'
           )
           if (err(extrudeNode)) {
-            return extrudeNode
+            return Promise.reject(extrudeNode)
           }
           cylinderDeclarator = extrudeNode.node.declaration
         } else if (mode === 'Axis' || mode === 'Edge') {
           const getAxisResult = getAxisExpressionAndIndex(mode, axis, edge, ast)
           if (err(getAxisResult)) {
-            return getAxisResult
+            return Promise.reject(getAxisResult)
           }
           axisExpression = getAxisResult.generatedAxis
         } else {
-          return new Error(
-            'Generated axis or cylinder declarator selection is missing.'
+          return Promise.reject(
+            new Error(
+              'Generated axis or cylinder declarator selection is missing.'
+            )
           )
         }
 
@@ -2132,7 +2242,7 @@ export const modelingMachine = setup({
         input: ModelingCommandSchema['Shell'] | undefined
       }) => {
         if (!input) {
-          return new Error('No input provided')
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
         }
 
         // Extract inputs
@@ -2173,9 +2283,11 @@ export const modelingMachine = setup({
             kclManager.artifactGraph
           )
           if (err(extrudeLookupResult)) {
-            return new Error(
-              "Couldn't find extrude paths from getPathToExtrudeForSegmentSelection",
-              { cause: extrudeLookupResult }
+            return Promise.reject(
+              new Error(
+                "Couldn't find extrude paths from getPathToExtrudeForSegmentSelection",
+                { cause: extrudeLookupResult }
+              )
             )
           }
 
@@ -2196,9 +2308,11 @@ export const modelingMachine = setup({
             'VariableDeclaration'
           )
           if (err(segmentNode)) {
-            return new Error("Couldn't find segment node from selection", {
-              cause: segmentNode,
-            })
+            return Promise.reject(
+              new Error("Couldn't find segment node from selection", {
+                cause: segmentNode,
+              })
+            )
           }
 
           if (extrudeNode.node.declaration.init.type === 'CallExpressionKw') {
@@ -2208,15 +2322,17 @@ export const modelingMachine = setup({
           ) {
             pathToExtrudeNode = extrudeLookupResult.pathToSegmentNode
           } else {
-            return new Error(
-              "Couldn't find extrude node that was either a call expression or a pipe",
-              { cause: segmentNode }
+            return Promise.reject(
+              new Error(
+                "Couldn't find extrude node that was either a call expression or a pipe",
+                { cause: segmentNode }
+              )
             )
           }
 
           const selectedArtifact = graphSelection.artifact
           if (!selectedArtifact) {
-            return new Error('Bad artifact from selection')
+            return Promise.reject(new Error('Bad artifact from selection'))
           }
 
           // Check on the selection, and handle the wall vs cap cases
@@ -2229,20 +2345,22 @@ export const modelingMachine = setup({
               extrudeLookupResult.pathToSegmentNode
             )
             if (err(tagResult)) {
-              return tagResult
+              return Promise.reject(tagResult)
             }
 
             const { tag } = tagResult
             expr = createLocalName(tag)
           } else {
-            return new Error('Artifact is neither a cap nor a wall')
+            return Promise.reject(
+              new Error('Artifact is neither a cap nor a wall')
+            )
           }
 
           faces.push(expr)
         }
 
         if (!pathToExtrudeNode) {
-          return new Error('No path to extrude node found')
+          return Promise.reject(new Error('No path to extrude node found'))
         }
 
         const extrudeNode = getNodeFromPath<VariableDeclarator>(
@@ -2251,9 +2369,11 @@ export const modelingMachine = setup({
           'VariableDeclarator'
         )
         if (err(extrudeNode)) {
-          return new Error("Couldn't find extrude node", {
-            cause: extrudeNode,
-          })
+          return Promise.reject(
+            new Error("Couldn't find extrude node", {
+              cause: extrudeNode,
+            })
+          )
         }
 
         // Perform the shell op
@@ -2308,7 +2428,7 @@ export const modelingMachine = setup({
         input: ModelingCommandSchema['Fillet'] | undefined
       }) => {
         if (!input) {
-          return new Error('No input provided')
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
         }
 
         // Extract inputs
@@ -2401,7 +2521,7 @@ export const modelingMachine = setup({
         input: ModelingCommandSchema['Chamfer'] | undefined
       }) => {
         if (!input) {
-          return Promise.reject(new Error('No input provided'))
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
         }
 
         // Extract inputs
@@ -2492,10 +2612,13 @@ export const modelingMachine = setup({
       }: {
         input: ModelingCommandSchema['event.parameter.create'] | undefined
       }) => {
-        if (!input) return new Error('No input provided')
+        if (!input) {
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
+        }
+
         const { value } = input
         if (!('variableName' in value)) {
-          return new Error('variable name is required')
+          return Promise.reject(new Error('variable name is required'))
         }
         const newAst = insertNamedConstant({
           node: kclManager.ast,
@@ -2514,7 +2637,10 @@ export const modelingMachine = setup({
       }: {
         input: ModelingCommandSchema['event.parameter.edit'] | undefined
       }) => {
-        if (!input) return new Error('No input provided')
+        if (!input) {
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
+        }
+
         // Get the variable AST node to edit
         const { nodeToEdit, value } = input
         const newAst = structuredClone(kclManager.ast)
@@ -2528,7 +2654,7 @@ export const modelingMachine = setup({
           variableNode.node.type !== 'VariableDeclarator' ||
           !variableNode.node
         ) {
-          return new Error('No variable found, this is a bug')
+          return Promise.reject(new Error('No variable found, this is a bug'))
         }
 
         // Mutate the variable's value
@@ -2541,79 +2667,6 @@ export const modelingMachine = setup({
         })
       }
     ),
-    'set-up-draft-circle': fromPromise(
-      async (_: {
-        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
-          data: [x: number, y: number]
-        }
-      }) => {
-        return {} as SketchDetailsUpdate
-      }
-    ),
-    'set-up-draft-circle-three-point': fromPromise(
-      async (_: {
-        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
-          data: { p1: [x: number, y: number]; p2: [x: number, y: number] }
-        }
-      }) => {
-        return {} as SketchDetailsUpdate
-      }
-    ),
-    'set-up-draft-rectangle': fromPromise(
-      async (_: {
-        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
-          data: [x: number, y: number]
-        }
-      }) => {
-        return {} as SketchDetailsUpdate
-      }
-    ),
-    'set-up-draft-center-rectangle': fromPromise(
-      async (_: {
-        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
-          data: [x: number, y: number]
-        }
-      }) => {
-        return {} as SketchDetailsUpdate
-      }
-    ),
-    'set-up-draft-arc': fromPromise(
-      async (_: {
-        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
-          data: [x: number, y: number]
-        }
-      }) => {
-        return {} as SketchDetailsUpdate
-      }
-    ),
-    'set-up-draft-arc-three-point': fromPromise(
-      async (_: {
-        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
-          data: [x: number, y: number]
-        }
-      }) => {
-        return {} as SketchDetailsUpdate
-      }
-    ),
-    'setup-client-side-sketch-segments': fromPromise(
-      async (_: {
-        input: Pick<ModelingMachineContext, 'sketchDetails' | 'selectionRanges'>
-      }) => {
-        return undefined
-      }
-    ),
-    'split-sketch-pipe-if-needed': fromPromise(
-      async (_: { input: Pick<ModelingMachineContext, 'sketchDetails'> }) => {
-        return {} as SketchDetailsUpdate
-      }
-    ),
-    'submit-prompt-edit': fromPromise(
-      async ({
-        input,
-      }: {
-        input: ModelingCommandSchema['Prompt-to-edit']
-      }) => {}
-    ),
     deleteSelectionAstMod: fromPromise(
       ({
         input: { selectionRanges },
@@ -2648,12 +2701,15 @@ export const modelingMachine = setup({
       }: {
         input: ModelingCommandSchema['Appearance'] | undefined
       }) => {
-        if (!input) return new Error('No input provided')
+        if (!input) {
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
+        }
+
         // Extract inputs
         const ast = kclManager.ast
         const { color, nodeToEdit } = input
         if (!(nodeToEdit && typeof nodeToEdit[1][0] === 'number')) {
-          return new Error('Appearance is only an edit flow')
+          return Promise.reject(new Error('Appearance is only an edit flow'))
         }
 
         const result = setAppearance({
@@ -2663,7 +2719,7 @@ export const modelingMachine = setup({
         })
 
         if (err(result)) {
-          return err(result)
+          return Promise.reject(err(result))
         }
 
         await updateModelingState(
@@ -2686,7 +2742,10 @@ export const modelingMachine = setup({
       }: {
         input: ModelingCommandSchema['Translate'] | undefined
       }) => {
-        if (!input) return Promise.reject(new Error('No input provided'))
+        if (!input) {
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
+        }
+
         const ast = kclManager.ast
         const modifiedAst = structuredClone(ast)
         const { x, y, z, nodeToEdit, selection } = input
@@ -2764,7 +2823,10 @@ export const modelingMachine = setup({
       }: {
         input: ModelingCommandSchema['Rotate'] | undefined
       }) => {
-        if (!input) return Promise.reject(new Error('No input provided'))
+        if (!input) {
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
+        }
+
         const ast = kclManager.ast
         const modifiedAst = structuredClone(ast)
         const { roll, pitch, yaw, nodeToEdit, selection } = input
@@ -2842,7 +2904,10 @@ export const modelingMachine = setup({
       }: {
         input: ModelingCommandSchema['Clone'] | undefined
       }) => {
-        if (!input) return Promise.reject(new Error('No input provided'))
+        if (!input) {
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
+        }
+
         const ast = kclManager.ast
         const { nodeToEdit, selection, variableName } = input
         let pathToNode = nodeToEdit
@@ -2933,15 +2998,17 @@ export const modelingMachine = setup({
         input: ModelingCommandSchema['Boolean Subtract'] | undefined
       }) => {
         if (!input) {
-          return new Error('No input provided')
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
         }
+
         const { target, tool } = input
         if (
           !target.graphSelections[0].artifact ||
           !tool.graphSelections[0].artifact
         ) {
-          return new Error('No artifact in selections found')
+          return Promise.reject(new Error('No artifact in selections found'))
         }
+
         await applySubtractFromTargetOperatorSelections(
           target.graphSelections[0],
           tool.graphSelections[0],
@@ -2961,12 +3028,14 @@ export const modelingMachine = setup({
         input: ModelingCommandSchema['Boolean Union'] | undefined
       }) => {
         if (!input) {
-          return new Error('No input provided')
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
         }
+
         const { solids } = input
         if (!solids.graphSelections[0].artifact) {
-          return new Error('No artifact in selections found')
+          return Promise.reject(new Error('No artifact in selections found'))
         }
+
         await applyUnionFromTargetOperatorSelections(solids, {
           kclManager,
           codeManager,
@@ -2982,12 +3051,14 @@ export const modelingMachine = setup({
         input: ModelingCommandSchema['Boolean Union'] | undefined
       }) => {
         if (!input) {
-          return new Error('No input provided')
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
         }
+
         const { solids } = input
         if (!solids.graphSelections[0].artifact) {
-          return new Error('No artifact in selections found')
+          return Promise.reject(new Error('No artifact in selections found'))
         }
+
         await applyIntersectFromTargetOperatorSelections(solids, {
           kclManager,
           codeManager,
@@ -2996,6 +3067,8 @@ export const modelingMachine = setup({
         })
       }
     ),
+
+    /* Pierre: looks like somewhat of a one-off */
     'reeval-node-paths': fromPromise(
       async ({
         input: { sketchDetails },
@@ -3096,50 +3169,74 @@ export const modelingMachine = setup({
             target: 'animating to existing sketch',
             guard: 'Selection is on face',
           },
-          'Sketch no face',
+          {
+            target: 'Sketch no face',
+            guard: 'no kcl errors',
+          },
         ],
 
         Extrude: {
           target: 'Applying extrude',
           reenter: true,
+          guard: 'no kcl errors',
+        },
+
+        Sweep: {
+          target: 'Applying sweep',
+          reenter: true,
+          guard: 'no kcl errors',
+        },
+
+        Loft: {
+          target: 'Applying loft',
+          reenter: true,
+          guard: 'no kcl errors',
         },
 
         Revolve: {
           target: 'Applying revolve',
           reenter: true,
+          guard: 'no kcl errors',
         },
 
-        Sweep: {
-          target: 'Applying sweep',
+        'Offset plane': {
+          target: 'Applying offset plane',
           reenter: true,
+          guard: 'no kcl errors',
         },
 
-        Loft: {
-          target: 'Applying loft',
+        Helix: {
+          target: 'Applying helix',
           reenter: true,
+          guard: 'no kcl errors',
         },
 
         Shell: {
           target: 'Applying shell',
           reenter: true,
+          guard: 'no kcl errors',
         },
 
         Fillet: {
           target: 'Applying fillet',
           reenter: true,
+          guard: 'no kcl errors',
         },
 
         Chamfer: {
           target: 'Applying chamfer',
           reenter: true,
+          guard: 'no kcl errors',
         },
 
         'event.parameter.create': {
           target: '#Modeling.parameter.creating',
+          guard: 'no kcl errors',
         },
 
         'event.parameter.edit': {
           target: '#Modeling.parameter.editing',
+          guard: 'no kcl errors',
         },
 
         Export: {
@@ -3164,41 +3261,44 @@ export const modelingMachine = setup({
           actions: ['Submit to Text-to-CAD API'],
         },
 
-        'Offset plane': {
-          target: 'Applying offset plane',
-          reenter: true,
-        },
-
-        Helix: {
-          target: 'Applying helix',
-          reenter: true,
-        },
-
         'Prompt-to-edit': 'Applying Prompt-to-edit',
 
         Appearance: {
           target: 'Applying appearance',
           reenter: true,
+          guard: 'no kcl errors',
         },
 
         Translate: {
           target: 'Applying translate',
           reenter: true,
+          guard: 'no kcl errors',
         },
 
         Rotate: {
           target: 'Applying rotate',
           reenter: true,
+          guard: 'no kcl errors',
         },
 
         Clone: {
           target: 'Applying clone',
           reenter: true,
+          guard: 'no kcl errors',
         },
 
-        'Boolean Subtract': 'Boolean subtracting',
-        'Boolean Union': 'Boolean uniting',
-        'Boolean Intersect': 'Boolean intersecting',
+        'Boolean Subtract': {
+          target: 'Boolean subtracting',
+          guard: 'no kcl errors',
+        },
+        'Boolean Union': {
+          target: 'Boolean uniting',
+          guard: 'no kcl errors',
+        },
+        'Boolean Intersect': {
+          target: 'Boolean intersecting',
+          guard: 'no kcl errors',
+        },
       },
 
       entry: 'reset client scene mouse handlers',
@@ -4415,12 +4515,12 @@ export const modelingMachine = setup({
       },
     },
 
-    'Applying revolve': {
+    'Applying sweep': {
       invoke: {
-        src: 'revolveAstMod',
-        id: 'revolveAstMod',
+        src: 'sweepAstMod',
+        id: 'sweepAstMod',
         input: ({ event }) => {
-          if (event.type !== 'Revolve') return undefined
+          if (event.type !== 'Sweep') return undefined
           return event.data
         },
         onDone: ['idle'],
@@ -4431,55 +4531,67 @@ export const modelingMachine = setup({
       },
     },
 
-    'Applying offset plane': {
+    'Applying loft': {
       invoke: {
-        src: 'offsetPlaneAstMod',
-        id: 'offsetPlaneAstMod',
+        src: 'loftAstMod',
+        id: 'loftAstMod',
         input: ({ event }) => {
-          if (event.type !== 'Offset plane') return undefined
+          if (event.type !== 'Loft') return undefined
           return event.data
         },
         onDone: ['idle'],
-        onError: ['idle'],
+        onError: {
+          target: 'idle',
+          actions: 'toastError',
+        },
       },
     },
 
-    'Applying helix': {
+    'Applying revolve': {
       invoke: {
-        src: 'helixAstMod',
-        id: 'helixAstMod',
+        src: 'revolveAstMod',
+        id: 'revolveAstMod',
         input: ({ event }) => {
-          if (event.type !== 'Helix') return undefined
+          if (event.type !== 'Revolve') return undefined
           return event.data
         },
         onDone: ['idle'],
-        onError: ['idle'],
+        onError: {
+          target: 'idle',
+          actions: 'toastError',
+        },
       },
     },
 
-    'Applying sweep': {
+    'Applying offset plane': {
       invoke: {
-        src: 'sweepAstMod',
-        id: 'sweepAstMod',
+        src: 'offsetPlaneAstMod',
+        id: 'offsetPlaneAstMod',
         input: ({ event }) => {
-          if (event.type !== 'Sweep') return undefined
+          if (event.type !== 'Offset plane') return undefined
           return event.data
         },
         onDone: ['idle'],
-        onError: ['idle'],
+        onError: {
+          target: 'idle',
+          actions: 'toastError',
+        },
       },
     },
 
-    'Applying loft': {
+    'Applying helix': {
       invoke: {
-        src: 'loftAstMod',
-        id: 'loftAstMod',
+        src: 'helixAstMod',
+        id: 'helixAstMod',
         input: ({ event }) => {
-          if (event.type !== 'Loft') return undefined
+          if (event.type !== 'Helix') return undefined
           return event.data
         },
         onDone: ['idle'],
-        onError: ['idle'],
+        onError: {
+          target: 'idle',
+          actions: 'toastError',
+        },
       },
     },
 
@@ -4492,7 +4604,10 @@ export const modelingMachine = setup({
           return event.data
         },
         onDone: ['idle'],
-        onError: ['idle'],
+        onError: {
+          target: 'idle',
+          actions: 'toastError',
+        },
       },
     },
 
@@ -4505,7 +4620,10 @@ export const modelingMachine = setup({
           return event.data
         },
         onDone: ['idle'],
-        onError: ['idle'],
+        onError: {
+          target: 'idle',
+          actions: 'toastError',
+        },
       },
     },
 
@@ -4518,7 +4636,10 @@ export const modelingMachine = setup({
           return event.data
         },
         onDone: ['idle'],
-        onError: ['idle'],
+        onError: {
+          target: 'idle',
+          actions: 'toastError',
+        },
       },
     },
 
@@ -4535,7 +4656,10 @@ export const modelingMachine = setup({
               return event.data
             },
             onDone: ['#Modeling.idle'],
-            onError: ['#Modeling.idle'],
+            onError: {
+              target: '#Modeling.idle',
+              actions: 'toastError',
+            },
           },
         },
         editing: {
@@ -4547,7 +4671,10 @@ export const modelingMachine = setup({
               return event.data
             },
             onDone: ['#Modeling.idle'],
-            onError: ['#Modeling.idle'],
+            onError: {
+              target: '#Modeling.idle',
+              actions: 'toastError',
+            },
           },
         },
       },
@@ -4604,7 +4731,10 @@ export const modelingMachine = setup({
           return event.data
         },
         onDone: ['idle'],
-        onError: ['idle'],
+        onError: {
+          target: 'idle',
+          actions: 'toastError',
+        },
       },
     },
 
@@ -4617,7 +4747,10 @@ export const modelingMachine = setup({
           return event.data
         },
         onDone: ['idle'],
-        onError: ['idle'],
+        onError: {
+          target: 'idle',
+          actions: 'toastError',
+        },
       },
     },
 
@@ -4630,7 +4763,10 @@ export const modelingMachine = setup({
           return event.data
         },
         onDone: ['idle'],
-        onError: ['idle'],
+        onError: {
+          target: 'idle',
+          actions: 'toastError',
+        },
       },
     },
 
@@ -4686,7 +4822,10 @@ export const modelingMachine = setup({
         input: ({ event }) =>
           event.type !== 'Boolean Subtract' ? undefined : event.data,
         onDone: 'idle',
-        onError: 'idle',
+        onError: {
+          target: 'idle',
+          actions: 'toastError',
+        },
       },
     },
 
@@ -4697,7 +4836,10 @@ export const modelingMachine = setup({
         input: ({ event }) =>
           event.type !== 'Boolean Union' ? undefined : event.data,
         onDone: 'idle',
-        onError: 'idle',
+        onError: {
+          target: 'idle',
+          actions: 'toastError',
+        },
       },
     },
 
@@ -4708,7 +4850,10 @@ export const modelingMachine = setup({
         input: ({ event }) =>
           event.type !== 'Boolean Intersect' ? undefined : event.data,
         onDone: 'idle',
-        onError: 'idle',
+        onError: {
+          target: 'idle',
+          actions: 'toastError',
+        },
       },
     },
   },


Reviews:
user: login: pierremtb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T20:03:54Z
user: login: pierremtb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T20:04:18Z
user: login: pierremtb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T20:04:51Z
user: login: pierremtb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T15:04:23Z
user: login: pierremtb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T15:55:25Z
user: login: pierremtb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T15:56:23Z
user: login: pierremtb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T15:57:08Z
user: login: pierremtb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T15:58:11Z
user: login: pierremtb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T22:14:14Z
user: login: pierremtb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T22:15:12Z
user: login: pierremtb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T22:16:10Z
user: login: pierremtb
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T22:16:56Z
user: login: nadr0
body: I tested locally and I was not able to figure out a way to delete my code via the empty ast + code mod. This looks like a great guard.

Awesome work on this Pierre. 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T16:19:26Z

Review comments:
diff_hunk: @@ -3096,50 +3162,74 @@ export const modelingMachine = setup({
             target: 'animating to existing sketch',
             guard: 'Selection is on face',
           },
-          'Sketch no face',
+          {
+            target: 'Sketch no face',
+            guard: 'no kcl errors',
+          },
path: src/machines/modelingMachine.ts
user: login: pierremtb
body: Not entirely sure if this is how we should do it but it seems to work?
created_at: 2025-05-13T20:03:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3164,41 +3254,44 @@ export const modelingMachine = setup({
           actions: ['Submit to Text-to-CAD API'],
         },
 
-        'Offset plane': {
-          target: 'Applying offset plane',
-          reenter: true,
-        },
-
-        Helix: {
-          target: 'Applying helix',
-          reenter: true,
-        },
-
         'Prompt-to-edit': 'Applying Prompt-to-edit',
 
         Appearance: {
           target: 'Applying appearance',
           reenter: true,
+          guard: 'no kcl errors',
         },
 
         Translate: {
           target: 'Applying translate',
           reenter: true,
+          guard: 'no kcl errors',
         },
 
         Rotate: {
           target: 'Applying rotate',
           reenter: true,
+          guard: 'no kcl errors',
         },
 
         Clone: {
           target: 'Applying clone',
           reenter: true,
+          guard: 'no kcl errors',
         },
 
-        'Boolean Subtract': 'Boolean subtracting',
-        'Boolean Union': 'Boolean uniting',
-        'Boolean Intersect': 'Boolean intersecting',
+        'Boolean Subtract': {
+          target: 'Boolean subtracting',
+          guard: 'no kcl errors',
+        },
+        'Boolean Union': {
+          target: 'Boolean uniting',
+          guard: 'no kcl errors',
+        },
+        'Boolean Intersect': {
+          target: 'Boolean intersecting',
+          guard: 'no kcl errors',
+        },
path: src/machines/modelingMachine.ts
user: login: pierremtb
body: I noticed those don't have `reenter: true` compared to all the others
created_at: 2025-05-13T20:04:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4547,7 +4664,10 @@ export const modelingMachine = setup({
               return event.data
             },
             onDone: ['#Modeling.idle'],
-            onError: ['#Modeling.idle'],
+            onError: {
+              target: '#Modeling.idle',
+              actions: 'toastError',
+            },
path: src/machines/modelingMachine.ts
user: login: pierremtb
body: Those two were doing fancy # business, not sure what's up
created_at: 2025-05-13T20:04:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1365,18 +1365,18 @@ solid001 = subtract([extrude001], tools = [extrude002])
     await page.addInitScript(async () => {
       localStorage.setItem(
         'persistCode',
-        `fn in2mm = (inches) => {
+        `fn in2mm(@inches) {
     return inches * 25.4
   }
 
-  const railTop = in2mm(.748)
-  const railSide = in2mm(.024)
-  const railBaseWidth = in2mm(.612)
-  const railWideWidth = in2mm(.835)
-  const railBaseLength = in2mm(.200)
-  const railClampable = in2mm(.200)
+  railTop = in2mm(.748)
+  railSide = in2mm(.024)
+  railBaseWidth = in2mm(.612)
+  railWideWidth = in2mm(.835)
+  railBaseLength = in2mm(.200)
+  railClampable = in2mm(.200)
 
-  const rail = startSketchOn(XZ)
+  rail = startSketchOn(XZ)
path: e2e/playwright/sketch-tests.spec.ts
user: login: pierremtb
body: This outdated KCL here was actually leading to errors, and showcasing the very overwrite behavior that this PR is trying to fix at 00:30 in the video below


https://github.com/user-attachments/assets/05a74204-c6d3-4711-854b-4e4d3954208f




created_at: 2025-05-14T15:04:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -578,7 +581,9 @@ export const modelingMachine = setup({
     'has valid selection for deletion': () => false,
     'is-error-free': () => false,
     'no kcl errors': () => {
-      return !kclManager.hasErrors()
+      const hasErrors = kclManager.hasErrors()
+      if (hasErrors) toast.error(KCL_ERRORS_MESSAGE)
path: src/machines/modelingMachine.ts
user: login: pierremtb
body: Toasting in here is probably anti-pattern, but this hasn't led to any side effect in my testing and I'm a little out of options here?
created_at: 2025-05-14T15:55:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1775,76 +1785,167 @@ export const modelingMachine = setup({
         return {} as SetSelections
       }
     ),
-    extrudeAstMod: fromPromise<
-      unknown,
-      ModelingCommandSchema['Extrude'] | undefined
-    >(async ({ input }) => {
-      if (!input) return Promise.reject(new Error('No input provided'))
-      const { nodeToEdit, sketches, length } = input
-      const { ast } = kclManager
-      const astResult = addExtrude({
-        ast,
-        sketches,
-        length,
-        nodeToEdit,
-      })
-      if (err(astResult)) {
-        return Promise.reject(new Error("Couldn't add extrude statement"))
+    'set-up-draft-circle': fromPromise(
+      async (_: {
+        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
+          data: [x: number, y: number]
+        }
+      }) => {
+        return {} as SketchDetailsUpdate
       }
+    ),
+    'set-up-draft-circle-three-point': fromPromise(
+      async (_: {
+        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
+          data: { p1: [x: number, y: number]; p2: [x: number, y: number] }
+        }
+      }) => {
+        return {} as SketchDetailsUpdate
+      }
+    ),
+    'set-up-draft-rectangle': fromPromise(
+      async (_: {
+        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
+          data: [x: number, y: number]
+        }
+      }) => {
+        return {} as SketchDetailsUpdate
+      }
+    ),
+    'set-up-draft-center-rectangle': fromPromise(
+      async (_: {
+        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
+          data: [x: number, y: number]
+        }
+      }) => {
+        return {} as SketchDetailsUpdate
+      }
+    ),
+    'set-up-draft-arc': fromPromise(
+      async (_: {
+        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
+          data: [x: number, y: number]
+        }
+      }) => {
+        return {} as SketchDetailsUpdate
+      }
+    ),
+    'set-up-draft-arc-three-point': fromPromise(
+      async (_: {
+        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
+          data: [x: number, y: number]
+        }
+      }) => {
+        return {} as SketchDetailsUpdate
+      }
+    ),
+    'setup-client-side-sketch-segments': fromPromise(
+      async (_: {
+        input: Pick<ModelingMachineContext, 'sketchDetails' | 'selectionRanges'>
+      }) => {
+        return undefined
+      }
+    ),
+    'split-sketch-pipe-if-needed': fromPromise(
+      async (_: { input: Pick<ModelingMachineContext, 'sketchDetails'> }) => {
+        return {} as SketchDetailsUpdate
+      }
+    ),
+    'submit-prompt-edit': fromPromise(
+      async ({
+        input,
+      }: {
+        input: ModelingCommandSchema['Prompt-to-edit']
+      }) => {}
+    ),
 
-      const { modifiedAst, pathToNode } = astResult
-      await updateModelingState(
-        modifiedAst,
-        EXECUTION_TYPE_REAL,
-        {
-          kclManager,
-          editorManager,
-          codeManager,
-        },
-        {
-          focusPath: [pathToNode],
+    /* Below are recent modeling codemods that are using updateModelinState,
+     * trigger toastError on Error, and have the 'no kcl errors' guard yet */
+    extrudeAstMod: fromPromise(
+      async ({
+        input,
+      }: {
+        input: ModelingCommandSchema['Extrude'] | undefined
+      }) => {
+        if (!input) {
+          return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
         }
path: src/machines/modelingMachine.ts
user: login: pierremtb
body: These are just moved from below, no change
created_at: 2025-05-14T15:56:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2541,79 +2670,6 @@ export const modelingMachine = setup({
         })
       }
     ),
-    'set-up-draft-circle': fromPromise(
-      async (_: {
-        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
-          data: [x: number, y: number]
-        }
-      }) => {
-        return {} as SketchDetailsUpdate
-      }
-    ),
-    'set-up-draft-circle-three-point': fromPromise(
-      async (_: {
-        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
-          data: { p1: [x: number, y: number]; p2: [x: number, y: number] }
-        }
-      }) => {
-        return {} as SketchDetailsUpdate
-      }
-    ),
-    'set-up-draft-rectangle': fromPromise(
-      async (_: {
-        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
-          data: [x: number, y: number]
-        }
-      }) => {
-        return {} as SketchDetailsUpdate
-      }
-    ),
-    'set-up-draft-center-rectangle': fromPromise(
-      async (_: {
-        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
-          data: [x: number, y: number]
-        }
-      }) => {
-        return {} as SketchDetailsUpdate
-      }
-    ),
-    'set-up-draft-arc': fromPromise(
-      async (_: {
-        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
-          data: [x: number, y: number]
-        }
-      }) => {
-        return {} as SketchDetailsUpdate
-      }
-    ),
-    'set-up-draft-arc-three-point': fromPromise(
-      async (_: {
-        input: Pick<ModelingMachineContext, 'sketchDetails'> & {
-          data: [x: number, y: number]
-        }
-      }) => {
-        return {} as SketchDetailsUpdate
-      }
-    ),
-    'setup-client-side-sketch-segments': fromPromise(
-      async (_: {
-        input: Pick<ModelingMachineContext, 'sketchDetails' | 'selectionRanges'>
-      }) => {
-        return undefined
-      }
-    ),
-    'split-sketch-pipe-if-needed': fromPromise(
-      async (_: { input: Pick<ModelingMachineContext, 'sketchDetails'> }) => {
-        return {} as SketchDetailsUpdate
-      }
-    ),
-    'submit-prompt-edit': fromPromise(
-      async ({
-        input,
-      }: {
-        input: ModelingCommandSchema['Prompt-to-edit']
-      }) => {}
-    ),
path: src/machines/modelingMachine.ts
user: login: pierremtb
body: Those are just moved up, see previous message
created_at: 2025-05-14T15:57:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3096,50 +3172,74 @@ export const modelingMachine = setup({
             target: 'animating to existing sketch',
             guard: 'Selection is on face',
           },
-          'Sketch no face',
+          {
+            target: 'Sketch no face',
+            guard: 'no kcl errors',
+          },
         ],
 
         Extrude: {
           target: 'Applying extrude',
           reenter: true,
+          guard: 'no kcl errors',
+        },
+
+        Sweep: {
+          target: 'Applying sweep',
+          reenter: true,
+          guard: 'no kcl errors',
+        },
path: src/machines/modelingMachine.ts
user: login: pierremtb
body: Doing just a tiny bit of reordering here and below for consistency
created_at: 2025-05-14T15:58:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -179,7 +179,7 @@ extrude001 = extrude(sketch001, length = 5)`
     await page.setBodyDimensions({ width: 1200, height: 500 })
     await homePage.goToModelingScene()
 
-    await scene.settled(cmdBar)
path: e2e/playwright/code-pane-and-errors.spec.ts
user: login: pierremtb
body: We can't use `scene.settled` anymore on bad kcl since the Start Sketch button will never be enabled @franknoirot. Guess I could update the fixture? 👀 
created_at: 2025-05-14T22:14:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -40,7 +41,8 @@ test.describe('Regression tests', () => {
     await page.setBodyDimensions({ width: 1000, height: 500 })
 
     await homePage.goToModelingScene()
-    await u.waitForPageLoad()
path: e2e/playwright/regression-tests.spec.ts
user: login: pierremtb
body: Same for this guy
created_at: 2025-05-14T22:15:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -158,7 +158,8 @@ export function Toolbar({
       const isDisabled =
         disableAllButtons ||
         !isConfiguredAvailable ||
-        maybeIconConfig.disabled?.(state) === true
+        maybeIconConfig.disabled?.(state) === true ||
+        kclManager.hasErrors()
path: src/Toolbar.tsx
user: login: pierremtb
body: @franknoirot I think I'd like to wrap `hasErrors()` here in some `hasNoKclErrors(kclManager)` function or something like that so we can edit it in one place if need be
created_at: 2025-05-14T22:16:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -578,7 +580,8 @@ export const modelingMachine = setup({
     'has valid selection for deletion': () => false,
     'is-error-free': () => false,
     'no kcl errors': () => {
-      return !kclManager.hasErrors()
+      const hasErrors = kclManager.hasErrors()
+      return !hasErrors
path: src/machines/modelingMachine.ts
user: login: pierremtb
body: https://github.com/KittyCAD/modeling-app/pull/6910/files#r2089843309 and like reusing it here as well
created_at: 2025-05-14T22:16:56Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-05-13T18:42:36Z
body: [vc]: #ypxldceDiKrLonFTDPl6zDNlzXYOfjQeORwXZm8IPmU=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJtb2RlbGluZy1hcHAiLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20va2l0dHljYWQvbW9kZWxpbmctYXBwL0JWYmZmbzR1bkc5b3lYejZBb0EyTXdQV0M3UnIiLCJwcmV2aWV3VXJsIjoibW9kZWxpbmctYXBwLWdpdC1waWVycmVtdGItaXNzdWU2NTE0LW1vcmUtY29uc2ktMWIzNWNiLWtpdHR5Y2FkLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoibW9kZWxpbmctYXBwLWdpdC1waWVycmVtdGItaXNzdWU2NTE0LW1vcmUtY29uc2ktMWIzNWNiLWtpdHR5Y2FkLnZlcmNlbC5hcHAifSwicm9vdERpcmVjdG9yeSI6bnVsbH1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **modeling-app** | ✅ Ready ([Inspect](https://vercel.com/kittycad/modeling-app/BVbffo4unG9oyXz6AoA2MwPWC7Rr)) | [Visit Preview](https://modeling-app-git-pierremtb-issue6514-more-consi-1b35cb-kittycad.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/modeling-app-git-pierremtb-issue6514-more-consi-1b35cb-kittycad.vercel.app?via=pr-comment-feedback-link) | May 14, 2025 10:41pm |


author_association: NONE
user: login: nadr0
created_at: 2025-05-15T16:16:22Z
body: I tested with the commands in the feature tree and those do not go through :+1: 
author_association: CONTRIBUTOR