Compare commits

...

8 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
30 changed files with 465 additions and 379 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,174 +1264,238 @@ 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 ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
page.on('console', console.log)
const initialCode = `sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [0, 0])
|> yLine(length = 100)
|> line(endAbsolute = [100, 0])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(profile001, length = 100)`
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)
// One dumb hardcoded screen pixel value
const [clickOnEdge] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
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({
stage: 'arguments',
currentArgKey: 'mode',
currentArgValue: '',
headerArguments: {
AngleStart: '',
Mode: '',
CounterClockWise: '',
Radius: '',
Revolutions: '',
},
highlightedHeaderArg: 'mode',
commandName: 'Helix',
})
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')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('1')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('100')
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Mode: 'Edge',
Edge: `1 ${selectionType}`,
AngleStart: '0',
Revolutions: '20',
Radius: '1',
CounterClockWise: '',
},
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
await page.waitForTimeout(1000)
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await toolbar.openPane('code')
await editor.expectEditor.toContain(expectedOutput)
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
)
await operationButton.dblclick()
const initialInput = '1'
const newInput = '5'
await cmdBar.expectState({
commandName: 'Helix',
stage: 'arguments',
currentArgKey: 'CounterClockWise',
currentArgValue: '',
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: initialInput,
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.expectState({
stage: 'review',
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: newInput,
CounterClockWise: '',
},
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
await toolbar.closePane('feature-tree')
await toolbar.openPane('code')
await editor.expectEditor.toContain(expectedEditedOutput)
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
)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await editor.expectEditor.not.toContain(expectedEditedOutput)
await expect(
await toolbar.getFeatureTreeOperation('Helix', 0)
).not.toBeVisible()
})
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,
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()
extrude001 = extrude(profile001, length = 100)`
// 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) => {
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 expect
.poll(() => page.getByText('Please select one').count())
.toBe(1)
await clickOnEdge()
await cmdBar.progressCmdBar()
await cmdBar.argumentInput.focus()
await page.keyboard.insertText('20')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('0')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('1')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('100')
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Mode: 'Edge',
Edge: `1 sweepEdge`,
AngleStart: '0',
Revolutions: '20',
Radius: '1',
},
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 = 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)
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',
currentArgKey: 'CounterClockWise',
currentArgValue: '',
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: newInput,
CounterClockWise: '',
},
highlightedHeaderArg: 'CounterClockWise',
})
await cmdBar.selectOption({ name: 'True' }).click()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: newInput,
CounterClockWise: '',
},
commandName: 'Helix',
})
await cmdBar.submit()
await toolbar.closePane('feature-tree')
await toolbar.openPane('code')
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)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
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

@ -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,
dependencies.kclManager.artifactGraph
)
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,8 +254,32 @@ async function getAndSyncStoredToken(input: {
async function logout() {
localStorage.removeItem(TOKEN_PERSIST_KEY)
if (isDesktop()) {
await writeTokenFile('')
return Promise.resolve(null)
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'), {

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, {
kclManager,
codeManager,
engineCommandManager,
editorManager,
})
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',
})