Compare commits

...

9 Commits

Author SHA1 Message Date
48beef4ae5 Example using preview environments for our api in dev 2025-06-27 13:21:54 -04:00
f49cf8281c Allow point-and-click Substract to take in multiple solids and tools (#7614)
* Allow point-and-click Substract to take in multiple tools
Fixes #7612

* Change target to solids for consistency and make it support multi select too

* Improve err message

* Update src/lang/modifyAst/boolean.ts

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

* Update src/lang/modifyAst/boolean.ts

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>

* Good bot

* Reduce array to single value if len 1

* Remove console.log

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
2025-06-26 16:43:53 -04:00
7de27c648f Revoke token when logging out (#7493)
* Revoke token when logging out

* extract OAUTH2_DEVICE_CLIENT_ID

* Update snapshots

* Update snapshots

* try fix

* try fix

* Move client id to `@src/lib/constants`

---------

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: Pierre Jacquier <pierre@zoo.dev>
2025-06-26 15:24:16 -04:00
344fb6f84d Hide Helix arguments that should have been hidden, plus other flow fixes (#7606)
* Make sure mode-related args are hidden in point-and-click commands after option args change
Fixes #7589

* WIP improving helix flows and fixing tests

* Fix 2 more tests

* Add test step for opt arg

* Fix last helix test

* Clean up tests, hope to fix CI
2025-06-26 14:12:36 -04:00
df808b3e58 Bump google-github-actions/auth from 2.1.8 to 2.1.10 in the patch group across 1 directory (#6566)
Bump google-github-actions/auth in the patch group across 1 directory

Bumps the patch group with 1 update in the / directory: [google-github-actions/auth](https://github.com/google-github-actions/auth).


Updates `google-github-actions/auth` from 2.1.8 to 2.1.10
- [Release notes](https://github.com/google-github-actions/auth/releases)
- [Changelog](https://github.com/google-github-actions/auth/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google-github-actions/auth/compare/v2.1.8...v2.1.10)

---
updated-dependencies:
- dependency-name: google-github-actions/auth
  dependency-version: 2.1.10
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2025-06-25 13:33:09 -04:00
e1ab6bbc48 Swap "must" for "should" in identifier casing lint (#7604)
Draft: Swap "must" for "should" in identifier casing lint
2025-06-25 12:48:02 -04:00
0a1f35b89a Release KCL 83 (#7603) 2025-06-25 10:42:57 -04:00
78278d6889 Force the samples manifest to be updated (#7591)
* Force the samples manifest to be updated

* Skip manifest generation on Windows

This results in non-POSIX paths in the manifest.
2025-06-25 10:42:39 -04:00
6f1a539e83 Error on non-count indexing (#7539)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-06-25 20:36:57 +12:00
31 changed files with 505 additions and 388 deletions

View File

@ -362,7 +362,7 @@ jobs:
- name: Authenticate to Google Cloud
if: ${{ env.IS_STAGING == 'true' }}
uses: 'google-github-actions/auth@v2.1.8'
uses: 'google-github-actions/auth@v2.1.10'
with:
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'

View File

@ -328,7 +328,7 @@ jobs:
mkdir -p releases/language-server/${{ env.TAG }}
cp -r build/* releases/language-server/${{ env.TAG }}
- name: "Authenticate to Google Cloud"
uses: "google-github-actions/auth@v2.1.8"
uses: "google-github-actions/auth@v2.1.10"
with:
credentials_json: "${{ secrets.GOOGLE_CLOUD_DL_SA }}"
- name: Set up Cloud SDK

View File

@ -108,7 +108,7 @@ jobs:
run: npm run files:set-notes
- name: Authenticate to Google Cloud
uses: 'google-github-actions/auth@v2.1.8'
uses: 'google-github-actions/auth@v2.1.10'
with:
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'

View File

@ -62,7 +62,10 @@ else
endif
public/kcl-samples/manifest.json: $(KCL_SOURCES)
ifndef WINDOWS
cd rust/kcl-lib && EXPECTORATE=overwrite cargo test generate_manifest
@ touch $@
endif
.vite/build/main.js: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES)
npm run tronb:vite:dev

View File

@ -12,7 +12,7 @@ test.describe('Point and click for boolean workflows', () => {
},
{
name: 'subtract',
code: 'subtract([extrude001], tools = [extrude006])',
code: 'subtract(extrude001, tools = extrude006)',
},
{
name: 'intersect',
@ -81,6 +81,8 @@ test.describe('Point and click for boolean workflows', () => {
if (operationName !== 'subtract') {
// should down shift key to select multiple objects
await page.keyboard.down('Shift')
} else {
await cmdBar.progressCmdBar()
}
// Select second object
@ -103,8 +105,8 @@ test.describe('Point and click for boolean workflows', () => {
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Tool: '1 path',
Target: '1 path',
Solids: '1 path',
Tools: '1 path',
},
commandName,
})

View File

@ -288,7 +288,7 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
// error text on hover
await page.hover('.cm-lint-marker-info')
await expect(
page.getByText('Identifiers must be lowerCamelCase').first()
page.getByText('Identifiers should be lowerCamelCase').first()
).toBeVisible()
await page.locator('#code-pane button:first-child').click()
@ -314,7 +314,7 @@ sketch_001 = startSketchOn(XY)
// error text on hover
await page.hover('.cm-lint-marker-info')
await expect(
page.getByText('Identifiers must be lowerCamelCase').first()
page.getByText('Identifiers should be lowerCamelCase').first()
).toBeVisible()
})
@ -511,7 +511,7 @@ sketch_001 = startSketchOn(XY)
// error text on hover
await page.hover('.cm-lint-marker-info')
await expect(
page.getByText('Identifiers must be lowerCamelCase').first()
page.getByText('Identifiers should be lowerCamelCase').first()
).toBeVisible()
// focus the editor
@ -539,7 +539,7 @@ sketch_001 = startSketchOn(XY)
// error text on hover
await page.hover('.cm-lint-marker-info')
await expect(
page.getByText('Identifiers must be lowerCamelCase').first()
page.getByText('Identifiers should be lowerCamelCase').first()
).toBeVisible()
})
@ -681,7 +681,7 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
// error text on hover
await page.hover('.cm-lint-marker-info')
await expect(
page.getByText('Identifiers must be lowerCamelCase').first()
page.getByText('Identifiers should be lowerCamelCase').first()
).toBeVisible()
// select the line that's causing the error and delete it

View File

@ -7,6 +7,7 @@ import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
import { expect, test } from '@e2e/playwright/zoo-test'
import { bracket } from '@e2e/playwright/fixtures/bracket'
import type { CmdBarSerialised } from '@e2e/playwright/fixtures/cmdBarFixture'
// test file is for testing point an click code gen functionality that's not sketch mode related
@ -1141,6 +1142,20 @@ openSketch = startSketchOn(XY)
})
})
const initialCmdBarStateHelix: CmdBarSerialised = {
stage: 'arguments',
currentArgKey: 'mode',
currentArgValue: '',
headerArguments: {
Mode: '',
AngleStart: '',
Revolutions: '',
Radius: '',
},
highlightedHeaderArg: 'mode',
commandName: 'Helix',
}
test('Helix point-and-click on default axis', async ({
context,
page,
@ -1150,30 +1165,14 @@ openSketch = startSketchOn(XY)
toolbar,
cmdBar,
}) => {
// One dumb hardcoded screen pixel value
const testPoint = { x: 620, y: 257 }
const expectedOutput = `helix001 = helix( axis = X, radius = 5, length = 5, revolutions = 1, angleStart = 270, ccw = false,)`
const expectedOutput = `helix001 = helix( axis = X, radius = 5, length = 5, revolutions = 1, angleStart = 270,)`
const expectedLine = `axis=X,`
await homePage.goToModelingScene()
await scene.connectionEstablished()
await test.step(`Go through the command bar flow`, async () => {
await toolbar.helixButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'mode',
currentArgValue: '',
headerArguments: {
Mode: '',
AngleStart: '',
Revolutions: '',
Radius: '',
CounterClockWise: '',
},
highlightedHeaderArg: 'mode',
commandName: 'Helix',
})
await cmdBar.expectState(initialCmdBarStateHelix)
await cmdBar.progressCmdBar()
await expect.poll(() => page.getByText('Axis').count()).toBe(6)
await cmdBar.progressCmdBar()
@ -1190,7 +1189,6 @@ openSketch = startSketchOn(XY)
AngleStart: '',
Length: '',
Radius: '',
CounterClockWise: '',
},
commandName: 'Helix',
})
@ -1207,11 +1205,10 @@ openSketch = startSketchOn(XY)
Revolutions: '1',
Length: '5',
Radius: '5',
CounterClockWise: '',
},
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
await cmdBar.submit()
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
@ -1221,8 +1218,6 @@ openSketch = startSketchOn(XY)
activeLines: [expectedLine],
highlightedCode: '',
})
// Red plane is now gone, white helix is there
await scene.expectPixelColor([250, 250, 250], testPoint, 15)
})
await test.step(`Edit helix through the feature tree`, async () => {
@ -1234,21 +1229,18 @@ openSketch = startSketchOn(XY)
await cmdBar.expectState({
commandName: 'Helix',
stage: 'arguments',
currentArgKey: 'CounterClockWise',
currentArgValue: '',
currentArgKey: 'length',
currentArgValue: '5',
headerArguments: {
Axis: 'X',
AngleStart: '270',
Revolutions: '1',
Radius: '5',
Length: initialInput,
CounterClockWise: '',
},
highlightedHeaderArg: 'CounterClockWise',
highlightedHeaderArg: 'length',
})
await page.keyboard.press('Shift+Backspace')
await expect(cmdBar.currentArgumentInput).toBeVisible()
await cmdBar.currentArgumentInput.locator('.cm-content').fill(newInput)
await page.keyboard.insertText(newInput)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
@ -1258,11 +1250,10 @@ openSketch = startSketchOn(XY)
Revolutions: '1',
Radius: '5',
Length: newInput,
CounterClockWise: '',
},
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
await cmdBar.submit()
await toolbar.closeFeatureTreePane()
await editor.openPane()
await editor.expectEditor.toContain('length = ' + newInput)
@ -1273,28 +1264,81 @@ openSketch = startSketchOn(XY)
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
// Red plane is back
await scene.expectPixelColor([96, 52, 52], testPoint, 15)
await scene.settled(cmdBar)
await editor.expectEditor.not.toContain('helix')
await expect(
await toolbar.getFeatureTreeOperation('Helix', 0)
).not.toBeVisible()
})
})
const helixCases = [
{
selectionType: 'segment',
testPoint: { x: 513, y: 221 },
expectedOutput: `helix001 = helix( axis = seg01, radius = 1, revolutions = 20, angleStart = 0, ccw = false,)`,
expectedEditedOutput: `helix001 = helix( axis = seg01, radius = 5, revolutions = 20, angleStart = 0, ccw = false,)`,
},
{
selectionType: 'sweepEdge',
testPoint: { x: 564, y: 364 },
expectedOutput: `helix001 = helix( axis = getOppositeEdge(seg01), radius = 1, revolutions = 20, angleStart = 0, ccw = false,)`,
expectedEditedOutput: `helix001 = helix( axis = getOppositeEdge(seg01), radius = 5, revolutions = 20, angleStart = 0, ccw = false,)`,
},
]
helixCases.map(
({ selectionType, testPoint, expectedOutput, expectedEditedOutput }) => {
test(`Helix point-and-click around ${selectionType}`, async ({
test(`Helix point-and-click around segment`, async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [0, 0])
|> yLine(length = 100)
|> line(endAbsolute = [100, 0])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.settled(cmdBar)
await test.step(`Go through the command bar flow`, async () => {
await toolbar.closePane('code')
await toolbar.helixButton.click()
await cmdBar.expectState(initialCmdBarStateHelix)
await cmdBar.selectOption({ name: 'Edge' }).click()
await editor.selectText('yLine(length = 100)')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('1')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('2')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('3')
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Mode: 'Edge',
Edge: `1 segment`,
AngleStart: '2',
Revolutions: '1',
Radius: '3',
},
commandName: 'Helix',
})
await cmdBar.submit()
await scene.settled(cmdBar)
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await toolbar.openPane('code')
await editor.expectEditor.toContain(
`
helix001 = helix(
axis = seg01,
radius = 3,
revolutions = 1,
angleStart = 2,
)`,
{ shouldNormalise: true }
)
await toolbar.closePane('code')
})
})
test(`Helix point-and-click around sweepEdge with edit and delete flows`, async ({
context,
page,
homePage,
@ -1303,7 +1347,6 @@ openSketch = startSketchOn(XY)
toolbar,
cmdBar,
}) => {
page.on('console', console.log)
const initialCode = `sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [0, 0])
|> yLine(length = 100)
@ -1312,7 +1355,8 @@ openSketch = startSketchOn(XY)
|> close()
extrude001 = extrude(profile001, length = 100)`
// One dumb hardcoded screen pixel value
// One dumb hardcoded screen pixel value to click on the sweepEdge, can't think of another way?
const testPoint = { x: 564, y: 364 }
const [clickOnEdge] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
await context.addInitScript((initialCode) => {
@ -1325,30 +1369,14 @@ openSketch = startSketchOn(XY)
await test.step(`Go through the command bar flow`, async () => {
await toolbar.closePane('code')
await toolbar.helixButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'mode',
currentArgValue: '',
headerArguments: {
AngleStart: '',
Mode: '',
CounterClockWise: '',
Radius: '',
Revolutions: '',
},
highlightedHeaderArg: 'mode',
commandName: 'Helix',
})
await cmdBar.expectState(initialCmdBarStateHelix)
await cmdBar.selectOption({ name: 'Edge' }).click()
await expect
.poll(() => page.getByText('Please select one').count())
.toBe(1)
await clickOnEdge()
await page.waitForTimeout(1000)
await cmdBar.progressCmdBar()
await page.waitForTimeout(1000)
await cmdBar.argumentInput.focus()
await page.waitForTimeout(1000)
await page.keyboard.insertText('20')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('0')
@ -1360,33 +1388,62 @@ openSketch = startSketchOn(XY)
stage: 'review',
headerArguments: {
Mode: 'Edge',
Edge: `1 ${selectionType}`,
Edge: `1 sweepEdge`,
AngleStart: '0',
Revolutions: '20',
Radius: '1',
CounterClockWise: '',
},
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
await page.waitForTimeout(1000)
await cmdBar.submit()
await scene.settled(cmdBar)
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await toolbar.openPane('code')
await editor.expectEditor.toContain(expectedOutput)
await editor.expectEditor.toContain(
`
helix001 = helix(
axis = getOppositeEdge(seg01),
radius = 1,
revolutions = 20,
angleStart = 0,
)`,
{ shouldNormalise: true }
)
await toolbar.closePane('code')
})
await test.step(`Edit helix through the feature tree`, async () => {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation(
'Helix',
0
)
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
await operationButton.dblclick()
const initialInput = '1'
const newInput = '5'
await cmdBar.expectState({
commandName: 'Helix',
stage: 'arguments',
currentArgKey: 'radius',
currentArgValue: initialInput,
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: initialInput,
},
highlightedHeaderArg: 'radius',
})
await page.keyboard.insertText(newInput)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: newInput,
},
commandName: 'Helix',
})
await cmdBar.clickOptionalArgument('ccw')
await cmdBar.expectState({
commandName: 'Helix',
stage: 'arguments',
@ -1395,19 +1452,12 @@ openSketch = startSketchOn(XY)
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: initialInput,
Radius: newInput,
CounterClockWise: '',
},
highlightedHeaderArg: 'CounterClockWise',
})
await page
.getByRole('button', { name: 'radius', exact: false })
.click()
await expect(cmdBar.currentArgumentInput).toBeVisible()
await cmdBar.currentArgumentInput
.locator('.cm-content')
.fill(newInput)
await cmdBar.progressCmdBar()
await cmdBar.selectOption({ name: 'True' }).click()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
@ -1418,29 +1468,34 @@ openSketch = startSketchOn(XY)
},
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
await cmdBar.submit()
await toolbar.closePane('feature-tree')
await toolbar.openPane('code')
await editor.expectEditor.toContain(expectedEditedOutput)
await editor.expectEditor.toContain(
`
helix001 = helix(
axis = getOppositeEdge(seg01),
radius = 5,
revolutions = 20,
angleStart = 0,
ccw = true,
)`,
{ shouldNormalise: true }
)
await toolbar.closePane('code')
})
await test.step('Delete helix via feature tree selection', async () => {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation(
'Helix',
0
)
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await editor.expectEditor.not.toContain(expectedEditedOutput)
await editor.expectEditor.not.toContain('helix')
await expect(
await toolbar.getFeatureTreeOperation('Helix', 0)
).not.toBeVisible()
})
})
}
)
test('Helix point-and-click on cylinder', async ({
context,
@ -1470,26 +1525,12 @@ extrude001 = extrude(profile001, length = 100)
// One dumb hardcoded screen pixel value
const testPoint = { x: 620, y: 257 }
const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const expectedOutput = `helix001 = helix( cylinder = extrude001, revolutions = 1, angleStart = 360, ccw = false,)`
const expectedLine = `cylinder = extrude001,`
const expectedEditedOutput = `helix001 = helix( cylinder = extrude001, revolutions = 1, angleStart = 360, ccw = true,)`
const expectedOutput = `helix001 = helix(cylinder = extrude001, revolutions = 1, angleStart = 360)`
const expectedEditedOutput = `helix001 = helix(cylinder = extrude001, revolutions = 1, angleStart = 10)`
await test.step(`Go through the command bar flow`, async () => {
await toolbar.helixButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'mode',
currentArgValue: '',
headerArguments: {
Mode: '',
AngleStart: '',
Revolutions: '',
Radius: '',
CounterClockWise: '',
},
highlightedHeaderArg: 'mode',
commandName: 'Helix',
})
await cmdBar.expectState(initialCmdBarStateHelix)
await cmdBar.selectOption({ name: 'Cylinder' }).click()
await cmdBar.expectState({
stage: 'arguments',
@ -1500,7 +1541,6 @@ extrude001 = extrude(profile001, length = 100)
Cylinder: '',
AngleStart: '',
Revolutions: '',
CounterClockWise: '',
},
highlightedHeaderArg: 'cylinder',
commandName: 'Helix',
@ -1516,18 +1556,17 @@ extrude001 = extrude(profile001, length = 100)
Cylinder: '1 face',
AngleStart: '360',
Revolutions: '1',
CounterClockWise: '',
},
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
await cmdBar.submit()
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await editor.expectEditor.toContain(expectedOutput)
await editor.expectState({
diagnostics: [],
activeLines: [expectedLine],
activeLines: [expectedOutput],
highlightedCode: '',
})
})
@ -1539,22 +1578,21 @@ extrude001 = extrude(profile001, length = 100)
await cmdBar.expectState({
commandName: 'Helix',
stage: 'arguments',
currentArgKey: 'CounterClockWise',
currentArgValue: '',
currentArgKey: 'angleStart',
currentArgValue: '360',
headerArguments: {
AngleStart: '360',
Revolutions: '1',
CounterClockWise: '',
},
highlightedHeaderArg: 'CounterClockWise',
highlightedHeaderArg: 'angleStart',
})
await cmdBar.selectOption({ name: 'True' }).click()
await page.keyboard.insertText('10')
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
AngleStart: '360',
AngleStart: '10',
Revolutions: '1',
CounterClockWise: 'true',
},
commandName: 'Helix',
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

@ -74,7 +74,7 @@
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
ELECTRON_OVERRIDE_DIST_PATH =
if pkgs.stdenv.isDarwin
then "${pkgs.electron}/Applications/Electron.app/Contents/MacOS/"
then "${pkgs.electron}/Applications"
else "${pkgs.electron}/bin";
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true;
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH = "${pkgs.playwright-driver.browsers}/chromium-1091/chrome-linux/chrome";

20
rust/Cargo.lock generated
View File

@ -1814,7 +1814,7 @@ dependencies = [
[[package]]
name = "kcl-bumper"
version = "0.1.82"
version = "0.1.83"
dependencies = [
"anyhow",
"clap",
@ -1825,7 +1825,7 @@ dependencies = [
[[package]]
name = "kcl-derive-docs"
version = "0.1.82"
version = "0.1.83"
dependencies = [
"proc-macro2",
"quote",
@ -1834,7 +1834,7 @@ dependencies = [
[[package]]
name = "kcl-directory-test-macro"
version = "0.1.82"
version = "0.1.83"
dependencies = [
"convert_case",
"proc-macro2",
@ -1844,7 +1844,7 @@ dependencies = [
[[package]]
name = "kcl-language-server"
version = "0.2.82"
version = "0.2.83"
dependencies = [
"anyhow",
"clap",
@ -1865,7 +1865,7 @@ dependencies = [
[[package]]
name = "kcl-language-server-release"
version = "0.1.82"
version = "0.1.83"
dependencies = [
"anyhow",
"clap",
@ -1885,7 +1885,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.82"
version = "0.2.83"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1962,7 +1962,7 @@ dependencies = [
[[package]]
name = "kcl-python-bindings"
version = "0.3.82"
version = "0.3.83"
dependencies = [
"anyhow",
"kcl-lib",
@ -1977,7 +1977,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.82"
version = "0.1.83"
dependencies = [
"anyhow",
"hyper 0.14.32",
@ -1990,7 +1990,7 @@ dependencies = [
[[package]]
name = "kcl-to-core"
version = "0.1.82"
version = "0.1.83"
dependencies = [
"anyhow",
"async-trait",
@ -2004,7 +2004,7 @@ dependencies = [
[[package]]
name = "kcl-wasm-lib"
version = "0.1.82"
version = "0.1.83"
dependencies = [
"anyhow",
"bson",

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-bumper"
version = "0.1.82"
version = "0.1.83"
edition = "2021"
repository = "https://github.com/KittyCAD/modeling-api"
rust-version = "1.76"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-derive-docs"
description = "A tool for generating documentation from Rust derive macros"
version = "0.1.82"
version = "0.1.83"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-directory-test-macro"
description = "A tool for generating tests from a directory of kcl files"
version = "0.1.82"
version = "0.1.83"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,6 +1,6 @@
[package]
name = "kcl-language-server-release"
version = "0.1.82"
version = "0.1.83"
edition = "2021"
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
publish = false

View File

@ -2,7 +2,7 @@
name = "kcl-language-server"
description = "A language server for KCL."
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
version = "0.2.82"
version = "0.2.83"
edition = "2021"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language implementation and tools"
version = "0.2.82"
version = "0.2.83"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -17,11 +17,14 @@ use crate::{
},
fmt,
modules::{ModuleId, ModulePath, ModuleRepr},
parsing::ast::types::{
parsing::{
ast::types::{
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
BinaryPart, BodyItem, Expr, IfExpression, ImportPath, ImportSelector, ItemVisibility, LiteralIdentifier,
LiteralValue, MemberExpression, Name, Node, NodeRef, ObjectExpression, PipeExpression, Program, TagDeclarator,
Type, UnaryExpression, UnaryOperator,
LiteralValue, MemberExpression, Name, Node, NodeRef, ObjectExpression, PipeExpression, Program,
TagDeclarator, Type, UnaryExpression, UnaryOperator,
},
token::NumericSuffix,
},
source_range::SourceRange,
std::args::TyF64,
@ -1666,12 +1669,18 @@ impl Property {
LiteralIdentifier::Literal(literal) => {
let value = literal.value.clone();
match value {
LiteralValue::Number { value, .. } => {
n @ LiteralValue::Number { value, suffix } => {
if !matches!(suffix, NumericSuffix::None | NumericSuffix::Count) {
return Err(KclError::new_semantic(KclErrorDetails::new(
format!("{n} is not a valid index, indices must be non-dimensional numbers"),
property_sr,
)));
}
if let Some(x) = crate::try_f64_to_usize(value) {
Ok(Property::UInt(x))
} else {
Err(KclError::new_semantic(KclErrorDetails::new(
format!("{value} is not a valid index, indices must be whole numbers >= 0"),
format!("{n} is not a valid index, indices must be whole numbers >= 0"),
property_sr,
)))
}
@ -1690,10 +1699,13 @@ fn jvalue_to_prop(value: &KclValue, property_sr: Vec<SourceRange>, name: &str) -
let make_err =
|message: String| Err::<Property, _>(KclError::new_semantic(KclErrorDetails::new(message, property_sr)));
match value {
KclValue::Number{value: num, .. } => {
n @ KclValue::Number{value: num, ty, .. } => {
if !matches!(ty, NumericType::Known(crate::exec::UnitType::Count) | NumericType::Default { .. } | NumericType::Any ) {
return make_err(format!("arrays can only be indexed by non-dimensioned numbers, found {}", n.human_friendly_type()));
}
let num = *num;
if num < 0.0 {
return make_err(format!("'{num}' is negative, so you can't index an array with it"))
return make_err(format!("'{num}' is negative, so you can't index an array with it"));
}
let nearest_int = crate::try_f64_to_usize(num);
if let Some(nearest_int) = nearest_int {
@ -2141,4 +2153,23 @@ c = ((PI * 2) / 3): number(deg)
let result = parse_execute(ast).await.unwrap();
assert_eq!(result.exec_state.errors().len(), 2);
}
#[tokio::test(flavor = "multi_thread")]
async fn non_count_indexing() {
let ast = r#"x = [0, 0]
y = x[1mm]
"#;
parse_execute(ast).await.unwrap_err();
let ast = r#"x = [0, 0]
y = 1deg
z = x[y]
"#;
parse_execute(ast).await.unwrap_err();
let ast = r#"x = [0, 0]
y = x[0mm + 1]
"#;
parse_execute(ast).await.unwrap_err();
}
}

View File

@ -11,10 +11,10 @@ use crate::{
def_finding!(
Z0001,
"Identifiers must be lowerCamelCase",
"Identifiers should be lowerCamelCase",
"\
By convention, variable names are lowerCamelCase, not snake_case, kebab-case,
nor CammelCase. 🐪
nor upper CamelCase (aka PascalCase). 🐪
For instance, a good identifier for the variable representing 'box height'
would be 'boxHeight', not 'BOX_HEIGHT', 'box_height' nor 'BoxHeight'. For

View File

@ -2359,7 +2359,7 @@ async fn test_kcl_lsp_diagnostic_has_lints() {
assert_eq!(diagnostics.full_document_diagnostic_report.items.len(), 1);
assert_eq!(
diagnostics.full_document_diagnostic_report.items[0].message,
"Identifiers must be lowerCamelCase"
"Identifiers should be lowerCamelCase"
);
} else {
panic!("Expected full diagnostics");

View File

@ -1,6 +1,6 @@
[package]
name = "kcl-python-bindings"
version = "0.3.82"
version = "0.3.83"
edition = "2021"
repository = "https://github.com/kittycad/modeling-app"
exclude = ["tests/*", "files/*", "venv/*"]

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-test-server"
description = "A test server for KCL"
version = "0.1.82"
version = "0.1.83"
edition = "2021"
license = "MIT"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-to-core"
description = "Utility methods to convert kcl to engine core executable tests"
version = "0.1.82"
version = "0.1.83"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,6 +1,6 @@
[package]
name = "kcl-wasm-lib"
version = "0.1.82"
version = "0.1.83"
edition = "2021"
repository = "https://github.com/KittyCAD/modeling-app"
rust-version = "1.83"

View File

@ -590,7 +590,7 @@ export function addHelix({
angleStart: Expr
radius?: Expr
length?: Expr
ccw: boolean
ccw?: boolean
insertIndex?: number
variableName?: string
}): { modifiedAst: Node<Program>; pathToNode: PathToNode } {
@ -610,6 +610,9 @@ export function addHelix({
)
}
// Extra labeled args expressions
const ccwExpr = ccw ? [createLabeledArg('ccw', createLiteral(ccw))] : []
const variable = createVariableDeclaration(
name,
createCallExpressionStdLibKw(
@ -619,7 +622,7 @@ export function addHelix({
...modeArgs,
createLabeledArg('revolutions', revolutions),
createLabeledArg('angleStart', angleStart),
createLabeledArg('ccw', createLiteral(ccw)),
...ccwExpr,
]
)
)

View File

@ -23,13 +23,13 @@ import type {
VariableDeclaration,
} from '@src/lang/wasm'
import { EXECUTION_TYPE_REAL } from '@src/lib/constants'
import type { Selection, Selections } from '@src/lib/selections'
import type { Selections } from '@src/lib/selections'
import { err } from '@src/lib/trap'
import { isArray } from '@src/lib/utils'
export async function applySubtractFromTargetOperatorSelections(
target: Selection,
tool: Selection,
solids: Selections,
tools: Selections,
dependencies: {
kclManager: KclManager
engineCommandManager: EngineCommandManager
@ -38,28 +38,28 @@ export async function applySubtractFromTargetOperatorSelections(
}
): Promise<Error | void> {
const ast = dependencies.kclManager.ast
if (!target.artifact || !tool.artifact) {
return new Error('No artifact found')
}
const orderedChildrenTarget = findAllChildrenAndOrderByPlaceInCode(
target.artifact,
const lastSolidsVars = getLastVariableDeclarationsFromSelections(
solids,
ast,
dependencies.kclManager.artifactGraph
)
const orderedChildrenTool = findAllChildrenAndOrderByPlaceInCode(
tool.artifact,
if (err(lastSolidsVars) || lastSolidsVars.length < 1) {
return new Error('Not enough or invalid solids variables found')
}
const lastToolsVars = getLastVariableDeclarationsFromSelections(
tools,
ast,
dependencies.kclManager.artifactGraph
)
const lastVarTarget = getLastVariable(orderedChildrenTarget, ast)
const lastVarTool = getLastVariable(orderedChildrenTool, ast)
if (!lastVarTarget || !lastVarTool) {
return new Error('No variable found')
if (err(lastToolsVars) || lastToolsVars.length < 1) {
return new Error('Not enough or invalid tools variables found')
}
const modifiedAst = booleanSubtractAstMod({
ast,
targets: [lastVarTarget?.variableDeclaration?.node],
tools: [lastVarTool?.variableDeclaration.node],
solids: lastSolidsVars,
tools: lastToolsVars,
})
await updateModelingState(modifiedAst, EXECUTION_TYPE_REAL, dependencies)
@ -75,34 +75,13 @@ export async function applyUnionFromTargetOperatorSelections(
}
): Promise<Error | void> {
const ast = dependencies.kclManager.ast
const artifacts: Artifact[] = []
for (const selection of solids.graphSelections) {
if (selection.artifact) {
artifacts.push(selection.artifact)
}
}
if (artifacts.length < 2) {
return new Error('Not enough artifacts selected')
}
const orderedChildrenEach = artifacts.map((artifact) =>
findAllChildrenAndOrderByPlaceInCode(
artifact,
const lastVars = getLastVariableDeclarationsFromSelections(
solids,
ast,
dependencies.kclManager.artifactGraph
)
)
const lastVars: VariableDeclaration[] = []
for (const orderedArtifactLeaves of orderedChildrenEach) {
const lastVar = getLastVariable(orderedArtifactLeaves, ast)
if (!lastVar) continue
lastVars.push(lastVar.variableDeclaration.node)
}
if (lastVars.length < 2) {
return new Error('Not enough variables found')
if (err(lastVars) || lastVars.length < 2) {
return new Error('Not enough or invalid solids variables found')
}
const modifiedAst = booleanUnionAstMod({
@ -122,23 +101,36 @@ export async function applyIntersectFromTargetOperatorSelections(
}
): Promise<Error | void> {
const ast = dependencies.kclManager.ast
const lastVars = getLastVariableDeclarationsFromSelections(
solids,
ast,
dependencies.kclManager.artifactGraph
)
if (err(lastVars) || lastVars.length < 2) {
return new Error('Not enough or invalid solids variables found')
}
const modifiedAst = booleanIntersectAstMod({
ast,
solids: lastVars,
})
await updateModelingState(modifiedAst, EXECUTION_TYPE_REAL, dependencies)
}
function getLastVariableDeclarationsFromSelections(
selections: Selections,
ast: Node<Program>,
artifactGraph: ArtifactGraph
): Error | VariableDeclaration[] {
const artifacts: Artifact[] = []
for (const selection of solids.graphSelections) {
for (const selection of selections.graphSelections) {
if (selection.artifact) {
artifacts.push(selection.artifact)
}
}
if (artifacts.length < 2) {
return new Error('Not enough artifacts selected')
}
const orderedChildrenEach = artifacts.map((artifact) =>
findAllChildrenAndOrderByPlaceInCode(
artifact,
dependencies.kclManager.artifactGraph
)
findAllChildrenAndOrderByPlaceInCode(artifact, artifactGraph)
)
const lastVars: VariableDeclaration[] = []
@ -148,15 +140,7 @@ export async function applyIntersectFromTargetOperatorSelections(
lastVars.push(lastVar.variableDeclaration.node)
}
if (lastVars.length < 2) {
return new Error('Not enough variables found')
}
const modifiedAst = booleanIntersectAstMod({
ast,
solids: lastVars,
})
await updateModelingState(modifiedAst, EXECUTION_TYPE_REAL, dependencies)
return lastVars
}
/** returns all children of a given artifact, and sorts them DESC by start sourceRange
@ -271,25 +255,27 @@ export function getLastVariable(
export function booleanSubtractAstMod({
ast,
targets,
solids,
tools,
}: {
ast: Node<Program>
targets: VariableDeclaration[]
solids: VariableDeclaration[]
tools: VariableDeclaration[]
}): Node<Program> {
const newAst = structuredClone(ast)
const newVarName = findUniqueName(newAst, 'solid')
const createArrExpr = (varDecs: VariableDeclaration[]) =>
createArrayExpression(
varDecs.map((varDec) => createLocalName(varDec.declaration.id.name))
const createArrExpr = (varDecs: VariableDeclaration[]) => {
const names = varDecs.map((varDec) =>
createLocalName(varDec.declaration.id.name)
)
const targetsArrayExpression = createArrExpr(targets)
return names.length === 1 ? names[0] : createArrayExpression(names)
}
const solidsArrayExpression = createArrExpr(solids)
const toolsArrayExpression = createArrExpr(tools)
const newVarDec = createVariableDeclaration(
newVarName,
createCallExpressionStdLibKw('subtract', targetsArrayExpression, [
createCallExpressionStdLibKw('subtract', solidsArrayExpression, [
createLabeledArg('tools', toolsArrayExpression),
])
)

View File

@ -204,8 +204,8 @@ export type ModelingCommandSchema = {
variableName: string
}
'Boolean Subtract': {
target: Selections
tool: Selections
solids: Selections
tools: Selections
}
'Boolean Union': {
solids: Selections
@ -595,23 +595,21 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
icon: 'booleanSubtract',
needsReview: true,
args: {
target: {
solids: {
inputType: 'selection',
selectionTypes: ['path'],
selectionFilter: ['object'],
multiple: false,
multiple: true,
required: true,
skip: true,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
},
tool: {
tools: {
clearSelectionFirst: true,
inputType: 'selection',
selectionTypes: ['path'],
selectionFilter: ['object'],
multiple: false,
multiple: true,
required: true,
skip: false,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
},
},
@ -690,32 +688,35 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
},
axis: {
inputType: 'options',
required: (commandContext) =>
['Axis'].includes(commandContext.argumentsToSubmit.mode as string),
options: [
{ name: 'X Axis', value: 'X' },
{ name: 'Y Axis', value: 'Y' },
{ name: 'Z Axis', value: 'Z' },
],
hidden: false, // for consistency here, we can actually edit here since it's not a selection
required: (context) =>
['Axis'].includes(context.argumentsToSubmit.mode as string),
hidden: (context) =>
!['Axis'].includes(context.argumentsToSubmit.mode as string),
},
edge: {
required: (commandContext) =>
['Edge'].includes(commandContext.argumentsToSubmit.mode as string),
inputType: 'selection',
selectionTypes: ['segment', 'sweepEdge'],
multiple: false,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
required: (context) =>
['Edge'].includes(context.argumentsToSubmit.mode as string),
hidden: (context) =>
Boolean(context.argumentsToSubmit.nodeToEdit) ||
!['Edge'].includes(context.argumentsToSubmit.mode as string),
},
cylinder: {
required: (commandContext) =>
['Cylinder'].includes(
commandContext.argumentsToSubmit.mode as string
),
inputType: 'selection',
selectionTypes: ['wall'],
multiple: false,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
required: (context) =>
['Cylinder'].includes(context.argumentsToSubmit.mode as string),
hidden: (context) =>
Boolean(context.argumentsToSubmit.nodeToEdit) ||
!['Cylinder'].includes(context.argumentsToSubmit.mode as string),
},
revolutions: {
inputType: 'kcl',
@ -730,34 +731,30 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
radius: {
inputType: 'kcl',
defaultValue: KCL_DEFAULT_LENGTH,
required: (commandContext) =>
!['Cylinder'].includes(
commandContext.argumentsToSubmit.mode as string
),
required: (context) =>
!['Cylinder'].includes(context.argumentsToSubmit.mode as string),
hidden: (context) =>
['Cylinder'].includes(context.argumentsToSubmit.mode as string),
},
length: {
inputType: 'kcl',
defaultValue: KCL_DEFAULT_LENGTH,
required: (commandContext) =>
['Axis'].includes(commandContext.argumentsToSubmit.mode as string),
// No need for hidden here, as it works with all modes
},
ccw: {
inputType: 'options',
skip: true,
required: true,
defaultValue: false,
valueSummary: (value) => String(value),
required: false,
displayName: 'CounterClockWise',
options: (commandContext) => [
options: [
{
name: 'False',
value: false,
isCurrent: !Boolean(commandContext.argumentsToSubmit.ccw),
},
{
name: 'True',
value: true,
isCurrent: Boolean(commandContext.argumentsToSubmit.ccw),
},
],
},

View File

@ -222,3 +222,6 @@ export const CODE_QUERY_PARAM = 'code'
/** A query parameter to skip the sign-on view if unnecessary. */
export const IMMEDIATE_SIGN_IN_IF_NECESSARY_QUERY_PARAM =
'immediate-sign-in-if-necessary'
// Only used by the desktop app
export const OAUTH2_DEVICE_CLIENT_ID = '2af127fb-e14e-400a-9c57-a9ed08d1a5b7'

View File

@ -7,7 +7,7 @@ import {
} from '@src/env'
import { assign, fromPromise, setup } from 'xstate'
import { COOKIE_NAME } from '@src/lib/constants'
import { COOKIE_NAME, OAUTH2_DEVICE_CLIENT_ID } from '@src/lib/constants'
import {
getUser as getUserDesktop,
readTokenFile,
@ -254,9 +254,33 @@ async function getAndSyncStoredToken(input: {
async function logout() {
localStorage.removeItem(TOKEN_PERSIST_KEY)
if (isDesktop()) {
try {
let token = await readTokenFile()
if (token) {
try {
await fetch(withBaseUrl('/oauth2/token/revoke'), {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
token: token,
client_id: OAUTH2_DEVICE_CLIENT_ID,
}).toString(),
})
} catch (e) {
console.error('Error revoking token:', e)
}
await writeTokenFile('')
return Promise.resolve(null)
}
} catch (e) {
console.error('Error reading token during logout (ignoring):', e)
}
}
return fetch(withBaseUrl('/logout'), {
method: 'POST',

View File

@ -3570,17 +3570,17 @@ export const modelingMachine = setup({
return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
}
const { target, tool } = input
const { solids, tools } = input
if (
!target.graphSelections[0].artifact ||
!tool.graphSelections[0].artifact
!solids.graphSelections.some((selection) => selection.artifact) ||
!tools.graphSelections.some((selection) => selection.artifact)
) {
return Promise.reject(new Error('No artifact in selections found'))
}
await applySubtractFromTargetOperatorSelections(
target.graphSelections[0],
tool.graphSelections[0],
const result = await applySubtractFromTargetOperatorSelections(
solids,
tools,
{
kclManager,
codeManager,
@ -3588,6 +3588,9 @@ export const modelingMachine = setup({
editorManager,
}
)
if (err(result)) {
return Promise.reject(result)
}
}
),
boolUnionAstMod: fromPromise(
@ -3605,12 +3608,15 @@ export const modelingMachine = setup({
return Promise.reject(new Error('No artifact in selections found'))
}
await applyUnionFromTargetOperatorSelections(solids, {
const result = await applyUnionFromTargetOperatorSelections(solids, {
kclManager,
codeManager,
engineCommandManager,
editorManager,
})
if (err(result)) {
return Promise.reject(result)
}
}
),
boolIntersectAstMod: fromPromise(
@ -3628,12 +3634,18 @@ export const modelingMachine = setup({
return Promise.reject(new Error('No artifact in selections found'))
}
await applyIntersectFromTargetOperatorSelections(solids, {
const result = await applyIntersectFromTargetOperatorSelections(
solids,
{
kclManager,
codeManager,
engineCommandManager,
editorManager,
})
}
)
if (err(result)) {
return Promise.reject(result)
}
}
),

View File

@ -15,6 +15,7 @@ import {
dialog,
ipcMain,
nativeTheme,
session,
screen,
shell,
systemPreferences,
@ -28,7 +29,10 @@ import {
parseCLIArgs,
} from '@src/commandLineArgs'
import { initPromiseNode } from '@src/lang/wasmUtilsNode'
import { ZOO_STUDIO_PROTOCOL } from '@src/lib/constants'
import {
ZOO_STUDIO_PROTOCOL,
OAUTH2_DEVICE_CLIENT_ID,
} from '@src/lib/constants'
import getCurrentProjectFile from '@src/lib/getCurrentProjectFile'
import { reportRejection } from '@src/lib/trap'
import {
@ -293,7 +297,21 @@ app.on('window-all-closed', () => {
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', (event, data) => {
app.on('ready', async (event, data) => {
try {
await session.defaultSession.cookies.set({
url: 'https://api.dev.zoo.dev',
name: 'preview-pr-2718',
value: 'always',
domain: '.dev.zoo.dev',
path: '/',
secure: true,
sameSite: 'no_restriction',
})
console.log('[preview] cookie seeded')
} catch (err) {
console.error('[preview] failed to set cookie', err)
}
// Avoid potentially 2 ready fires
if (mainWindow) return
// Create the mainWindow
@ -402,7 +420,7 @@ ipcMain.handle('startDeviceFlow', async (_, host: string) => {
// We can hardcode the client ID.
// This value is safe to be embedded in version control.
// This is the client ID of the KittyCAD app.
client_id: '2af127fb-e14e-400a-9c57-a9ed08d1a5b7',
client_id: OAUTH2_DEVICE_CLIENT_ID,
token_endpoint_auth_method: 'none',
})