Compare commits

...

11 Commits

Author SHA1 Message Date
5f7a75a327 #7408 Can not pick sketch plane using the feature tree and related improvements (#7609)
* Add ability to pick default plane in feature tree in 'Sketch no face' mode

* add ability to select deoffset plane where starting a new sketch

* use selectDefaultSketchPlane

* refactor: remove some duplication

* warning cleanups

* feature tree items selectable depedngin on no face sketch mode

* lint

* fix small jump because of border:none when going into and back from 'No face sketch' mode

* grey out items other than offset planes in 'No face sketch' mode

* start sketching on plane in context menu

* sketch on offset plane with context menu

* add ability to right click on default plane and start sketch on it

* default planes in feature tree should be selectable because of right click context menu

* add right click Start sketch option for selected plane on the canvas

* selectDefaultSketchPlane returns error now

* circular deps

* move select functions to lib/selections.ts to avoid circular deps

* add test for clicking on feature tree after starting a new sketch

* graphite suggestion

* fix bug of not being able to create offset plane using another offset plane with command bar

* add ability to select default plane on feature when going through the Offset plane command bar flow
2025-07-04 13:14:12 -04:00
01230b0aa1 [Bug fix]: Do not store token in window! (#7671)
* fix: we really cannot do this

* fix: limiting footprint
2025-07-04 11:57:29 -04:00
597f4a1d54 Bump vite from 5.4.18 to 5.4.19 in the security-major group (#7685)
* Bump vite from 5.4.18 to 5.4.19 in the security-major group

Bumps the security-major group with 1 update: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 5.4.18 to 5.4.19
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.19/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.19/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 5.4.19
  dependency-type: direct:development
  dependency-group: security-major
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-07-03 19:36:15 -04:00
c814c90a9e Rebuild when the environment changes (#7688) 2025-07-03 17:05:59 -04:00
64832f9046 Bump esbuild from 0.25.3 to 0.25.4 in the security group (#7684)
* Bump esbuild from 0.25.3 to 0.25.4 in the security group

Bumps the security group with 1 update: [esbuild](https://github.com/evanw/esbuild).


Updates `esbuild` from 0.25.3 to 0.25.4
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.25.3...v0.25.4)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.25.4
  dependency-type: direct:development
  dependency-group: security
...

Signed-off-by: dependabot[bot] <support@github.com>

* Update snapshots

* Update snapshots

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-07-03 20:16:46 +00:00
34494f3bba Consolidate KittyCAD API token environment variables (#7665)
* Consolidate KittyCAD API token environment variables

* Remove duplicate variable in type definition

* Remove unnecessary intermediate steps

* Keep base label for concatenation functions
2025-07-03 13:15:21 -04:00
df6256266c [Chore] All api urls are now using the helper function (#7672)
* fix: logging information about the login

* chore: improving the withBaseURL workflow

* chore: moving VITE_KC_API_BASE_URL to the helper function

* fix: env to helper function api base url

* chore: fixing another api base url

* chore: shortlinks with base api helper function

* chore: prompt edit with base helper function

* fix: auto fmt

* fix: withAPIBaseURL for all urls

* fix: AI caught my typo, RIP

* fix: expected

* fix: renaming this so it is less specific to environment

---------

Co-authored-by: Jace Browning <jacebrowning@gmail.com>
2025-07-03 13:54:03 +00:00
e5d082f441 [Chore] Removing old confusing BASE_URL environment variable. (#7678)
chore: this is deprecated, we should be using the VITE_KC_*_BASE_URL workflows
2025-07-02 22:51:01 +00:00
d4d3e179b1 Update test data (#7674) 2025-07-02 15:04:03 -07:00
4f4c44e7c7 KCL: Getter for axes of planes (#7662)
## Goal

Currently, there's no way in KCL to get fields of a plane, e.g. the underlying X axis, Y axis or origin.

This would be useful for geometry calculations in KCL. It would help KCL users write transformations between planes for rotating geometry.

For example, this enables

```kcl
export fn crossProduct(@vectors) {
  a = vectors[0]
  b = vectors[1]
  x = a[1] * b[2] - (a[2] * b[1])
  y = a[2] * b[0] - (a[0] * b[2])
  z = a[0] * b[1] - (a[1] * b[0])
  return [x, y, z]
}

export fn normalOf(@plane) {
  return crossProduct([plane.xAxis, plane.yAxis])
}
```

## Implementation

My goal was just to enable a simple getter for planes, like `myPlane.xAxis` and yAxis and origins. That's nearly what happened, except I discovered that there's two ways to represent a plane: either `KclValue::Plane` or `KclValue::Object` with the right fields.

No matter which format your plane is represented as, it should behave consistently when you get its properties. Those properties should be returned as `[number; 3]` because that is how KCL represents points.

Unfortunately we actually require planes-as-objects to be defined with axes like `myPlane = { xAxis = { x = 1, y = 0, z = 0 }, ...}`, but that's a mistake in my opinion. So if you do use that representation of a plane, it should still return a [number; 3]. This required some futzing around so that we let you access object fields .x and .y as [0] and [1], which is weird, but whatever, I think it's good.

This PR is tested via planestuff.kcl which has a Rust unit test.

Part of the hole efforts, see https://github.com/KittyCAD/modeling-app/discussions/7543
2025-07-02 16:24:26 +00:00
1b75020686 Remove unused code to skip auth (#7280) 2025-07-02 14:49:34 +00:00
47 changed files with 930 additions and 480 deletions

View File

@ -3,18 +3,17 @@
NODE_ENV=development NODE_ENV=development
DEV=true DEV=true
# App
VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev VITE_KITTYCAD_API_BASE_URL=https://api.dev.zoo.dev
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
VITE_KC_SITE_APP_URL=https://app.dev.zoo.dev VITE_KC_SITE_APP_URL=https://app.dev.zoo.dev
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=5000 VITE_KC_CONNECTION_TIMEOUT_MS=5000
#VITE_WASM_URL="optional way of overriding the wasm url, particular for unit tests which need this if you running not on the default 3000 port" #VITE_WASM_URL="optional override of Wasm URL if not on default port 3000"
#VITE_KC_DEV_TOKEN="optional token to skip auth in the app" #VITE_KITTYCAD_API_TOKEN="required for testing, optional to skip auth in the app"
#token="required token for playwright. TODO: clean up env vars in #3973" FAIL_ON_CONSOLE_ERRORS=true
# KCL
RUST_BACKTRACE=1 RUST_BACKTRACE=1
PYO3_PYTHON=/usr/local/bin/python3 PYO3_PYTHON=/usr/local/bin/python3
#KITTYCAD_API_TOKEN="required token for engine testing" #KITTYCAD_API_TOKEN=$VITE_KITTYCAD_API_TOKEN
FAIL_ON_CONSOLE_ERRORS=true

View File

@ -1,7 +1,8 @@
NODE_ENV=production NODE_ENV=production
# App
VITE_KC_API_WS_MODELING_URL=wss://api.zoo.dev/ws/modeling/commands VITE_KC_API_WS_MODELING_URL=wss://api.zoo.dev/ws/modeling/commands
VITE_KC_API_BASE_URL=https://api.zoo.dev VITE_KITTYCAD_API_BASE_URL=https://api.zoo.dev
VITE_KC_SITE_BASE_URL=https://zoo.dev VITE_KC_SITE_BASE_URL=https://zoo.dev
VITE_KC_SITE_APP_URL=https://app.zoo.dev VITE_KC_SITE_APP_URL=https://app.zoo.dev
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=15000 VITE_KC_CONNECTION_TIMEOUT_MS=15000

View File

@ -157,7 +157,7 @@ jobs:
timeout_minutes: 5 timeout_minutes: 5
max_attempts: 5 max_attempts: 5
env: env:
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} VITE_KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
TAB_API_URL: ${{ secrets.TAB_API_URL }} TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }} TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }} CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
@ -169,7 +169,7 @@ jobs:
if: always() if: always()
run: npm run test:snapshots -- --last-failed --update-snapshots run: npm run test:snapshots -- --last-failed --update-snapshots
env: env:
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} VITE_KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
TAB_API_URL: ${{ secrets.TAB_API_URL }} TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }} TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }} CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
@ -284,7 +284,7 @@ jobs:
timeout_minutes: 5 timeout_minutes: 5
max_attempts: 5 max_attempts: 5
env: env:
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} VITE_KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
TAB_API_URL: ${{ secrets.TAB_API_URL }} TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }} TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }} CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
@ -410,7 +410,7 @@ jobs:
max_attempts: 9 max_attempts: 9
env: env:
FAIL_ON_CONSOLE_ERRORS: true FAIL_ON_CONSOLE_ERRORS: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} VITE_KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
TAB_API_URL: ${{ secrets.TAB_API_URL }} TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }} TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }} CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}

View File

@ -62,7 +62,7 @@ jobs:
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: xvfb-run -a npm run test:unit run: xvfb-run -a npm run test:unit
env: env:
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} VITE_KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- name: Check for changes - name: Check for changes
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}

View File

@ -65,7 +65,7 @@ If you're not a Zoo employee you won't be able to access the dev environment, yo
### Development environment variables ### Development environment variables
The Copilot LSP plugin in the editor requires a Zoo API token to run. In production, we authenticate this with a token via cookie in the browser and device auth token in the desktop environment, but this token is inaccessible in the dev browser version because the cookie is considered "cross-site" (from `localhost` to `zoo.dev`). There is an optional environment variable called `VITE_KC_DEV_TOKEN` that you can populate with a dev token in a `.env.development.local` file to not check it into Git, which will use that token instead of other methods for the LSP service. The Copilot LSP plugin in the editor requires a Zoo API token to run. In production, we authenticate this with a token via cookie in the browser and device auth token in the desktop environment, but this token is inaccessible in the dev browser version because the cookie is considered "cross-site" (from `localhost` to `zoo.dev`). There is an optional environment variable called `VITE_KITTYCAD_API_TOKEN` that you can populate with a dev token in a `.env.development.local` file to not check it into Git, which will use that token instead of other methods for the LSP service.
### Developing in Chrome ### Developing in Chrome
@ -96,7 +96,7 @@ To package the app for your platform with electron-builder, run `npm run tronb:p
Prepare these system dependencies: Prepare these system dependencies:
- Set $token from https://zoo.dev/account/api-tokens - Set `$VITE_KITTYCAD_API_TOKEN` from https://zoo.dev/account/api-tokens
#### Snapshot tests (Google Chrome on Ubuntu only) #### Snapshot tests (Google Chrome on Ubuntu only)
@ -259,7 +259,7 @@ If the application needs to overwrite the known file on disk use this pattern. T
- `npm run circular-deps:overwrite` - `npm run circular-deps:overwrite`
- `npm run url-checker:overwrite` - `npm run url-checker:overwrite`
#### Diff baseline and current #### Diff baseline and current
These commands will write a /tmp/ file on disk and compare it to the known file in the repository. This command will also be used in the CI CD pipeline for automated checks These commands will write a /tmp/ file on disk and compare it to the known file in the repository. This command will also be used in the CI CD pipeline for automated checks

View File

@ -49,7 +49,7 @@ RUST_SOURCES := $(wildcard rust/**/*.rs)
REACT_SOURCES := $(wildcard src/*.tsx) $(wildcard src/**/*.tsx) REACT_SOURCES := $(wildcard src/*.tsx) $(wildcard src/**/*.tsx)
TYPESCRIPT_SOURCES := tsconfig.* $(wildcard src/*.ts) $(wildcard src/**/*.ts) TYPESCRIPT_SOURCES := tsconfig.* $(wildcard src/*.ts) $(wildcard src/**/*.ts)
VITE_SOURCES := $(wildcard vite.*) $(wildcard vite/**/*.tsx) VITE_SOURCES := $(wildcard vite.*) $(wildcard vite/**/*.tsx) .env*
.PHONY: build .PHONY: build
build: install public/kcl_wasm_lib_bg.wasm public/kcl-samples/manifest.json .vite/build/main.js build: install public/kcl_wasm_lib_bg.wasm public/kcl-samples/manifest.json .vite/build/main.js

View File

@ -1,6 +1,5 @@
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
// test file is for testing auth functionality
test.describe('Authentication tests', () => { test.describe('Authentication tests', () => {
test( test(
`The user can sign out and back in`, `The user can sign out and back in`,
@ -13,22 +12,12 @@ test.describe('Authentication tests', () => {
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.projectSection.waitFor() await homePage.projectSection.waitFor()
// This is only needed as an override to test-utils' setup() for this test
await page.addInitScript(() => {
localStorage.setItem('TOKEN_PERSIST_KEY', '')
})
await test.step('Click on sign out and expect sign in page', async () => { await test.step('Click on sign out and expect sign in page', async () => {
await toolbar.userSidebarButton.click() await toolbar.userSidebarButton.click()
await toolbar.signOutButton.click() await toolbar.signOutButton.click()
await expect(signInPage.signInButton).toBeVisible() await expect(signInPage.signInButton).toBeVisible()
}) })
await test.step("Refresh doesn't log the user back in", async () => {
await page.reload()
await expect(signInPage.signInButton).toBeVisible()
})
await test.step('Click on sign in and cancel, click again and expect different code', async () => { await test.step('Click on sign in and cancel, click again and expect different code', async () => {
await signInPage.signInButton.click() await signInPage.signInButton.click()
await expect(signInPage.userCode).toBeVisible() await expect(signInPage.userCode).toBeVisible()

View File

@ -187,6 +187,68 @@ sketch001 = startProfile(sketch002, at = [12.34, -12.34])
page.getByRole('button', { name: 'Start Sketch' }) page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible() ).toBeVisible()
}) })
test('Can select planes in Feature Tree after Start Sketch', async ({
page,
homePage,
toolbar,
editor,
}) => {
// Load the app with empty code
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`plane001 = offsetPlane(XZ, offset = 5)`
)
})
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
await test.step('Click Start Sketch button', async () => {
await page.getByRole('button', { name: 'Start Sketch' }).click()
await expect(
page.getByRole('button', { name: 'Exit Sketch' })
).toBeVisible()
await expect(page.getByText('select a plane or face')).toBeVisible()
})
await test.step('Open feature tree and select Front plane (XZ)', async () => {
await toolbar.openFeatureTreePane()
await page.getByRole('button', { name: 'Front plane' }).click()
await page.waitForTimeout(600)
await expect(toolbar.lineBtn).toBeEnabled()
await editor.expectEditor.toContain('startSketchOn(XZ)')
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).toBeVisible()
})
await test.step('Click Start Sketch button again', async () => {
await page.getByRole('button', { name: 'Start Sketch' }).click()
await expect(
page.getByRole('button', { name: 'Exit Sketch' })
).toBeVisible()
})
await test.step('Select the offset plane', async () => {
await toolbar.openFeatureTreePane()
await page.getByRole('button', { name: 'Offset plane' }).click()
await page.waitForTimeout(600)
await expect(toolbar.lineBtn).toBeEnabled()
await editor.expectEditor.toContain('startSketchOn(plane001)')
})
})
test('Can edit segments by dragging their handles', () => { test('Can edit segments by dragging their handles', () => {
const doEditSegmentsByDraggingHandle = async ( const doEditSegmentsByDraggingHandle = async (
page: Page, page: Page,

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

@ -17,7 +17,7 @@ import dotenv from 'dotenv'
const NODE_ENV = process.env.NODE_ENV || 'development' const NODE_ENV = process.env.NODE_ENV || 'development'
dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] }) dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] })
export const token = process.env.token || '' export const token = process.env.VITE_KITTYCAD_API_TOKEN || ''
import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration' import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'

7
interface.d.ts vendored
View File

@ -72,16 +72,13 @@ export interface IElectronAPI {
} }
process: { process: {
env: { env: {
BASE_URL: string
IS_PLAYWRIGHT: string IS_PLAYWRIGHT: string
VITE_KC_DEV_TOKEN: string VITE_KITTYCAD_API_TOKEN: string
VITE_KC_API_WS_MODELING_URL: string VITE_KC_API_WS_MODELING_URL: string
VITE_KC_API_BASE_URL: string VITE_KITTYCAD_API_BASE_URL: string
VITE_KC_SITE_BASE_URL: string VITE_KC_SITE_BASE_URL: string
VITE_KC_SITE_APP_URL: string VITE_KC_SITE_APP_URL: string
VITE_KC_SKIP_AUTH: string
VITE_KC_CONNECTION_TIMEOUT_MS: string VITE_KC_CONNECTION_TIMEOUT_MS: string
VITE_KC_DEV_TOKEN: string
NODE_ENV: string NODE_ENV: string
PROD: string PROD: string
DEV: string DEV: string

234
package-lock.json generated
View File

@ -149,7 +149,7 @@
"ts-node": "^10.0.0", "ts-node": "^10.0.0",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"typescript-eslint": "^8.30.1", "typescript-eslint": "^8.30.1",
"vite": "^5.4.18", "vite": "^5.4.19",
"vite-plugin-package-version": "^1.1.0", "vite-plugin-package-version": "^1.1.0",
"vite-plugin-top-level-await": "^1.5.0", "vite-plugin-top-level-await": "^1.5.0",
"vite-tsconfig-paths": "^4.3.2", "vite-tsconfig-paths": "^4.3.2",
@ -3652,9 +3652,9 @@
} }
}, },
"node_modules/@esbuild/aix-ppc64": { "node_modules/@esbuild/aix-ppc64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz",
"integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@ -3668,9 +3668,9 @@
} }
}, },
"node_modules/@esbuild/android-arm": { "node_modules/@esbuild/android-arm": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz",
"integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -3684,9 +3684,9 @@
} }
}, },
"node_modules/@esbuild/android-arm64": { "node_modules/@esbuild/android-arm64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz",
"integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3700,9 +3700,9 @@
} }
}, },
"node_modules/@esbuild/android-x64": { "node_modules/@esbuild/android-x64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz",
"integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3716,9 +3716,9 @@
} }
}, },
"node_modules/@esbuild/darwin-arm64": { "node_modules/@esbuild/darwin-arm64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz",
"integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3732,9 +3732,9 @@
} }
}, },
"node_modules/@esbuild/darwin-x64": { "node_modules/@esbuild/darwin-x64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz",
"integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3748,9 +3748,9 @@
} }
}, },
"node_modules/@esbuild/freebsd-arm64": { "node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz",
"integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3764,9 +3764,9 @@
} }
}, },
"node_modules/@esbuild/freebsd-x64": { "node_modules/@esbuild/freebsd-x64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz",
"integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3780,9 +3780,9 @@
} }
}, },
"node_modules/@esbuild/linux-arm": { "node_modules/@esbuild/linux-arm": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz",
"integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -3796,9 +3796,9 @@
} }
}, },
"node_modules/@esbuild/linux-arm64": { "node_modules/@esbuild/linux-arm64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz",
"integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3812,9 +3812,9 @@
} }
}, },
"node_modules/@esbuild/linux-ia32": { "node_modules/@esbuild/linux-ia32": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz",
"integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -3828,9 +3828,9 @@
} }
}, },
"node_modules/@esbuild/linux-loong64": { "node_modules/@esbuild/linux-loong64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz",
"integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==",
"cpu": [ "cpu": [
"loong64" "loong64"
], ],
@ -3844,9 +3844,9 @@
} }
}, },
"node_modules/@esbuild/linux-mips64el": { "node_modules/@esbuild/linux-mips64el": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz",
"integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==",
"cpu": [ "cpu": [
"mips64el" "mips64el"
], ],
@ -3860,9 +3860,9 @@
} }
}, },
"node_modules/@esbuild/linux-ppc64": { "node_modules/@esbuild/linux-ppc64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz",
"integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==",
"cpu": [ "cpu": [
"ppc64" "ppc64"
], ],
@ -3876,9 +3876,9 @@
} }
}, },
"node_modules/@esbuild/linux-riscv64": { "node_modules/@esbuild/linux-riscv64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz",
"integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@ -3892,9 +3892,9 @@
} }
}, },
"node_modules/@esbuild/linux-s390x": { "node_modules/@esbuild/linux-s390x": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz",
"integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==",
"cpu": [ "cpu": [
"s390x" "s390x"
], ],
@ -3908,9 +3908,9 @@
} }
}, },
"node_modules/@esbuild/linux-x64": { "node_modules/@esbuild/linux-x64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz",
"integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3924,9 +3924,9 @@
} }
}, },
"node_modules/@esbuild/netbsd-arm64": { "node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz",
"integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3940,9 +3940,9 @@
} }
}, },
"node_modules/@esbuild/netbsd-x64": { "node_modules/@esbuild/netbsd-x64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz",
"integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3956,9 +3956,9 @@
} }
}, },
"node_modules/@esbuild/openbsd-arm64": { "node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz",
"integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -3972,9 +3972,9 @@
} }
}, },
"node_modules/@esbuild/openbsd-x64": { "node_modules/@esbuild/openbsd-x64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz",
"integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -3988,9 +3988,9 @@
} }
}, },
"node_modules/@esbuild/sunos-x64": { "node_modules/@esbuild/sunos-x64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz",
"integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -4004,9 +4004,9 @@
} }
}, },
"node_modules/@esbuild/win32-arm64": { "node_modules/@esbuild/win32-arm64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz",
"integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -4020,9 +4020,9 @@
} }
}, },
"node_modules/@esbuild/win32-ia32": { "node_modules/@esbuild/win32-ia32": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz",
"integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -4036,9 +4036,9 @@
} }
}, },
"node_modules/@esbuild/win32-x64": { "node_modules/@esbuild/win32-x64": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz",
"integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -13219,9 +13219,9 @@
"optional": true "optional": true
}, },
"node_modules/esbuild": { "node_modules/esbuild": {
"version": "0.25.3", "version": "0.25.4",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz",
"integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"bin": { "bin": {
@ -13231,31 +13231,31 @@
"node": ">=18" "node": ">=18"
}, },
"optionalDependencies": { "optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.3", "@esbuild/aix-ppc64": "0.25.4",
"@esbuild/android-arm": "0.25.3", "@esbuild/android-arm": "0.25.4",
"@esbuild/android-arm64": "0.25.3", "@esbuild/android-arm64": "0.25.4",
"@esbuild/android-x64": "0.25.3", "@esbuild/android-x64": "0.25.4",
"@esbuild/darwin-arm64": "0.25.3", "@esbuild/darwin-arm64": "0.25.4",
"@esbuild/darwin-x64": "0.25.3", "@esbuild/darwin-x64": "0.25.4",
"@esbuild/freebsd-arm64": "0.25.3", "@esbuild/freebsd-arm64": "0.25.4",
"@esbuild/freebsd-x64": "0.25.3", "@esbuild/freebsd-x64": "0.25.4",
"@esbuild/linux-arm": "0.25.3", "@esbuild/linux-arm": "0.25.4",
"@esbuild/linux-arm64": "0.25.3", "@esbuild/linux-arm64": "0.25.4",
"@esbuild/linux-ia32": "0.25.3", "@esbuild/linux-ia32": "0.25.4",
"@esbuild/linux-loong64": "0.25.3", "@esbuild/linux-loong64": "0.25.4",
"@esbuild/linux-mips64el": "0.25.3", "@esbuild/linux-mips64el": "0.25.4",
"@esbuild/linux-ppc64": "0.25.3", "@esbuild/linux-ppc64": "0.25.4",
"@esbuild/linux-riscv64": "0.25.3", "@esbuild/linux-riscv64": "0.25.4",
"@esbuild/linux-s390x": "0.25.3", "@esbuild/linux-s390x": "0.25.4",
"@esbuild/linux-x64": "0.25.3", "@esbuild/linux-x64": "0.25.4",
"@esbuild/netbsd-arm64": "0.25.3", "@esbuild/netbsd-arm64": "0.25.4",
"@esbuild/netbsd-x64": "0.25.3", "@esbuild/netbsd-x64": "0.25.4",
"@esbuild/openbsd-arm64": "0.25.3", "@esbuild/openbsd-arm64": "0.25.4",
"@esbuild/openbsd-x64": "0.25.3", "@esbuild/openbsd-x64": "0.25.4",
"@esbuild/sunos-x64": "0.25.3", "@esbuild/sunos-x64": "0.25.4",
"@esbuild/win32-arm64": "0.25.3", "@esbuild/win32-arm64": "0.25.4",
"@esbuild/win32-ia32": "0.25.3", "@esbuild/win32-ia32": "0.25.4",
"@esbuild/win32-x64": "0.25.3" "@esbuild/win32-x64": "0.25.4"
} }
}, },
"node_modules/escalade": { "node_modules/escalade": {
@ -25132,11 +25132,10 @@
"optional": true "optional": true
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "5.4.18", "version": "5.4.19",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.18.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.19.tgz",
"integrity": "sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==", "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"esbuild": "^0.21.3", "esbuild": "^0.21.3",
"postcss": "^8.4.43", "postcss": "^8.4.43",
@ -26640,10 +26639,25 @@
"vscode-uri": "^3.1.0" "vscode-uri": "^3.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.14.1", "@types/node": "^24.0.7",
"ts-node": "^10.9.2" "ts-node": "^10.9.2"
} }
}, },
"packages/codemirror-lsp-client/node_modules/@types/node": {
"version": "24.0.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.10.tgz",
"integrity": "sha512-ENHwaH+JIRTDIEEbDK6QSQntAYGtbvdDXnMXnZaZ6k13Du1dPMmprkEHIL7ok2Wl2aZevetwTAb5S+7yIF+enA==",
"dev": true,
"dependencies": {
"undici-types": "~7.8.0"
}
},
"packages/codemirror-lsp-client/node_modules/undici-types": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz",
"integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==",
"dev": true
},
"rust/kcl-language-server": { "rust/kcl-language-server": {
"version": "0.0.0", "version": "0.0.0",
"license": "MIT", "license": "MIT",
@ -26661,7 +26675,7 @@
"@vscode/test-electron": "^2.4.1", "@vscode/test-electron": "^2.4.1",
"@vscode/vsce": "^3.3.2", "@vscode/vsce": "^3.3.2",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"esbuild": "^0.25.3", "esbuild": "^0.25.4",
"glob": "^11.0.1", "glob": "^11.0.1",
"mocha": "^11.1.0", "mocha": "^11.1.0",
"typescript": "^5.8.3" "typescript": "^5.8.3"

View File

@ -227,7 +227,7 @@
"ts-node": "^10.0.0", "ts-node": "^10.0.0",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"typescript-eslint": "^8.30.1", "typescript-eslint": "^8.30.1",
"vite": "^5.4.18", "vite": "^5.4.19",
"vite-plugin-package-version": "^1.1.0", "vite-plugin-package-version": "^1.1.0",
"vite-plugin-top-level-await": "^1.5.0", "vite-plugin-top-level-await": "^1.5.0",
"vite-tsconfig-paths": "^4.3.2", "vite-tsconfig-paths": "^4.3.2",

View File

@ -123,7 +123,7 @@
"@vscode/test-electron": "^2.4.1", "@vscode/test-electron": "^2.4.1",
"@vscode/vsce": "^3.3.2", "@vscode/vsce": "^3.3.2",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"esbuild": "^0.25.3", "esbuild": "^0.25.4",
"glob": "^11.0.1", "glob": "^11.0.1",
"mocha": "^11.1.0", "mocha": "^11.1.0",
"typescript": "^5.8.3" "typescript": "^5.8.3"

View File

@ -10,71 +10,76 @@ DATA;
NAMED_UNIT(*) NAMED_UNIT(*)
SI_UNIT($, .METRE.) SI_UNIT($, .METRE.)
); );
#2 = UNCERTAINTY_MEASURE_WITH_UNIT(0.00001, #1, 'DISTANCE_ACCURACY_VALUE', $); #2 = (
#3 = ( NAMED_UNIT(*)
PLANE_ANGLE_UNIT()
SI_UNIT($, .RADIAN.)
);
#3 = UNCERTAINTY_MEASURE_WITH_UNIT(0.00001, #1, 'DISTANCE_ACCURACY_VALUE', $);
#4 = (
GEOMETRIC_REPRESENTATION_CONTEXT(3) GEOMETRIC_REPRESENTATION_CONTEXT(3)
GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#2)) GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#3))
GLOBAL_UNIT_ASSIGNED_CONTEXT((#1)) GLOBAL_UNIT_ASSIGNED_CONTEXT((#1, #2))
REPRESENTATION_CONTEXT('', '3D') REPRESENTATION_CONTEXT('', '3D')
); );
#4 = CARTESIAN_POINT('NONE', (0.015, -0.01, -0.005)); #5 = CARTESIAN_POINT('NONE', (0.015, -0.01, -0.005));
#5 = VERTEX_POINT('NONE', #4); #6 = VERTEX_POINT('NONE', #5);
#6 = CARTESIAN_POINT('NONE', (0.015, 0, -0.005)); #7 = CARTESIAN_POINT('NONE', (0.015, 0, -0.005));
#7 = VERTEX_POINT('NONE', #6); #8 = VERTEX_POINT('NONE', #7);
#8 = DIRECTION('NONE', (1, 0, -0)); #9 = DIRECTION('NONE', (1, 0, -0));
#9 = DIRECTION('NONE', (0, 1, 0)); #10 = DIRECTION('NONE', (0, 1, 0));
#10 = CARTESIAN_POINT('NONE', (0.005, -0.01, -0.005)); #11 = CARTESIAN_POINT('NONE', (0.005, -0.01, -0.005));
#11 = AXIS2_PLACEMENT_3D('NONE', #10, #9, #8); #12 = AXIS2_PLACEMENT_3D('NONE', #11, #10, #9);
#12 = CIRCLE('NONE', #11, 0.01); #13 = CIRCLE('NONE', #12, 0.01);
#13 = DIRECTION('NONE', (0, 1, 0)); #14 = DIRECTION('NONE', (0, 1, 0));
#14 = VECTOR('NONE', #13, 1); #15 = VECTOR('NONE', #14, 1);
#15 = CARTESIAN_POINT('NONE', (0.015, -0.01, -0.005)); #16 = CARTESIAN_POINT('NONE', (0.015, -0.01, -0.005));
#16 = LINE('NONE', #15, #14); #17 = LINE('NONE', #16, #15);
#17 = DIRECTION('NONE', (1, 0, -0)); #18 = DIRECTION('NONE', (1, 0, -0));
#18 = DIRECTION('NONE', (0, 1, 0)); #19 = DIRECTION('NONE', (0, 1, 0));
#19 = CARTESIAN_POINT('NONE', (0.005, 0, -0.005)); #20 = CARTESIAN_POINT('NONE', (0.005, 0, -0.005));
#20 = AXIS2_PLACEMENT_3D('NONE', #19, #18, #17); #21 = AXIS2_PLACEMENT_3D('NONE', #20, #19, #18);
#21 = CIRCLE('NONE', #20, 0.01); #22 = CIRCLE('NONE', #21, 0.01);
#22 = EDGE_CURVE('NONE', #5, #5, #12, .T.); #23 = EDGE_CURVE('NONE', #6, #6, #13, .T.);
#23 = EDGE_CURVE('NONE', #5, #7, #16, .T.); #24 = EDGE_CURVE('NONE', #6, #8, #17, .T.);
#24 = EDGE_CURVE('NONE', #7, #7, #21, .T.); #25 = EDGE_CURVE('NONE', #8, #8, #22, .T.);
#25 = CARTESIAN_POINT('NONE', (0.005, -0.005, -0.005)); #26 = CARTESIAN_POINT('NONE', (0.005, -0.005, -0.005));
#26 = DIRECTION('NONE', (0, 1, 0)); #27 = DIRECTION('NONE', (0, 1, 0));
#27 = DIRECTION('NONE', (1, 0, -0)); #28 = DIRECTION('NONE', (1, 0, -0));
#28 = AXIS2_PLACEMENT_3D('NONE', #25, #26, #27); #29 = AXIS2_PLACEMENT_3D('NONE', #26, #27, #28);
#29 = CYLINDRICAL_SURFACE('NONE', #28, 0.01); #30 = CYLINDRICAL_SURFACE('NONE', #29, 0.01);
#30 = CARTESIAN_POINT('NONE', (0, -0.01, -0)); #31 = CARTESIAN_POINT('NONE', (0, -0.01, -0));
#31 = DIRECTION('NONE', (0, 1, 0)); #32 = DIRECTION('NONE', (0, 1, 0));
#32 = AXIS2_PLACEMENT_3D('NONE', #30, #31, $); #33 = AXIS2_PLACEMENT_3D('NONE', #31, #32, $);
#33 = PLANE('NONE', #32); #34 = PLANE('NONE', #33);
#34 = CARTESIAN_POINT('NONE', (0, 0, -0)); #35 = CARTESIAN_POINT('NONE', (0, 0, -0));
#35 = DIRECTION('NONE', (0, 1, 0)); #36 = DIRECTION('NONE', (0, 1, 0));
#36 = AXIS2_PLACEMENT_3D('NONE', #34, #35, $); #37 = AXIS2_PLACEMENT_3D('NONE', #35, #36, $);
#37 = PLANE('NONE', #36); #38 = PLANE('NONE', #37);
#38 = ORIENTED_EDGE('NONE', *, *, #22, .T.); #39 = ORIENTED_EDGE('NONE', *, *, #23, .T.);
#39 = ORIENTED_EDGE('NONE', *, *, #24, .F.); #40 = ORIENTED_EDGE('NONE', *, *, #25, .F.);
#40 = EDGE_LOOP('NONE', (#38)); #41 = EDGE_LOOP('NONE', (#39));
#41 = FACE_BOUND('NONE', #40, .T.); #42 = FACE_BOUND('NONE', #41, .T.);
#42 = EDGE_LOOP('NONE', (#39)); #43 = EDGE_LOOP('NONE', (#40));
#43 = FACE_BOUND('NONE', #42, .T.); #44 = FACE_BOUND('NONE', #43, .T.);
#44 = ADVANCED_FACE('NONE', (#41, #43), #29, .T.); #45 = ADVANCED_FACE('NONE', (#42, #44), #30, .T.);
#45 = ORIENTED_EDGE('NONE', *, *, #22, .F.); #46 = ORIENTED_EDGE('NONE', *, *, #23, .F.);
#46 = EDGE_LOOP('NONE', (#45)); #47 = EDGE_LOOP('NONE', (#46));
#47 = FACE_BOUND('NONE', #46, .T.); #48 = FACE_BOUND('NONE', #47, .T.);
#48 = ADVANCED_FACE('NONE', (#47), #33, .F.); #49 = ADVANCED_FACE('NONE', (#48), #34, .F.);
#49 = ORIENTED_EDGE('NONE', *, *, #24, .T.); #50 = ORIENTED_EDGE('NONE', *, *, #25, .T.);
#50 = EDGE_LOOP('NONE', (#49)); #51 = EDGE_LOOP('NONE', (#50));
#51 = FACE_BOUND('NONE', #50, .T.); #52 = FACE_BOUND('NONE', #51, .T.);
#52 = ADVANCED_FACE('NONE', (#51), #37, .T.); #53 = ADVANCED_FACE('NONE', (#52), #38, .T.);
#53 = CLOSED_SHELL('NONE', (#44, #48, #52)); #54 = CLOSED_SHELL('NONE', (#45, #49, #53));
#54 = MANIFOLD_SOLID_BREP('NONE', #53); #55 = MANIFOLD_SOLID_BREP('NONE', #54);
#55 = APPLICATION_CONTEXT('configuration controlled 3D design of mechanical parts and assemblies'); #56 = APPLICATION_CONTEXT('configuration controlled 3D design of mechanical parts and assemblies');
#56 = PRODUCT_DEFINITION_CONTEXT('part definition', #55, 'design'); #57 = PRODUCT_DEFINITION_CONTEXT('part definition', #56, 'design');
#57 = PRODUCT('UNIDENTIFIED_PRODUCT', 'NONE', $, ()); #58 = PRODUCT('UNIDENTIFIED_PRODUCT', 'NONE', $, ());
#58 = PRODUCT_DEFINITION_FORMATION('', $, #57); #59 = PRODUCT_DEFINITION_FORMATION('', $, #58);
#59 = PRODUCT_DEFINITION('design', $, #58, #56); #60 = PRODUCT_DEFINITION('design', $, #59, #57);
#60 = PRODUCT_DEFINITION_SHAPE('NONE', $, #59); #61 = PRODUCT_DEFINITION_SHAPE('NONE', $, #60);
#61 = ADVANCED_BREP_SHAPE_REPRESENTATION('NONE', (#54), #3); #62 = ADVANCED_BREP_SHAPE_REPRESENTATION('NONE', (#55), #4);
#62 = SHAPE_DEFINITION_REPRESENTATION(#60, #61); #63 = SHAPE_DEFINITION_REPRESENTATION(#61, #62);
ENDSEC; ENDSEC;
END-ISO-10303-21; END-ISO-10303-21;

View File

@ -994,6 +994,39 @@ impl Node<MemberExpression> {
// Check the property and object match -- e.g. ints for arrays, strs for objects. // Check the property and object match -- e.g. ints for arrays, strs for objects.
match (object, property, self.computed) { match (object, property, self.computed) {
(KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() {
"yAxis" => {
let (p, u) = plane.info.y_axis.as_3_dims();
Ok(KclValue::array_from_point3d(
p,
NumericType::Known(crate::exec::UnitType::Length(u)),
vec![meta],
))
}
"xAxis" => {
let (p, u) = plane.info.x_axis.as_3_dims();
Ok(KclValue::array_from_point3d(
p,
NumericType::Known(crate::exec::UnitType::Length(u)),
vec![meta],
))
}
"origin" => {
let (p, u) = plane.info.origin.as_3_dims();
Ok(KclValue::array_from_point3d(
p,
NumericType::Known(crate::exec::UnitType::Length(u)),
vec![meta],
))
}
other => Err(KclError::new_undefined_value(
KclErrorDetails::new(
format!("Property '{other}' not found in plane"),
vec![self.clone().into()],
),
None,
)),
},
(KclValue::Object { value: map, meta: _ }, Property::String(property), false) => { (KclValue::Object { value: map, meta: _ }, Property::String(property), false) => {
if let Some(value) = map.get(&property) { if let Some(value) = map.get(&property) {
Ok(value.to_owned()) Ok(value.to_owned())
@ -1013,7 +1046,22 @@ impl Node<MemberExpression> {
vec![self.clone().into()], vec![self.clone().into()],
))) )))
} }
(KclValue::Object { .. }, p, _) => { (KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => {
if i == 0
&& let Some(value) = map.get("x")
{
return Ok(value.to_owned());
}
if i == 1
&& let Some(value) = map.get("y")
{
return Ok(value.to_owned());
}
if i == 2
&& let Some(value) = map.get("z")
{
return Ok(value.to_owned());
}
let t = p.type_name(); let t = p.type_name();
let article = article_for(t); let article = article_for(t);
Err(KclError::new_semantic(KclErrorDetails::new( Err(KclError::new_semantic(KclErrorDetails::new(
@ -2205,4 +2253,12 @@ y = x[0mm + 1]
"#; "#;
parse_execute(ast).await.unwrap_err(); parse_execute(ast).await.unwrap_err();
} }
#[tokio::test(flavor = "multi_thread")]
async fn getting_property_of_plane() {
// let ast = include_str!("../../tests/inputs/planestuff.kcl");
let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
parse_execute(&ast).await.unwrap();
}
} }

View File

@ -921,6 +921,12 @@ impl Point3d {
units: UnitLen::Unknown, units: UnitLen::Unknown,
} }
} }
pub fn as_3_dims(&self) -> ([f64; 3], UnitLen) {
let p = [self.x, self.y, self.z];
let u = self.units;
(p, u)
}
} }
impl From<[TyF64; 3]> for Point3d { impl From<[TyF64; 3]> for Point3d {

View File

@ -458,6 +458,31 @@ impl KclValue {
} }
} }
/// Put the point into a KCL point.
pub fn array_from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
let [x, y, z] = p;
Self::HomArray {
value: vec![
Self::Number {
value: x,
meta: meta.clone(),
ty,
},
Self::Number {
value: y,
meta: meta.clone(),
ty,
},
Self::Number {
value: z,
meta: meta.clone(),
ty,
},
],
ty: ty.into(),
}
}
pub(crate) fn as_usize(&self) -> Option<usize> { pub(crate) fn as_usize(&self) -> Option<usize> {
match self { match self {
KclValue::Number { value, .. } => crate::try_f64_to_usize(*value), KclValue::Number { value, .. } => crate::try_f64_to_usize(*value),

View File

@ -0,0 +1,60 @@
// There are 3 ways to define a plane in KCL, according to https://zoo.dev/docs/kcl-std/types/std-types-Plane
// - A default plane
// - Modifying a default plane e.g. via offsetPlane
// - Defining your own struct
// This file tests they all work equivalently.
// Define a plane using struct representation.
myPlane = {
origin = { x = 0, y = 0, z = 0 },
xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 1, z = 0 },
}
// Prove we can get its axes and origin.
ax = myPlane.xAxis
assert(ax[0], isEqualTo = 1)
assert(ax[1], isEqualTo = 0)
assert(ax[2], isEqualTo = 0)
ay = myPlane.yAxis
assert(ay[0], isEqualTo = 0)
assert(ay[1], isEqualTo = 1)
assert(ay[2], isEqualTo = 0)
aorigin = myPlane.origin
assert(aorigin[0], isEqualTo = 0)
assert(aorigin[1], isEqualTo = 0)
assert(aorigin[2], isEqualTo = 0)
// Define a plane using standard planes.
myOtherPlane = XY
// Prove we can get its axes and origin.
axOther = myOtherPlane.xAxis
assert(axOther[0], isEqualTo = 1)
assert(axOther[1], isEqualTo = 0)
assert(axOther[2], isEqualTo = 0)
ayOther = myOtherPlane.yAxis
assert(ayOther[0], isEqualTo = 0)
assert(ayOther[1], isEqualTo = 1)
assert(ayOther[2], isEqualTo = 0)
aoriginOther = myOtherPlane.origin
assert(aoriginOther[0], isEqualTo = 0)
assert(aoriginOther[1], isEqualTo = 0)
assert(aoriginOther[2], isEqualTo = 0)
// Define a plane using a plane-modifying function like offsetPlane.
myAlternatePlane = offsetPlane(XY, offset = 0)
// Prove we can get its axes and origin.
axAlternate = myAlternatePlane.xAxis
assert(axAlternate[0], isEqualTo = 1)
assert(axAlternate[1], isEqualTo = 0)
assert(axAlternate[2], isEqualTo = 0)
ayAlternate = myAlternatePlane.yAxis
assert(ayAlternate[0], isEqualTo = 0)
assert(ayAlternate[1], isEqualTo = 1)
assert(ayAlternate[2], isEqualTo = 0)
aoriginAlternate = myAlternatePlane.origin
assert(aoriginAlternate[0], isEqualTo = 0)
assert(aoriginAlternate[1], isEqualTo = 0)
assert(aoriginAlternate[2], isEqualTo = 0)

View File

@ -4,6 +4,8 @@
URL STATUS URL STATUS
000 https://${BASE_URL} 000 https://${BASE_URL}
405 https://api.dev.zoo.dev/oauth2/token/revoke
401 https://api.dev.zoo.dev/users
301 https://discord.gg/JQEpHR7Nt2 301 https://discord.gg/JQEpHR7Nt2
404 https://github.com/KittyCAD/engine/issues/3528 404 https://github.com/KittyCAD/engine/issues/3528
404 https://github.com/KittyCAD/modeling-app/commit/${ref} 404 https://github.com/KittyCAD/modeling-app/commit/${ref}

View File

@ -7,7 +7,7 @@ import {
LanguageServerClient, LanguageServerClient,
LspWorkerEventType, LspWorkerEventType,
} from '@kittycad/codemirror-lsp-client' } from '@kittycad/codemirror-lsp-client'
import { TEST, VITE_KC_API_BASE_URL } from '@src/env' import { TEST } from '@src/env'
import React, { createContext, useContext, useMemo, useState } from 'react' import React, { createContext, useContext, useMemo, useState } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import type * as LSP from 'vscode-languageserver-protocol' import type * as LSP from 'vscode-languageserver-protocol'
@ -28,6 +28,7 @@ import type { FileEntry } from '@src/lib/project'
import { codeManager } from '@src/lib/singletons' import { codeManager } from '@src/lib/singletons'
import { err } from '@src/lib/trap' import { err } from '@src/lib/trap'
import { useToken } from '@src/lib/singletons' import { useToken } from '@src/lib/singletons'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
function getWorkspaceFolders(): LSP.WorkspaceFolder[] { function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
return [] return []
@ -85,7 +86,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
const initEvent: KclWorkerOptions = { const initEvent: KclWorkerOptions = {
wasmUrl: wasmUrl(), wasmUrl: wasmUrl(),
token: token, token: token,
apiBaseUrl: VITE_KC_API_BASE_URL, apiBaseUrl: withAPIBaseURL(''),
} }
lspWorker.postMessage({ lspWorker.postMessage({
worker: LspWorker.Kcl, worker: LspWorker.Kcl,
@ -178,7 +179,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
const initEvent: CopilotWorkerOptions = { const initEvent: CopilotWorkerOptions = {
wasmUrl: wasmUrl(), wasmUrl: wasmUrl(),
token: token, token: token,
apiBaseUrl: VITE_KC_API_BASE_URL, apiBaseUrl: withAPIBaseURL(''),
} }
lspWorker.postMessage({ lspWorker.postMessage({
worker: LspWorker.Copilot, worker: LspWorker.Copilot,

View File

@ -24,7 +24,12 @@ import {
getOperationVariableName, getOperationVariableName,
stdLibMap, stdLibMap,
} from '@src/lib/operations' } from '@src/lib/operations'
import { editorManager, kclManager, rustContext } from '@src/lib/singletons' import {
editorManager,
kclManager,
rustContext,
sceneInfra,
} from '@src/lib/singletons'
import { import {
featureTreeMachine, featureTreeMachine,
featureTreeMachineDefaultContext, featureTreeMachineDefaultContext,
@ -34,11 +39,20 @@ import {
kclEditorActor, kclEditorActor,
selectionEventSelector, selectionEventSelector,
} from '@src/machines/kclEditorMachine' } from '@src/machines/kclEditorMachine'
import type { Plane } from '@rust/kcl-lib/bindings/Artifact'
import {
selectDefaultSketchPlane,
selectOffsetSketchPlane,
} from '@src/lib/selections'
import type { DefaultPlaneStr } from '@src/lib/planes'
export const FeatureTreePane = () => { export const FeatureTreePane = () => {
const isEditorMounted = useSelector(kclEditorActor, editorIsMountedSelector) const isEditorMounted = useSelector(kclEditorActor, editorIsMountedSelector)
const lastSelectionEvent = useSelector(kclEditorActor, selectionEventSelector) const lastSelectionEvent = useSelector(kclEditorActor, selectionEventSelector)
const { send: modelingSend, state: modelingState } = useModelingContext() const { send: modelingSend, state: modelingState } = useModelingContext()
const sketchNoFace = modelingState.matches('Sketch no face')
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [_featureTreeState, featureTreeSend] = useMachine( const [_featureTreeState, featureTreeSend] = useMachine(
featureTreeMachine.provide({ featureTreeMachine.provide({
@ -195,6 +209,7 @@ export const FeatureTreePane = () => {
key={key} key={key}
item={operation} item={operation}
send={featureTreeSend} send={featureTreeSend}
sketchNoFace={sketchNoFace}
/> />
) )
})} })}
@ -251,6 +266,7 @@ const OperationItemWrapper = ({
customSuffix, customSuffix,
className, className,
selectable = true, selectable = true,
greyedOut = false,
...props ...props
}: React.HTMLAttributes<HTMLButtonElement> & { }: React.HTMLAttributes<HTMLButtonElement> & {
icon: CustomIconName icon: CustomIconName
@ -262,18 +278,19 @@ const OperationItemWrapper = ({
menuItems?: ComponentProps<typeof ContextMenu>['items'] menuItems?: ComponentProps<typeof ContextMenu>['items']
errors?: Diagnostic[] errors?: Diagnostic[]
selectable?: boolean selectable?: boolean
greyedOut?: boolean
}) => { }) => {
const menuRef = useRef<HTMLDivElement>(null) const menuRef = useRef<HTMLDivElement>(null)
return ( return (
<div <div
ref={menuRef} ref={menuRef}
className={`flex select-none items-center group/item my-0 py-0.5 px-1 ${selectable ? 'focus-within:bg-primary/10 hover:bg-primary/5' : ''}`} className={`flex select-none items-center group/item my-0 py-0.5 px-1 ${selectable ? 'focus-within:bg-primary/10 hover:bg-primary/5' : ''} ${greyedOut ? 'opacity-50 cursor-not-allowed' : ''}`}
data-testid="feature-tree-operation-item" data-testid="feature-tree-operation-item"
> >
<button <button
{...props} {...props}
className={`reset !py-0.5 !px-1 flex-1 flex items-center gap-2 text-left text-base ${selectable ? 'border-transparent dark:border-transparent' : 'border-none cursor-default'} ${className}`} className={`reset !py-0.5 !px-1 flex-1 flex items-center gap-2 text-left text-base ${selectable ? 'border-transparent dark:border-transparent' : '!border-transparent cursor-default'} ${className}`}
> >
<CustomIcon name={icon} className="w-5 h-5 block" /> <CustomIcon name={icon} className="w-5 h-5 block" />
<div className="flex flex-1 items-baseline align-baseline"> <div className="flex flex-1 items-baseline align-baseline">
@ -311,6 +328,7 @@ const OperationItemWrapper = ({
const OperationItem = (props: { const OperationItem = (props: {
item: Operation item: Operation
send: Prop<Actor<typeof featureTreeMachine>, 'send'> send: Prop<Actor<typeof featureTreeMachine>, 'send'>
sketchNoFace: boolean
}) => { }) => {
const kclContext = useKclContext() const kclContext = useKclContext()
const name = getOperationLabel(props.item) const name = getOperationLabel(props.item)
@ -343,15 +361,22 @@ const OperationItem = (props: {
}, [kclContext.diagnostics.length]) }, [kclContext.diagnostics.length])
function selectOperation() { function selectOperation() {
if (props.item.type === 'GroupEnd') { if (props.sketchNoFace) {
return if (isOffsetPlane(props.item)) {
const artifact = findOperationArtifact(props.item)
void selectOffsetSketchPlane(artifact)
}
} else {
if (props.item.type === 'GroupEnd') {
return
}
props.send({
type: 'selectOperation',
data: {
targetSourceRange: sourceRangeFromRust(props.item.sourceRange),
},
})
} }
props.send({
type: 'selectOperation',
data: {
targetSourceRange: sourceRangeFromRust(props.item.sourceRange),
},
})
} }
/** /**
@ -432,6 +457,20 @@ const OperationItem = (props: {
} }
} }
function startSketchOnOffsetPlane() {
if (isOffsetPlane(props.item)) {
const artifact = findOperationArtifact(props.item)
if (artifact?.id) {
sceneInfra.modelingSend({
type: 'Enter sketch',
data: { forceNewSketch: true },
})
void selectOffsetSketchPlane(artifact)
}
}
}
const menuItems = useMemo( const menuItems = useMemo(
() => [ () => [
<ContextMenuItem <ContextMenuItem
@ -477,6 +516,13 @@ const OperationItem = (props: {
</ContextMenuItem>, </ContextMenuItem>,
] ]
: []), : []),
...(isOffsetPlane(props.item)
? [
<ContextMenuItem onClick={startSketchOnOffsetPlane}>
Start Sketch
</ContextMenuItem>,
]
: []),
...(props.item.type === 'StdLibCall' || ...(props.item.type === 'StdLibCall' ||
props.item.type === 'VariableDeclaration' props.item.type === 'VariableDeclaration'
? [ ? [
@ -550,22 +596,63 @@ const OperationItem = (props: {
[props.item, props.send] [props.item, props.send]
) )
const enabled = !props.sketchNoFace || isOffsetPlane(props.item)
return ( return (
<OperationItemWrapper <OperationItemWrapper
selectable={enabled}
icon={getOperationIcon(props.item)} icon={getOperationIcon(props.item)}
name={name} name={name}
variableName={variableName} variableName={variableName}
valueDetail={valueDetail} valueDetail={valueDetail}
menuItems={menuItems} menuItems={menuItems}
onClick={selectOperation} onClick={selectOperation}
onDoubleClick={enterEditFlow} onDoubleClick={props.sketchNoFace ? undefined : enterEditFlow} // no double click in "Sketch no face" mode
errors={errors} errors={errors}
greyedOut={!enabled}
/> />
) )
} }
const DefaultPlanes = () => { const DefaultPlanes = () => {
const { state: modelingState, send } = useModelingContext() const { state: modelingState, send } = useModelingContext()
const sketchNoFace = modelingState.matches('Sketch no face')
const onClickPlane = useCallback(
(planeId: string) => {
if (sketchNoFace) {
selectDefaultSketchPlane(planeId)
} else {
const foundDefaultPlane =
rustContext.defaultPlanes !== null &&
Object.entries(rustContext.defaultPlanes).find(
([, plane]) => plane === planeId
)
if (foundDefaultPlane) {
send({
type: 'Set selection',
data: {
selectionType: 'defaultPlaneSelection',
selection: {
name: foundDefaultPlane[0] as DefaultPlaneStr,
id: planeId,
},
},
})
}
}
},
[sketchNoFace]
)
const startSketchOnDefaultPlane = useCallback((planeId: string) => {
sceneInfra.modelingSend({
type: 'Enter sketch',
data: { forceNewSketch: true },
})
selectDefaultSketchPlane(planeId)
}, [])
const defaultPlanes = rustContext.defaultPlanes const defaultPlanes = rustContext.defaultPlanes
if (!defaultPlanes) return null if (!defaultPlanes) return null
@ -603,7 +690,15 @@ const DefaultPlanes = () => {
customSuffix={plane.customSuffix} customSuffix={plane.customSuffix}
icon={'plane'} icon={'plane'}
name={plane.name} name={plane.name}
selectable={false} selectable={true}
onClick={() => onClickPlane(plane.id)}
menuItems={[
<ContextMenuItem
onClick={() => startSketchOnDefaultPlane(plane.id)}
>
Start Sketch
</ContextMenuItem>,
]}
visibilityToggle={{ visibilityToggle={{
visible: modelingState.context.defaultPlaneVisibility[plane.key], visible: modelingState.context.defaultPlaneVisibility[plane.key],
onVisibilityChange: () => { onVisibilityChange: () => {
@ -620,3 +715,17 @@ const DefaultPlanes = () => {
</div> </div>
) )
} }
type StdLibCallOp = Extract<Operation, { type: 'StdLibCall' }>
const isOffsetPlane = (item: Operation): item is StdLibCallOp => {
return item.type === 'StdLibCall' && item.name === 'offsetPlane'
}
const findOperationArtifact = (item: StdLibCallOp) => {
const nodePath = JSON.stringify(item.nodePath)
const artifact = [...kclManager.artifactGraph.values()].find(
(a) => JSON.stringify((a as Plane).codeRef?.nodePath) === nodePath
)
return artifact
}

View File

@ -10,13 +10,22 @@ import {
import { useModelingContext } from '@src/hooks/useModelingContext' import { useModelingContext } from '@src/hooks/useModelingContext'
import type { AxisNames } from '@src/lib/constants' import type { AxisNames } from '@src/lib/constants'
import { VIEW_NAMES_SEMANTIC } from '@src/lib/constants' import { VIEW_NAMES_SEMANTIC } from '@src/lib/constants'
import { sceneInfra } from '@src/lib/singletons' import { kclManager, sceneInfra } from '@src/lib/singletons'
import { reportRejection } from '@src/lib/trap' import { err, reportRejection } from '@src/lib/trap'
import { useSettings } from '@src/lib/singletons' import { useSettings } from '@src/lib/singletons'
import { resetCameraPosition } from '@src/lib/resetCameraPosition' import { resetCameraPosition } from '@src/lib/resetCameraPosition'
import type { Selections } from '@src/lib/selections'
import {
selectDefaultSketchPlane,
selectOffsetSketchPlane,
} from '@src/lib/selections'
export function useViewControlMenuItems() { export function useViewControlMenuItems() {
const { state: modelingState, send: modelingSend } = useModelingContext() const { state: modelingState, send: modelingSend } = useModelingContext()
const selectedPlaneId = getCurrentPlaneId(
modelingState.context.selectionRanges
)
const settings = useSettings() const settings = useSettings()
const shouldLockView = const shouldLockView =
modelingState.matches('Sketch') && modelingState.matches('Sketch') &&
@ -56,9 +65,35 @@ export function useViewControlMenuItems() {
Center view on selection Center view on selection
</ContextMenuItem>, </ContextMenuItem>,
<ContextMenuDivider />, <ContextMenuDivider />,
<ContextMenuItem
onClick={() => {
if (selectedPlaneId) {
sceneInfra.modelingSend({
type: 'Enter sketch',
data: { forceNewSketch: true },
})
const defaultSketchPlaneSelected =
selectDefaultSketchPlane(selectedPlaneId)
if (
!err(defaultSketchPlaneSelected) &&
defaultSketchPlaneSelected
) {
return
}
const artifact = kclManager.artifactGraph.get(selectedPlaneId)
void selectOffsetSketchPlane(artifact)
}
}}
disabled={!selectedPlaneId}
>
Start sketch on selection
</ContextMenuItem>,
<ContextMenuDivider />,
<ContextMenuItemRefresh />, <ContextMenuItemRefresh />,
], ],
[VIEW_NAMES_SEMANTIC, shouldLockView] [VIEW_NAMES_SEMANTIC, shouldLockView, selectedPlaneId]
) )
return menuItems return menuItems
} }
@ -77,3 +112,21 @@ export function ViewControlContextMenu({
/> />
) )
} }
function getCurrentPlaneId(selectionRanges: Selections): string | null {
const defaultPlane = selectionRanges.otherSelections.find(
(selection) => typeof selection === 'object' && 'name' in selection
)
if (defaultPlane) {
return defaultPlane.id
}
const planeSelection = selectionRanges.graphSelections.find(
(selection) => selection.artifact?.type === 'plane'
)
if (planeSelection) {
return planeSelection.artifact?.id || null
}
return null
}

View File

@ -8,13 +8,14 @@ export const NODE_ENV = env.NODE_ENV as string | undefined
export const VITE_KC_API_WS_MODELING_URL = env.VITE_KC_API_WS_MODELING_URL as export const VITE_KC_API_WS_MODELING_URL = env.VITE_KC_API_WS_MODELING_URL as
| string | string
| undefined | undefined
export const VITE_KC_API_BASE_URL = env.VITE_KC_API_BASE_URL export const VITE_KITTYCAD_API_BASE_URL = env.VITE_KITTYCAD_API_BASE_URL
export const VITE_KC_SITE_BASE_URL = env.VITE_KC_SITE_BASE_URL export const VITE_KC_SITE_BASE_URL = env.VITE_KC_SITE_BASE_URL
export const VITE_KC_SITE_APP_URL = env.VITE_KC_SITE_APP_URL export const VITE_KC_SITE_APP_URL = env.VITE_KC_SITE_APP_URL
export const VITE_KC_SKIP_AUTH = env.VITE_KC_SKIP_AUTH as string | undefined
export const VITE_KC_CONNECTION_TIMEOUT_MS = export const VITE_KC_CONNECTION_TIMEOUT_MS =
env.VITE_KC_CONNECTION_TIMEOUT_MS as string | undefined env.VITE_KC_CONNECTION_TIMEOUT_MS as string | undefined
export const VITE_KC_DEV_TOKEN = env.VITE_KC_DEV_TOKEN as string | undefined export const VITE_KITTYCAD_API_TOKEN = env.VITE_KITTYCAD_API_TOKEN as
| string
| undefined
export const PROD = env.PROD as string | undefined export const PROD = env.PROD as string | undefined
export const TEST = env.TEST as string | undefined export const TEST = env.TEST as string | undefined
export const DEV = env.DEV as string | undefined export const DEV = env.DEV as string | undefined

View File

@ -14,13 +14,15 @@ import {
import { isTopLevelModule } from '@src/lang/util' import { isTopLevelModule } from '@src/lang/util'
import type { CallExpressionKw, PathToNode } from '@src/lang/wasm' import type { CallExpressionKw, PathToNode } from '@src/lang/wasm'
import { defaultSourceRange } from '@src/lang/sourceRange' import { defaultSourceRange } from '@src/lang/sourceRange'
import type { DefaultPlaneStr } from '@src/lib/planes' import {
import { getEventForSelectWithPoint } from '@src/lib/selections' getEventForSelectWithPoint,
selectDefaultSketchPlane,
selectOffsetSketchPlane,
} from '@src/lib/selections'
import { import {
editorManager, editorManager,
engineCommandManager, engineCommandManager,
kclManager, kclManager,
rustContext,
sceneEntitiesManager, sceneEntitiesManager,
sceneInfra, sceneInfra,
} from '@src/lib/singletons' } from '@src/lib/singletons'
@ -96,131 +98,18 @@ export function useEngineConnectionSubscriptions() {
;(async () => { ;(async () => {
let planeOrFaceId = data.entity_id let planeOrFaceId = data.entity_id
if (!planeOrFaceId) return if (!planeOrFaceId) return
const defaultSketchPlaneSelected =
selectDefaultSketchPlane(planeOrFaceId)
if ( if (
rustContext.defaultPlanes?.xy === planeOrFaceId || !err(defaultSketchPlaneSelected) &&
rustContext.defaultPlanes?.xz === planeOrFaceId || defaultSketchPlaneSelected
rustContext.defaultPlanes?.yz === planeOrFaceId ||
rustContext.defaultPlanes?.negXy === planeOrFaceId ||
rustContext.defaultPlanes?.negXz === planeOrFaceId ||
rustContext.defaultPlanes?.negYz === planeOrFaceId
) { ) {
let planeId = planeOrFaceId
const defaultPlaneStrMap: Record<string, DefaultPlaneStr> = {
[rustContext.defaultPlanes.xy]: 'XY',
[rustContext.defaultPlanes.xz]: 'XZ',
[rustContext.defaultPlanes.yz]: 'YZ',
[rustContext.defaultPlanes.negXy]: '-XY',
[rustContext.defaultPlanes.negXz]: '-XZ',
[rustContext.defaultPlanes.negYz]: '-YZ',
}
// TODO can we get this information from rust land when it creates the default planes?
// maybe returned from make_default_planes (src/wasm-lib/src/wasm.rs)
let zAxis: [number, number, number] = [0, 0, 1]
let yAxis: [number, number, number] = [0, 1, 0]
// get unit vector from camera position to target
const camVector = sceneInfra.camControls.camera.position
.clone()
.sub(sceneInfra.camControls.target)
if (rustContext.defaultPlanes?.xy === planeId) {
zAxis = [0, 0, 1]
yAxis = [0, 1, 0]
if (camVector.z < 0) {
zAxis = [0, 0, -1]
planeId = rustContext.defaultPlanes?.negXy || ''
}
} else if (rustContext.defaultPlanes?.yz === planeId) {
zAxis = [1, 0, 0]
yAxis = [0, 0, 1]
if (camVector.x < 0) {
zAxis = [-1, 0, 0]
planeId = rustContext.defaultPlanes?.negYz || ''
}
} else if (rustContext.defaultPlanes?.xz === planeId) {
zAxis = [0, 1, 0]
yAxis = [0, 0, 1]
planeId = rustContext.defaultPlanes?.negXz || ''
if (camVector.y < 0) {
zAxis = [0, -1, 0]
planeId = rustContext.defaultPlanes?.xz || ''
}
}
sceneInfra.modelingSend({
type: 'Select sketch plane',
data: {
type: 'defaultPlane',
planeId: planeId,
plane: defaultPlaneStrMap[planeId],
zAxis,
yAxis,
},
})
return return
} }
const artifact = kclManager.artifactGraph.get(planeOrFaceId) const artifact = kclManager.artifactGraph.get(planeOrFaceId)
if (await selectOffsetSketchPlane(artifact)) {
if (artifact?.type === 'plane') {
const planeInfo =
await sceneEntitiesManager.getFaceDetails(planeOrFaceId)
// Apply camera-based orientation logic similar to default planes
let zAxis: [number, number, number] = [
planeInfo.z_axis.x,
planeInfo.z_axis.y,
planeInfo.z_axis.z,
]
let yAxis: [number, number, number] = [
planeInfo.y_axis.x,
planeInfo.y_axis.y,
planeInfo.y_axis.z,
]
// Get camera vector to determine which side of the plane we're viewing from
const camVector = sceneInfra.camControls.camera.position
.clone()
.sub(sceneInfra.camControls.target)
// Determine the canonical (absolute) plane orientation
const absZAxis: [number, number, number] = [
Math.abs(zAxis[0]),
Math.abs(zAxis[1]),
Math.abs(zAxis[2]),
]
// Find the dominant axis (like default planes do)
const maxComponent = Math.max(...absZAxis)
const dominantAxisIndex = absZAxis.indexOf(maxComponent)
// Check camera position against canonical orientation (like default planes)
const cameraComponents = [camVector.x, camVector.y, camVector.z]
let negated = cameraComponents[dominantAxisIndex] < 0
if (dominantAxisIndex === 1) {
// offset of the XZ is being weird, not sure if this is a camera bug
negated = !negated
}
sceneInfra.modelingSend({
type: 'Select sketch plane',
data: {
type: 'offsetPlane',
zAxis,
yAxis,
position: [
planeInfo.origin.x,
planeInfo.origin.y,
planeInfo.origin.z,
].map((num) => num / sceneInfra._baseUnitMultiplier) as [
number,
number,
number,
],
planeId: planeOrFaceId,
pathToNode: artifact.codeRef.pathToNode,
negated,
},
})
return return
} }

View File

@ -483,13 +483,13 @@ export function sketchOnExtrudedFace(
*/ */
export function addOffsetPlane({ export function addOffsetPlane({
node, node,
defaultPlane, plane,
insertIndex, insertIndex,
offset, offset,
planeName, planeName,
}: { }: {
node: Node<Program> node: Node<Program>
defaultPlane: DefaultPlaneStr plane: Node<Literal> | Node<Name> // Can be DefaultPlaneStr or string for offsetPlanes
insertIndex?: number insertIndex?: number
offset: Expr offset: Expr
planeName?: string planeName?: string
@ -500,11 +500,9 @@ export function addOffsetPlane({
const newPlane = createVariableDeclaration( const newPlane = createVariableDeclaration(
newPlaneName, newPlaneName,
createCallExpressionStdLibKw( createCallExpressionStdLibKw('offsetPlane', plane, [
'offsetPlane', createLabeledArg('offset', offset),
createLiteral(defaultPlane.toUpperCase()), ])
[createLabeledArg('offset', offset)]
)
) )
const insertAt = const insertAt =

View File

@ -1,4 +1,4 @@
import { VITE_KC_DEV_TOKEN } from '@src/env' import { VITE_KITTYCAD_API_TOKEN } from '@src/env'
import { createLiteral } from '@src/lang/create' import { createLiteral } from '@src/lang/create'
import type { import type {
@ -40,10 +40,9 @@ import { isOverlap } from '@src/lib/utils'
beforeAll(async () => { beforeAll(async () => {
await initPromise await initPromise
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
await new Promise((resolve) => { await new Promise((resolve) => {
engineCommandManager.start({ engineCommandManager.start({
token: VITE_KC_DEV_TOKEN, token: VITE_KITTYCAD_API_TOKEN,
width: 256, width: 256,
height: 256, height: 256,
setMediaStream: () => {}, setMediaStream: () => {},

View File

@ -4,16 +4,15 @@ import { initPromise } from '@src/lang/wasmUtils'
import { err } from '@src/lib/trap' import { err } from '@src/lib/trap'
import type { Selection } from '@src/lib/selections' import type { Selection } from '@src/lib/selections'
import { engineCommandManager, kclManager } from '@src/lib/singletons' import { engineCommandManager, kclManager } from '@src/lib/singletons'
import { VITE_KC_DEV_TOKEN } from '@src/env' import { VITE_KITTYCAD_API_TOKEN } from '@src/env'
import { modifyAstWithTagsForSelection } from '@src/lang/modifyAst/tagManagement' import { modifyAstWithTagsForSelection } from '@src/lang/modifyAst/tagManagement'
beforeAll(async () => { beforeAll(async () => {
await initPromise await initPromise
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
await new Promise((resolve) => { await new Promise((resolve) => {
engineCommandManager.start({ engineCommandManager.start({
token: VITE_KC_DEV_TOKEN, token: VITE_KITTYCAD_API_TOKEN,
width: 256, width: 256,
height: 256, height: 256,
setMediaStream: () => {}, setMediaStream: () => {},

View File

@ -1,5 +1,5 @@
import type { Models } from '@kittycad/lib' import type { Models } from '@kittycad/lib'
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from '@src/env' import { VITE_KC_API_WS_MODELING_URL, VITE_KITTYCAD_API_TOKEN } from '@src/env'
import { jsAppSettings } from '@src/lib/settings/settingsUtils' import { jsAppSettings } from '@src/lib/settings/settingsUtils'
import { BSON } from 'bson' import { BSON } from 'bson'
@ -400,7 +400,7 @@ class EngineConnection extends EventTarget {
this.send({ this.send({
type: 'headers', type: 'headers',
headers: { headers: {
Authorization: `Bearer ${VITE_KC_DEV_TOKEN}`, Authorization: `Bearer ${VITE_KITTYCAD_API_TOKEN}`,
}, },
}) })
} }

View File

@ -1,4 +1,3 @@
import { VITE_KC_API_BASE_URL } from '@src/env'
import { UAParser } from 'ua-parser-js' import { UAParser } from 'ua-parser-js'
import type { OsInfo } from '@rust/kcl-lib/bindings/OsInfo' import type { OsInfo } from '@rust/kcl-lib/bindings/OsInfo'
@ -11,6 +10,7 @@ import { isDesktop } from '@src/lib/isDesktop'
import type RustContext from '@src/lib/rustContext' import type RustContext from '@src/lib/rustContext'
import screenshot from '@src/lib/screenshot' import screenshot from '@src/lib/screenshot'
import { APP_VERSION } from '@src/routes/utils' import { APP_VERSION } from '@src/routes/utils'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
/* eslint-disable suggest-no-throw/suggest-no-throw -- /* eslint-disable suggest-no-throw/suggest-no-throw --
* All the throws in CoreDumpManager are intentional and should be caught and handled properly * All the throws in CoreDumpManager are intentional and should be caught and handled properly
@ -35,7 +35,7 @@ export class CoreDumpManager {
codeManager: CodeManager codeManager: CodeManager
rustContext: RustContext rustContext: RustContext
token: string | undefined token: string | undefined
baseUrl: string = VITE_KC_API_BASE_URL baseUrl: string = withAPIBaseURL('')
constructor( constructor(
engineCommandManager: EngineCommandManager, engineCommandManager: EngineCommandManager,

View File

@ -26,6 +26,7 @@ import { err } from '@src/lib/trap'
import type { DeepPartial } from '@src/lib/types' import type { DeepPartial } from '@src/lib/types'
import { getInVariableCase } from '@src/lib/utils' import { getInVariableCase } from '@src/lib/utils'
import { IS_STAGING } from '@src/routes/utils' import { IS_STAGING } from '@src/routes/utils'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
export async function renameProjectDirectory( export async function renameProjectDirectory(
projectPath: string, projectPath: string,
@ -697,7 +698,9 @@ export const readTokenFile = async () => {
export const writeTokenFile = async (token: string) => { export const writeTokenFile = async (token: string) => {
const tokenFilePath = await getTokenFilePath() const tokenFilePath = await getTokenFilePath()
if (err(token)) return Promise.reject(token) if (err(token)) return Promise.reject(token)
return window.electron.writeFile(tokenFilePath, token) const result = window.electron.writeFile(tokenFilePath, token)
console.log('token written to disk')
return result
} }
export const writeTelemetryFile = async (content: string) => { export const writeTelemetryFile = async (content: string) => {
@ -722,12 +725,9 @@ export const setState = async (state: Project | undefined): Promise<void> => {
appStateStore = state appStateStore = state
} }
export const getUser = async ( export const getUser = async (token: string): Promise<Models['User_type']> => {
token: string,
hostname: string
): Promise<Models['User_type']> => {
try { try {
const user = await fetch(`${hostname}/users/me`, { const user = await fetch(withAPIBaseURL('/users/me'), {
headers: new Headers({ headers: new Headers({
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}), }),

View File

@ -1,4 +1,4 @@
import { VITE_KC_API_BASE_URL, VITE_KC_SITE_APP_URL } from '@src/env' import { VITE_KC_SITE_APP_URL } from '@src/env'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { stringToBase64 } from '@src/lib/base64' import { stringToBase64 } from '@src/lib/base64'
@ -7,6 +7,7 @@ import {
CREATE_FILE_URL_PARAM, CREATE_FILE_URL_PARAM,
} from '@src/lib/constants' } from '@src/lib/constants'
import { err } from '@src/lib/trap' import { err } from '@src/lib/trap'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
export interface FileLinkParams { export interface FileLinkParams {
code: string code: string
@ -96,7 +97,7 @@ export async function createShortlink(
if (password) { if (password) {
body.password = password body.password = password
} }
const response = await fetch(`${VITE_KC_API_BASE_URL}/user/shortlinks`, { const response = await fetch(withAPIBaseURL('/user/shortlinks'), {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-type': 'application/json', 'Content-type': 'application/json',

View File

@ -1,7 +1,7 @@
import type { SelectionRange } from '@codemirror/state' import type { SelectionRange } from '@codemirror/state'
import { EditorSelection, Transaction } from '@codemirror/state' import { EditorSelection, Transaction } from '@codemirror/state'
import type { Models } from '@kittycad/lib' import type { Models } from '@kittycad/lib'
import { VITE_KC_API_BASE_URL, VITE_KC_SITE_BASE_URL } from '@src/env' import { VITE_KC_SITE_BASE_URL } from '@src/env'
import { diffLines } from 'diff' import { diffLines } from 'diff'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import type { TextToCadMultiFileIteration_type } from '@kittycad/lib/dist/types/src/models' import type { TextToCadMultiFileIteration_type } from '@kittycad/lib/dist/types/src/models'
@ -28,6 +28,7 @@ import { uuidv4 } from '@src/lib/utils'
import type { File as KittyCadLibFile } from '@kittycad/lib/dist/types/src/models' import type { File as KittyCadLibFile } from '@kittycad/lib/dist/types/src/models'
import type { FileMeta } from '@src/lib/types' import type { FileMeta } from '@src/lib/types'
import type { RequestedKCLFile } from '@src/machines/systemIO/utils' import type { RequestedKCLFile } from '@src/machines/systemIO/utils'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
type KclFileMetaMap = { type KclFileMetaMap = {
[execStateFileNamesIndex: number]: Extract<FileMeta, { type: 'kcl' }> [execStateFileNamesIndex: number]: Extract<FileMeta, { type: 'kcl' }>
@ -77,7 +78,7 @@ async function submitTextToCadRequest(
}) })
const response = await fetch( const response = await fetch(
`${VITE_KC_API_BASE_URL}/ml/text-to-cad/multi-file/iteration`, withAPIBaseURL('/ml/text-to-cad/multi-file/iteration'),
{ {
method: 'POST', method: 'POST',
headers: { headers: {
@ -304,7 +305,7 @@ export async function getPromptToEditResult(
id: string, id: string,
token?: string token?: string
): Promise<Models['TextToCadMultiFileIteration_type'] | Error> { ): Promise<Models['TextToCadMultiFileIteration_type'] | Error> {
const url = VITE_KC_API_BASE_URL + '/async/operations/' + id const url = withAPIBaseURL(`/async/operations/${id}`)
const data: Models['TextToCadMultiFileIteration_type'] | Error = const data: Models['TextToCadMultiFileIteration_type'] | Error =
await crossPlatformFetch( await crossPlatformFetch(
url, url,
@ -335,14 +336,6 @@ export async function doPromptEdit({
const toastId = toast.loading('Submitting to Text-to-CAD API...') const toastId = toast.loading('Submitting to Text-to-CAD API...')
let submitResult let submitResult
// work around for @kittycad/lib not really being built for the browser
;(window as any).process = {
env: {
ZOO_API_TOKEN: token,
ZOO_HOST: VITE_KC_API_BASE_URL,
},
}
try { try {
submitResult = await submitPromptToEditToQueue({ submitResult = await submitPromptToEditToQueue({
prompt, prompt,

View File

@ -34,6 +34,7 @@ import {
kclManager, kclManager,
rustContext, rustContext,
sceneEntitiesManager, sceneEntitiesManager,
sceneInfra,
} from '@src/lib/singletons' } from '@src/lib/singletons'
import { err } from '@src/lib/trap' import { err } from '@src/lib/trap'
import { import {
@ -803,3 +804,156 @@ export function getSemanticSelectionType(selectionType: Artifact['type'][]) {
return Array.from(semanticSelectionType) return Array.from(semanticSelectionType)
} }
export function selectDefaultSketchPlane(
defaultPlaneId: string
): Error | boolean {
const defaultPlanes = rustContext.defaultPlanes
if (!defaultPlanes) {
return new Error('No default planes defined in rustContext')
}
if (
![
defaultPlanes.xy,
defaultPlanes.xz,
defaultPlanes.yz,
defaultPlanes.negXy,
defaultPlanes.negXz,
defaultPlanes.negYz,
].includes(defaultPlaneId)
) {
// Supplied defaultPlaneId is not a valid default plane id
return false
}
const camVector = sceneInfra.camControls.camera.position
.clone()
.sub(sceneInfra.camControls.target)
// TODO can we get this information from rust land when it creates the default planes?
// maybe returned from make_default_planes (src/wasm-lib/src/wasm.rs)
let zAxis: [number, number, number] = [0, 0, 1]
let yAxis: [number, number, number] = [0, 1, 0]
if (defaultPlanes?.xy === defaultPlaneId) {
zAxis = [0, 0, 1]
yAxis = [0, 1, 0]
if (camVector.z < 0) {
zAxis = [0, 0, -1]
defaultPlaneId = defaultPlanes?.negXy || ''
}
} else if (defaultPlanes?.yz === defaultPlaneId) {
zAxis = [1, 0, 0]
yAxis = [0, 0, 1]
if (camVector.x < 0) {
zAxis = [-1, 0, 0]
defaultPlaneId = defaultPlanes?.negYz || ''
}
} else if (defaultPlanes?.xz === defaultPlaneId) {
zAxis = [0, 1, 0]
yAxis = [0, 0, 1]
defaultPlaneId = defaultPlanes?.negXz || ''
if (camVector.y < 0) {
zAxis = [0, -1, 0]
defaultPlaneId = defaultPlanes?.xz || ''
}
}
const defaultPlaneStrMap: Record<string, DefaultPlaneStr> = {
[defaultPlanes.xy]: 'XY',
[defaultPlanes.xz]: 'XZ',
[defaultPlanes.yz]: 'YZ',
[defaultPlanes.negXy]: '-XY',
[defaultPlanes.negXz]: '-XZ',
[defaultPlanes.negYz]: '-YZ',
}
sceneInfra.modelingSend({
type: 'Select sketch plane',
data: {
type: 'defaultPlane',
planeId: defaultPlaneId,
plane: defaultPlaneStrMap[defaultPlaneId],
zAxis,
yAxis,
},
})
return true
}
export async function selectOffsetSketchPlane(artifact: Artifact | undefined) {
return new Promise((resolve) => {
if (artifact?.type === 'plane') {
const planeId = artifact.id
void sceneEntitiesManager
.getFaceDetails(planeId)
.then((planeInfo) => {
// Apply camera-based orientation logic similar to default planes
let zAxis: [number, number, number] = [
planeInfo.z_axis.x,
planeInfo.z_axis.y,
planeInfo.z_axis.z,
]
let yAxis: [number, number, number] = [
planeInfo.y_axis.x,
planeInfo.y_axis.y,
planeInfo.y_axis.z,
]
// Get camera vector to determine which side of the plane we're viewing from
const camVector = sceneInfra.camControls.camera.position
.clone()
.sub(sceneInfra.camControls.target)
// Determine the canonical (absolute) plane orientation
const absZAxis: [number, number, number] = [
Math.abs(zAxis[0]),
Math.abs(zAxis[1]),
Math.abs(zAxis[2]),
]
// Find the dominant axis (like default planes do)
const maxComponent = Math.max(...absZAxis)
const dominantAxisIndex = absZAxis.indexOf(maxComponent)
// Check camera position against canonical orientation (like default planes)
const cameraComponents = [camVector.x, camVector.y, camVector.z]
let negated = cameraComponents[dominantAxisIndex] < 0
if (dominantAxisIndex === 1) {
// offset of the XZ is being weird, not sure if this is a camera bug
negated = !negated
}
sceneInfra.modelingSend({
type: 'Select sketch plane',
data: {
type: 'offsetPlane',
zAxis,
yAxis,
position: [
planeInfo.origin.x,
planeInfo.origin.y,
planeInfo.origin.z,
].map((num) => num / sceneInfra._baseUnitMultiplier) as [
number,
number,
number,
],
planeId,
pathToNode: artifact.codeRef.pathToNode,
negated,
},
})
resolve(true)
})
.catch((error) => {
console.error('Error getting face details:', error)
resolve(false)
})
} else {
// selectOffsetSketchPlane called with an invalid artifact type',
resolve(false)
}
})
}

View File

@ -1,4 +1,4 @@
import { VITE_KC_API_BASE_URL } from '@src/env' import { withAPIBaseURL } from '@src/lib/withBaseURL'
import EditorManager from '@src/editor/manager' import EditorManager from '@src/editor/manager'
import { KclManager } from '@src/lang/KclSingleton' import { KclManager } from '@src/lang/KclSingleton'
@ -171,7 +171,7 @@ const appMachine = setup({
systemId: BILLING, systemId: BILLING,
input: { input: {
...BILLING_CONTEXT_DEFAULTS, ...BILLING_CONTEXT_DEFAULTS,
urlUserService: VITE_KC_API_BASE_URL, urlUserService: withAPIBaseURL(''),
}, },
}), }),
], ],

View File

@ -1,5 +1,4 @@
import type { Models } from '@kittycad/lib' import type { Models } from '@kittycad/lib'
import { VITE_KC_API_BASE_URL } from '@src/env'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import type { NavigateFunction } from 'react-router-dom' import type { NavigateFunction } from 'react-router-dom'
import { import {
@ -19,6 +18,7 @@ import { err, reportRejection } from '@src/lib/trap'
import { toSync } from '@src/lib/utils' import { toSync } from '@src/lib/utils'
import { getAllSubDirectoriesAtProjectRoot } from '@src/machines/systemIO/snapshotContext' import { getAllSubDirectoriesAtProjectRoot } from '@src/machines/systemIO/snapshotContext'
import { joinOSPaths } from '@src/lib/paths' import { joinOSPaths } from '@src/lib/paths'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
export async function submitTextToCadPrompt( export async function submitTextToCadPrompt(
prompt: string, prompt: string,
@ -32,7 +32,7 @@ export async function submitTextToCadPrompt(
kcl_version: kclManager.kclVersion, kcl_version: kclManager.kclVersion,
} }
// Glb has a smaller footprint than gltf, should we want to render it. // Glb has a smaller footprint than gltf, should we want to render it.
const url = VITE_KC_API_BASE_URL + '/ai/text-to-cad/glb?kcl=true' const url = withAPIBaseURL('/ai/text-to-cad/glb?kcl=true')
const data: Models['TextToCad_type'] | Error = await crossPlatformFetch( const data: Models['TextToCad_type'] | Error = await crossPlatformFetch(
url, url,
{ {
@ -58,7 +58,7 @@ export async function getTextToCadResult(
id: string, id: string,
token?: string token?: string
): Promise<Models['TextToCad_type'] | Error> { ): Promise<Models['TextToCad_type'] | Error> {
const url = VITE_KC_API_BASE_URL + '/user/text-to-cad/' + id const url = withAPIBaseURL(`/user/text-to-cad/${id}`)
const data: Models['TextToCad_type'] | Error = await crossPlatformFetch( const data: Models['TextToCad_type'] | Error = await crossPlatformFetch(
url, url,
{ {

View File

@ -1,14 +1,13 @@
import type { Models } from '@kittycad/lib/dist/types/src' import type { Models } from '@kittycad/lib/dist/types/src'
import { VITE_KC_API_BASE_URL } from '@src/env'
import crossPlatformFetch from '@src/lib/crossPlatformFetch' import crossPlatformFetch from '@src/lib/crossPlatformFetch'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
export async function sendTelemetry( export async function sendTelemetry(
id: string, id: string,
feedback: Models['MlFeedback_type'], feedback: Models['MlFeedback_type'],
token?: string token?: string
): Promise<void> { ): Promise<void> {
const url = const url = withAPIBaseURL(`/user/text-to-cad/${id}?feedback=${feedback}`)
VITE_KC_API_BASE_URL + '/user/text-to-cad/' + id + '?feedback=' + feedback
await crossPlatformFetch( await crossPlatformFetch(
url, url,
{ {

View File

@ -0,0 +1,34 @@
import { withAPIBaseURL } from '@src/lib/withBaseURL'
describe('withBaseURL', () => {
/**
* running in the development environment
* the .env.development should load
*/
describe('withAPIBaseUrl', () => {
it('should return base url', () => {
const expected = 'https://api.dev.zoo.dev'
const actual = withAPIBaseURL('')
expect(actual).toBe(expected)
})
it('should return base url with /users', () => {
const expected = 'https://api.dev.zoo.dev/users'
const actual = withAPIBaseURL('/users')
expect(actual).toBe(expected)
})
it('should return a longer base url with /oauth2/token/revoke', () => {
const expected = 'https://api.dev.zoo.dev/oauth2/token/revoke'
const actual = withAPIBaseURL('/oauth2/token/revoke')
expect(actual).toBe(expected)
})
it('should ensure base url does not have ending slash', () => {
const expected = 'https://api.dev.zoo.dev'
const actual = withAPIBaseURL('')
expect(actual).toBe(expected)
const expectedEndsWith = expected[expected.length - 1]
const actualEndsWith = actual[actual.length - 1]
expect(actual).toBe(expected)
expect(actualEndsWith).toBe(expectedEndsWith)
})
})
})

View File

@ -1,5 +1,5 @@
import { VITE_KC_API_BASE_URL } from '@src/env' import { VITE_KITTYCAD_API_BASE_URL } from '@src/env'
export default function withBaseUrl(path: string): string { export function withAPIBaseURL(path: string): string {
return VITE_KC_API_BASE_URL + path return VITE_KITTYCAD_API_BASE_URL + path
} }

View File

@ -1,10 +1,5 @@
import type { Models } from '@kittycad/lib' import type { Models } from '@kittycad/lib'
import { import { VITE_KITTYCAD_API_TOKEN } from '@src/env'
DEV,
VITE_KC_API_BASE_URL,
VITE_KC_DEV_TOKEN,
VITE_KC_SKIP_AUTH,
} from '@src/env'
import { assign, fromPromise, setup } from 'xstate' import { assign, fromPromise, setup } from 'xstate'
import { COOKIE_NAME, OAUTH2_DEVICE_CLIENT_ID } from '@src/lib/constants' import { COOKIE_NAME, OAUTH2_DEVICE_CLIENT_ID } from '@src/lib/constants'
@ -15,32 +10,9 @@ import {
} from '@src/lib/desktop' } from '@src/lib/desktop'
import { isDesktop } from '@src/lib/isDesktop' import { isDesktop } from '@src/lib/isDesktop'
import { markOnce } from '@src/lib/performance' import { markOnce } from '@src/lib/performance'
import { import { withAPIBaseURL } from '@src/lib/withBaseURL'
default as withBaseURL,
default as withBaseUrl,
} from '@src/lib/withBaseURL'
import { ACTOR_IDS } from '@src/machines/machineConstants' import { ACTOR_IDS } from '@src/machines/machineConstants'
const SKIP_AUTH = VITE_KC_SKIP_AUTH === 'true' && DEV
const LOCAL_USER: Models['User_type'] = {
id: '8675309',
name: 'Test User',
email: 'kittycad.sidebar.test@example.com',
image: 'https://placekitten.com/200/200',
created_at: 'yesteryear',
updated_at: 'today',
company: 'Test Company',
discord: 'Test User#1234',
github: 'testuser',
phone: '555-555-5555',
first_name: 'Test',
last_name: 'User',
can_train_on_data: false,
is_service_account: false,
deletion_scheduled: false,
}
export interface UserContext { export interface UserContext {
user?: Models['User_type'] user?: Models['User_type']
token: string token: string
@ -56,11 +28,21 @@ export type Events =
} }
export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY' export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY'
/**
* Determine which token do we have persisted to initialize the auth machine
*/
const persistedCookie = getCookie(COOKIE_NAME)
const persistedLocalStorage = localStorage?.getItem(TOKEN_PERSIST_KEY) || ''
const persistedDevToken = VITE_KITTYCAD_API_TOKEN
export const persistedToken = export const persistedToken =
VITE_KC_DEV_TOKEN || persistedDevToken || persistedCookie || persistedLocalStorage
getCookie(COOKIE_NAME) || console.log('Initial persisted token')
localStorage?.getItem(TOKEN_PERSIST_KEY) || console.table([
'' ['cookie', !!persistedCookie],
['local storage', !!persistedLocalStorage],
['api token', !!persistedDevToken],
])
export const authMachine = setup({ export const authMachine = setup({
types: {} as { types: {} as {
@ -157,7 +139,7 @@ export const authMachine = setup({
async function getUser(input: { token?: string }) { async function getUser(input: { token?: string }) {
const token = await getAndSyncStoredToken(input) const token = await getAndSyncStoredToken(input)
const url = withBaseURL('/user') const url = withAPIBaseURL('/user')
const headers: { [key: string]: string } = { const headers: { [key: string]: string } = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
} }
@ -165,21 +147,8 @@ async function getUser(input: { token?: string }) {
if (!token && isDesktop()) return Promise.reject(new Error('No token found')) if (!token && isDesktop()) return Promise.reject(new Error('No token found'))
if (token) headers['Authorization'] = `Bearer ${token}` if (token) headers['Authorization'] = `Bearer ${token}`
if (SKIP_AUTH) {
// For local tests
if (localStorage.getItem('FORCE_NO_IMAGE')) {
LOCAL_USER.image = ''
}
markOnce('code/didAuth')
return {
user: LOCAL_USER,
token,
}
}
const userPromise = isDesktop() const userPromise = isDesktop()
? getUserDesktop(token, VITE_KC_API_BASE_URL) ? getUserDesktop(token)
: fetch(url, { : fetch(url, {
method: 'GET', method: 'GET',
credentials: 'include', credentials: 'include',
@ -228,16 +197,28 @@ async function getAndSyncStoredToken(input: {
token?: string token?: string
}): Promise<string> { }): Promise<string> {
// dev mode // dev mode
if (VITE_KC_DEV_TOKEN) return VITE_KC_DEV_TOKEN if (VITE_KITTYCAD_API_TOKEN) {
console.log('Token used for authentication')
console.table([['api token', !!VITE_KITTYCAD_API_TOKEN]])
return VITE_KITTYCAD_API_TOKEN
}
const token = const inputToken = input.token && input.token !== '' ? input.token : ''
input.token && input.token !== '' const cookieToken = getCookie(COOKIE_NAME)
? input.token const localStorageToken = localStorage?.getItem(TOKEN_PERSIST_KEY) || ''
: getCookie(COOKIE_NAME) || localStorage?.getItem(TOKEN_PERSIST_KEY) || '' const token = inputToken || cookieToken || localStorageToken
console.log('Token used for authentication')
console.table([
['persisted token', !!inputToken],
['cookie', !!cookieToken],
['local storage', !!localStorageToken],
['api token', !!VITE_KITTYCAD_API_TOKEN],
])
if (token) { if (token) {
// has just logged in, update storage
localStorage.setItem(TOKEN_PERSIST_KEY, token)
if (isDesktop()) { if (isDesktop()) {
// has just logged in, update storage
localStorage.setItem(TOKEN_PERSIST_KEY, token)
await writeTokenFile(token) await writeTokenFile(token)
} }
return token return token
@ -259,7 +240,7 @@ async function logout() {
if (token) { if (token) {
try { try {
await fetch(withBaseUrl('/oauth2/token/revoke'), { await fetch(withAPIBaseURL('/oauth2/token/revoke'), {
method: 'POST', method: 'POST',
credentials: 'include', credentials: 'include',
headers: { headers: {
@ -282,7 +263,7 @@ async function logout() {
} }
} }
return fetch(withBaseUrl('/logout'), { return fetch(withAPIBaseURL('/logout'), {
method: 'POST', method: 'POST',
credentials: 'include', credentials: 'include',
}) })

View File

@ -11,7 +11,7 @@ import {
engineCommandManager, engineCommandManager,
kclManager, kclManager,
} from '@src/lib/singletons' } from '@src/lib/singletons'
import { VITE_KC_DEV_TOKEN } from '@src/env' import { VITE_KITTYCAD_API_TOKEN } from '@src/env'
import { getConstraintInfoKw } from '@src/lang/std/sketch' import { getConstraintInfoKw } from '@src/lang/std/sketch'
import { getNodeFromPath } from '@src/lang/queryAst' import { getNodeFromPath } from '@src/lang/queryAst'
import type { Node } from '@rust/kcl-lib/bindings/Node' import type { Node } from '@rust/kcl-lib/bindings/Node'
@ -29,10 +29,9 @@ import { removeSingleConstraintInfo } from '@src/lang/modifyAst'
beforeAll(async () => { beforeAll(async () => {
await initPromise await initPromise
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
await new Promise((resolve) => { await new Promise((resolve) => {
engineCommandManager.start({ engineCommandManager.start({
token: VITE_KC_DEV_TOKEN, token: VITE_KITTYCAD_API_TOKEN,
width: 256, width: 256,
height: 256, height: 256,
setMediaStream: () => {}, setMediaStream: () => {},

View File

@ -2609,15 +2609,15 @@ export const modelingMachine = setup({
insertIndex = nodeToEdit[1][0] insertIndex = nodeToEdit[1][0]
} }
// Extract the default plane from selection const selectedPlane = getSelectedPlane(selection)
const plane = selection.otherSelections[0] if (!selectedPlane) {
if (!(plane && plane instanceof Object && 'name' in plane))
return trap('No plane selected') return trap('No plane selected')
}
// Get the default plane name from the selection // Get the default plane name from the selection
const offsetPlaneResult = addOffsetPlane({ const offsetPlaneResult = addOffsetPlane({
node: ast, node: ast,
defaultPlane: plane.name, plane: selectedPlane,
offset: offset:
'variableName' in distance 'variableName' in distance
? distance.variableIdentifierAst ? distance.variableIdentifierAst
@ -5520,6 +5520,33 @@ export function isEditingExistingSketch({
return (hasStartProfileAt && maybePipeExpression.body.length > 1) || hasCircle return (hasStartProfileAt && maybePipeExpression.body.length > 1) || hasCircle
} }
const getSelectedPlane = (
selection: Selections
): Node<Name> | Node<Literal> | undefined => {
const defaultPlane = selection.otherSelections[0]
if (
defaultPlane &&
defaultPlane instanceof Object &&
'name' in defaultPlane
) {
return createLiteral(defaultPlane.name.toUpperCase())
}
const offsetPlane = selection.graphSelections[0]
if (offsetPlane.artifact?.type === 'plane') {
const artifactId = offsetPlane.artifact?.id
const variableName = Object.entries(kclManager.variables).find(
([_, value]) => {
return value?.type === 'Plane' && value.value?.artifactId === artifactId
}
)
const offsetPlaneName = variableName?.[0]
return offsetPlaneName ? createLocalName(offsetPlaneName) : undefined
}
return undefined
}
export function pipeHasCircle({ export function pipeHasCircle({
sketchDetails, sketchDetails,
}: { }: {

View File

@ -1,5 +1,5 @@
import { engineCommandManager, kclManager } from '@src/lib/singletons' import { engineCommandManager, kclManager } from '@src/lib/singletons'
import { VITE_KC_DEV_TOKEN } from '@src/env' import { VITE_KITTYCAD_API_TOKEN } from '@src/env'
import { getModuleIdByFileName, isArray } from '@src/lib/utils' import { getModuleIdByFileName, isArray } from '@src/lib/utils'
import { vi, inject } from 'vitest' import { vi, inject } from 'vitest'
import { assertParse } from '@src/lang/wasm' import { assertParse } from '@src/lang/wasm'
@ -355,10 +355,9 @@ cases.push(
beforeAll(async () => { beforeAll(async () => {
await initPromise await initPromise
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
await new Promise((resolve) => { await new Promise((resolve) => {
engineCommandManager.start({ engineCommandManager.start({
token: VITE_KC_DEV_TOKEN, token: VITE_KITTYCAD_API_TOKEN,
width: 256, width: 256,
height: 256, height: 256,
setMediaStream: () => {}, setMediaStream: () => {},

View File

@ -70,12 +70,10 @@ dotenv.config({ path: [`.env.${NODE_ENV}.local`, `.env.${NODE_ENV}`] })
// default vite values based on mode // default vite values based on mode
process.env.NODE_ENV ??= viteEnv.MODE process.env.NODE_ENV ??= viteEnv.MODE
process.env.BASE_URL ??= viteEnv.VITE_KC_API_BASE_URL
process.env.VITE_KC_API_WS_MODELING_URL ??= viteEnv.VITE_KC_API_WS_MODELING_URL process.env.VITE_KC_API_WS_MODELING_URL ??= viteEnv.VITE_KC_API_WS_MODELING_URL
process.env.VITE_KC_API_BASE_URL ??= viteEnv.VITE_KC_API_BASE_URL process.env.VITE_KITTYCAD_API_BASE_URL ??= viteEnv.VITE_KITTYCAD_API_BASE_URL
process.env.VITE_KC_SITE_BASE_URL ??= viteEnv.VITE_KC_SITE_BASE_URL process.env.VITE_KC_SITE_BASE_URL ??= viteEnv.VITE_KC_SITE_BASE_URL
process.env.VITE_KC_SITE_APP_URL ??= viteEnv.VITE_KC_SITE_APP_URL process.env.VITE_KC_SITE_APP_URL ??= viteEnv.VITE_KC_SITE_APP_URL
process.env.VITE_KC_SKIP_AUTH ??= viteEnv.VITE_KC_SKIP_AUTH
process.env.VITE_KC_CONNECTION_TIMEOUT_MS ??= process.env.VITE_KC_CONNECTION_TIMEOUT_MS ??=
viteEnv.VITE_KC_CONNECTION_TIMEOUT_MS viteEnv.VITE_KC_CONNECTION_TIMEOUT_MS

View File

@ -289,12 +289,11 @@ contextBridge.exposeInMainWorld('electron', {
exposeProcessEnvs([ exposeProcessEnvs([
'NODE_ENV', 'NODE_ENV',
'VITE_KC_API_WS_MODELING_URL', 'VITE_KC_API_WS_MODELING_URL',
'VITE_KC_API_BASE_URL', 'VITE_KITTYCAD_API_BASE_URL',
'VITE_KC_SITE_BASE_URL', 'VITE_KC_SITE_BASE_URL',
'VITE_KC_SITE_APP_URL', 'VITE_KC_SITE_APP_URL',
'VITE_KC_SKIP_AUTH',
'VITE_KC_CONNECTION_TIMEOUT_MS', 'VITE_KC_CONNECTION_TIMEOUT_MS',
'VITE_KC_DEV_TOKEN', 'VITE_KITTYCAD_API_TOKEN',
'IS_PLAYWRIGHT', 'IS_PLAYWRIGHT',

View File

@ -6,7 +6,7 @@ import { Link } from 'react-router-dom'
import { ActionButton } from '@src/components/ActionButton' import { ActionButton } from '@src/components/ActionButton'
import { CustomIcon } from '@src/components/CustomIcon' import { CustomIcon } from '@src/components/CustomIcon'
import { Logo } from '@src/components/Logo' import { Logo } from '@src/components/Logo'
import { VITE_KC_API_BASE_URL, VITE_KC_SITE_BASE_URL } from '@src/env' import { VITE_KC_SITE_BASE_URL } from '@src/env'
import { APP_NAME } from '@src/lib/constants' import { APP_NAME } from '@src/lib/constants'
import { isDesktop } from '@src/lib/isDesktop' import { isDesktop } from '@src/lib/isDesktop'
import { openExternalBrowserIfDesktop } from '@src/lib/openWindow' import { openExternalBrowserIfDesktop } from '@src/lib/openWindow'
@ -15,6 +15,7 @@ import { reportRejection } from '@src/lib/trap'
import { toSync } from '@src/lib/utils' import { toSync } from '@src/lib/utils'
import { authActor, useSettings } from '@src/lib/singletons' import { authActor, useSettings } from '@src/lib/singletons'
import { APP_VERSION, generateSignInUrl } from '@src/routes/utils' import { APP_VERSION, generateSignInUrl } from '@src/routes/utils'
import { withAPIBaseURL } from '@src/lib/withBaseURL'
const subtleBorder = const subtleBorder =
'border border-solid border-chalkboard-30 dark:border-chalkboard-80' 'border border-solid border-chalkboard-30 dark:border-chalkboard-80'
@ -54,7 +55,7 @@ const SignIn = () => {
const signInDesktop = async () => { const signInDesktop = async () => {
// We want to invoke our command to login via device auth. // We want to invoke our command to login via device auth.
const userCodeToDisplay = await window.electron const userCodeToDisplay = await window.electron
.startDeviceFlow(VITE_KC_API_BASE_URL + location.search) .startDeviceFlow(withAPIBaseURL(location.search))
.catch(reportError) .catch(reportError)
if (!userCodeToDisplay) { if (!userCodeToDisplay) {
console.error('No user code received while trying to log in') console.error('No user code received while trying to log in')