web based infinite canvas

feat: geometry (hit testing/picking)

+2003 -181
+1
.gitignore
··· 2 2 dist 3 3 *.log 4 4 .DS_Store 5 + coverage
+9 -10
TODO.txt
··· 194 194 Goal: determine what the cursor is over. 195 195 196 196 Geometry (/packages/core/src/geom): 197 - [ ] shapeBounds(shape) -> Box2 198 - [ ] pointInRect(p, rectShape) -> bool 199 - [ ] pointInEllipse(p, ellipseShape) -> bool 200 - [ ] pointNearSegment(p, a, b, tolerance) -> bool 201 - [ ] hitTestPoint(state, worldPoint) -> shapeId? (topmost wins) 197 + [x] shapeBounds(shape) -> Box2 198 + [x] pointInRect(p, rectShape) -> bool 199 + [x] pointInEllipse(p, ellipseShape) -> bool 200 + [x] pointNearSegment(p, a, b, tolerance) -> bool 201 + [x] hitTestPoint(state, worldPoint) -> shapeId? (topmost wins) 202 202 203 203 Layering: 204 - [ ] Define draw order = page.shapeIds order 205 - [ ] hitTest uses reverse order for topmost selection 204 + [x] Define draw order = page.shapeIds order 205 + [x] hitTest uses reverse order for topmost selection 206 206 207 207 Tests: 208 - [ ] hitTestPoint returns expected shapeId for overlapping shapes 209 - [ ] tolerance works for line/arrow selection 208 + [x] hitTestPoint returns expected shapeId for overlapping shapes 209 + [x] tolerance works for line/arrow selection 210 210 211 211 (DoD): 212 212 - You can hover shapes and log the hit shape id (no selection yet). 213 - 214 213 215 214 ============================================================================== 216 215 7. Milestone G: Input system (pointer + keyboard) *wb-G*
apps/.gitkeep

This is a binary file and will not be displayed.

+4
apps/web/.prettierignore
··· 7 7 8 8 # Miscellaneous 9 9 /static/ 10 + 11 + # TS covered by dprint 12 + **/*.ts 13 + **/*.js
-38
apps/web/README.md
··· 1 - # sv 2 - 3 - Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). 4 - 5 - ## Creating a project 6 - 7 - If you're seeing this, you've probably already done this step. Congrats! 8 - 9 - ```sh 10 - # create a new project in the current directory 11 - npx sv create 12 - 13 - # create a new project in my-app 14 - npx sv create my-app 15 - ``` 16 - 17 - ## Developing 18 - 19 - Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 20 - 21 - ```sh 22 - npm run dev 23 - 24 - # or start the server and open the app in a new browser tab 25 - npm run dev -- --open 26 - ``` 27 - 28 - ## Building 29 - 30 - To create a production version of your app: 31 - 32 - ```sh 33 - npm run build 34 - ``` 35 - 36 - You can preview the production build with `npm run preview`. 37 - 38 - > To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
+30 -36
apps/web/eslint.config.js
··· 1 - import prettier from 'eslint-config-prettier'; 2 - import { fileURLToPath } from 'node:url'; 3 - import { includeIgnoreFile } from '@eslint/compat'; 4 - import js from '@eslint/js'; 5 - import svelte from 'eslint-plugin-svelte'; 6 - import { defineConfig } from 'eslint/config'; 7 - import globals from 'globals'; 8 - import ts from 'typescript-eslint'; 9 - import svelteConfig from './svelte.config.js'; 1 + import { includeIgnoreFile } from "@eslint/compat"; 2 + import js from "@eslint/js"; 3 + import prettier from "eslint-config-prettier"; 4 + import svelte from "eslint-plugin-svelte"; 5 + import { defineConfig } from "eslint/config"; 6 + import globals from "globals"; 7 + import { fileURLToPath } from "node:url"; 8 + import ts from "typescript-eslint"; 9 + import svelteConfig from "./svelte.config.js"; 10 10 11 - const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); 11 + const gitignorePath = fileURLToPath(new URL("./.gitignore", import.meta.url)); 12 12 13 13 export default defineConfig( 14 - includeIgnoreFile(gitignorePath), 15 - js.configs.recommended, 16 - ...ts.configs.recommended, 17 - ...svelte.configs.recommended, 18 - prettier, 19 - ...svelte.configs.prettier, 20 - { 21 - languageOptions: { globals: { ...globals.browser, ...globals.node } }, 22 - 23 - rules: { 24 - // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects. 25 - // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors 26 - "no-undef": 'off' 27 - } 28 - }, 29 - { 30 - files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], 14 + includeIgnoreFile(gitignorePath), 15 + js.configs.recommended, 16 + ...ts.configs.recommended, 17 + ...svelte.configs.recommended, 18 + prettier, 19 + ...svelte.configs.prettier, 20 + { 21 + languageOptions: { globals: { ...globals.browser, ...globals.node } }, 31 22 32 - languageOptions: { 33 - parserOptions: { 34 - projectService: true, 35 - extraFileExtensions: ['.svelte'], 36 - parser: ts.parser, 37 - svelteConfig 38 - } 39 - } 40 - } 23 + rules: { 24 + // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects. 25 + // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors 26 + "no-undef": "off", 27 + }, 28 + }, 29 + { 30 + files: ["**/*.svelte", "**/*.svelte.ts", "**/*.svelte.js"], 31 + languageOptions: { 32 + parserOptions: { projectService: true, extraFileExtensions: [".svelte"], parser: ts.parser, svelteConfig }, 33 + }, 34 + }, 41 35 );
+40 -40
apps/web/package.json
··· 1 1 { 2 - "name": "web", 3 - "private": true, 4 - "version": "0.0.1", 5 - "type": "module", 6 - "scripts": { 7 - "dev": "vite dev", 8 - "build": "vite build", 9 - "preview": "vite preview", 10 - "prepare": "svelte-kit sync || echo ''", 11 - "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 12 - "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 13 - "lint": "eslint . && prettier --check .", 14 - "test:unit": "vitest", 15 - "test": "npm run test:unit -- --run", 16 - "format": "prettier --write ." 17 - }, 18 - "devDependencies": { 19 - "@eslint/compat": "^1.4.0", 20 - "@eslint/js": "^9.39.1", 21 - "@sveltejs/adapter-static": "^3.0.10", 22 - "@sveltejs/kit": "^2.49.1", 23 - "@sveltejs/vite-plugin-svelte": "^6.2.1", 24 - "@types/node": "^24", 25 - "@vitest/browser-playwright": "^4.0.15", 26 - "eslint": "^9.39.1", 27 - "eslint-config-prettier": "^10.1.8", 28 - "eslint-plugin-svelte": "^3.13.1", 29 - "globals": "^16.5.0", 30 - "playwright": "^1.57.0", 31 - "prettier": "^3.7.4", 32 - "prettier-plugin-svelte": "^3.4.0", 33 - "svelte": "^5.45.6", 34 - "svelte-check": "^4.3.4", 35 - "typescript": "^5.9.3", 36 - "typescript-eslint": "^8.48.1", 37 - "vite": "^7.2.6", 38 - "vite-plugin-devtools-json": "^1.0.0", 39 - "vitest": "^4.0.15", 40 - "vitest-browser-svelte": "^2.0.1" 41 - } 2 + "name": "inkfinite-web", 3 + "private": true, 4 + "version": "0.0.1", 5 + "type": "module", 6 + "scripts": { 7 + "dev": "vite dev", 8 + "build": "vite build", 9 + "preview": "vite preview", 10 + "prepare": "svelte-kit sync || echo ''", 11 + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 12 + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 13 + "lint": "eslint . && prettier --check .", 14 + "test:unit": "vitest", 15 + "test": "npm run test:unit -- --run", 16 + "format": "prettier --write ." 17 + }, 18 + "devDependencies": { 19 + "@eslint/compat": "^1.4.0", 20 + "@eslint/js": "^9.39.1", 21 + "@sveltejs/adapter-static": "^3.0.10", 22 + "@sveltejs/kit": "^2.49.1", 23 + "@sveltejs/vite-plugin-svelte": "^6.2.1", 24 + "@types/node": "^24", 25 + "@vitest/browser-playwright": "^4.0.15", 26 + "eslint": "^9.39.1", 27 + "eslint-config-prettier": "^10.1.8", 28 + "eslint-plugin-svelte": "^3.13.1", 29 + "globals": "^16.5.0", 30 + "playwright": "^1.57.0", 31 + "prettier": "^3.7.4", 32 + "prettier-plugin-svelte": "^3.4.0", 33 + "svelte": "^5.45.6", 34 + "svelte-check": "^4.3.4", 35 + "typescript": "^5.9.3", 36 + "typescript-eslint": "^8.48.1", 37 + "vite": "^7.2.6", 38 + "vite-plugin-devtools-json": "^1.0.0", 39 + "vitest": "^4.0.15", 40 + "vitest-browser-svelte": "^2.0.1" 41 + } 42 42 }
+18 -18
apps/web/tsconfig.json
··· 1 1 { 2 - "extends": "./.svelte-kit/tsconfig.json", 3 - "compilerOptions": { 4 - "rewriteRelativeImportExtensions": true, 5 - "allowJs": true, 6 - "checkJs": true, 7 - "esModuleInterop": true, 8 - "forceConsistentCasingInFileNames": true, 9 - "resolveJsonModule": true, 10 - "skipLibCheck": true, 11 - "sourceMap": true, 12 - "strict": true, 13 - "moduleResolution": "bundler" 14 - } 15 - // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias 16 - // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files 17 - // 18 - // To make changes to top-level options such as include and exclude, we recommend extending 19 - // the generated config; see https://svelte.dev/docs/kit/configuration#typescript 2 + "extends": "./.svelte-kit/tsconfig.json", 3 + "compilerOptions": { 4 + "rewriteRelativeImportExtensions": true, 5 + "allowJs": true, 6 + "checkJs": true, 7 + "esModuleInterop": true, 8 + "forceConsistentCasingInFileNames": true, 9 + "resolveJsonModule": true, 10 + "skipLibCheck": true, 11 + "sourceMap": true, 12 + "strict": true, 13 + "moduleResolution": "bundler" 14 + } 15 + // Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias 16 + // except $lib which is handled by https://svelte.dev/docs/kit/configuration#files 17 + // 18 + // To make changes to top-level options such as include and exclude, we recommend extending 19 + // the generated config; see https://svelte.dev/docs/kit/configuration#typescript 20 20 }
+2 -7
package.json
··· 3 3 "version": "1.0.0", 4 4 "private": true, 5 5 "type": "module", 6 - "workspaces": [ 7 - "packages/*" 8 - ], 6 + "workspaces": ["packages/*", "apps/*"], 9 7 "scripts": {}, 10 8 "devDependencies": { 11 9 "@eslint/js": "^9.39.2", ··· 16 14 "typescript": "^5.9.3", 17 15 "typescript-eslint": "^8.50.0" 18 16 }, 19 - "engines": { 20 - "node": ">=18.0.0", 21 - "pnpm": ">=8.0.0" 22 - }, 17 + "engines": { "node": ">=18.0.0", "pnpm": ">=8.0.0" }, 23 18 "packageManager": "pnpm@10.26.1" 24 19 }
+6 -18
packages/core/package.json
··· 6 6 "author": "Author Name <author.name@mail.com>", 7 7 "license": "MIT", 8 8 "homepage": "https://github.com/author/library#readme", 9 - "repository": { 10 - "type": "git", 11 - "url": "git+https://github.com/author/library.git" 12 - }, 13 - "bugs": { 14 - "url": "https://github.com/author/library/issues" 15 - }, 16 - "exports": { 17 - ".": "./dist/index.mjs", 18 - "./package.json": "./package.json" 19 - }, 9 + "repository": { "type": "git", "url": "git+https://github.com/author/library.git" }, 10 + "bugs": { "url": "https://github.com/author/library/issues" }, 11 + "exports": { ".": "./dist/index.mjs", "./package.json": "./package.json" }, 20 12 "main": "./dist/index.mjs", 21 13 "module": "./dist/index.mjs", 22 14 "types": "./dist/index.d.mts", 23 - "files": [ 24 - "dist" 25 - ], 15 + "files": ["dist"], 26 16 "scripts": { 27 17 "build": "tsdown", 28 18 "dev": "tsdown --watch", ··· 32 22 }, 33 23 "devDependencies": { 34 24 "@types/node": "^25.0.3", 25 + "@vitest/coverage-v8": "^4.0.16", 35 26 "bumpp": "^10.3.2", 36 27 "tsdown": "^0.18.1", 37 28 "typescript": "^5.9.3", 38 29 "vitest": "^4.0.16" 39 30 }, 40 - "dependencies": { 41 - "rxjs": "^7.8.2", 42 - "uuid": "^13.0.0" 43 - } 31 + "dependencies": { "rxjs": "^7.8.2", "uuid": "^13.0.0" } 44 32 }
+300
packages/core/src/geom.ts
··· 1 + import type { Box2, Vec2 } from "./math"; 2 + import { Box2 as Box2Ops, Vec2 as Vec2Ops } from "./math"; 3 + import type { ArrowShape, EllipseShape, LineShape, RectShape, ShapeRecord, TextShape } from "./model"; 4 + import type { EditorState } from "./reactivity"; 5 + import { getShapesOnCurrentPage } from "./reactivity"; 6 + 7 + /** 8 + * Get the axis-aligned bounding box of a shape in world coordinates 9 + * 10 + * For shapes with rotation, this returns the bounding box of the rotated shape 11 + * (not the minimal bounding box of the original shape) 12 + * 13 + * @param shape - The shape to get bounds for 14 + * @returns Bounding box in world coordinates 15 + */ 16 + export function shapeBounds(shape: ShapeRecord): Box2 { 17 + switch (shape.type) { 18 + case "rect": { 19 + return rectBounds(shape); 20 + } 21 + case "ellipse": { 22 + return ellipseBounds(shape); 23 + } 24 + case "line": { 25 + return lineBounds(shape); 26 + } 27 + case "arrow": { 28 + return arrowBounds(shape); 29 + } 30 + case "text": { 31 + return textBounds(shape); 32 + } 33 + } 34 + } 35 + 36 + /** 37 + * Get bounds for a rectangle shape 38 + */ 39 + function rectBounds(shape: RectShape): Box2 { 40 + const { w, h } = shape.props; 41 + const { x, y, rot } = shape; 42 + 43 + if (rot === 0) { 44 + return Box2Ops.create(x, y, x + w, y + h); 45 + } 46 + 47 + const corners = [{ x: 0, y: 0 }, { x: w, y: 0 }, { x: w, y: h }, { x: 0, y: h }]; 48 + const rotatedCorners = corners.map((corner) => rotatePoint(corner, rot)); 49 + const translatedCorners = rotatedCorners.map((corner) => ({ x: corner.x + x, y: corner.y + y })); 50 + return Box2Ops.fromPoints(translatedCorners); 51 + } 52 + 53 + /** 54 + * Get bounds for an ellipse shape 55 + */ 56 + function ellipseBounds(shape: EllipseShape): Box2 { 57 + const { w, h } = shape.props; 58 + const { x, y, rot } = shape; 59 + 60 + if (rot === 0) { 61 + return Box2Ops.create(x, y, x + w, y + h); 62 + } 63 + 64 + const corners = [{ x: 0, y: 0 }, { x: w, y: 0 }, { x: w, y: h }, { x: 0, y: h }]; 65 + const rotatedCorners = corners.map((corner) => rotatePoint(corner, rot)); 66 + const translatedCorners = rotatedCorners.map((corner) => ({ x: corner.x + x, y: corner.y + y })); 67 + return Box2Ops.fromPoints(translatedCorners); 68 + } 69 + 70 + /** 71 + * Get bounds for a line shape 72 + */ 73 + function lineBounds(shape: LineShape): Box2 { 74 + const { a, b } = shape.props; 75 + const { x, y, rot } = shape; 76 + 77 + const points = [a, b]; 78 + 79 + if (rot === 0) { 80 + const translatedPoints = points.map((p) => ({ x: p.x + x, y: p.y + y })); 81 + return Box2Ops.fromPoints(translatedPoints); 82 + } 83 + 84 + const rotatedPoints = points.map((p) => rotatePoint(p, rot)); 85 + const translatedPoints = rotatedPoints.map((p) => ({ x: p.x + x, y: p.y + y })); 86 + return Box2Ops.fromPoints(translatedPoints); 87 + } 88 + 89 + /** 90 + * Get bounds for an arrow shape 91 + */ 92 + function arrowBounds(shape: ArrowShape): Box2 { 93 + const { a, b } = shape.props; 94 + const { x, y, rot } = shape; 95 + 96 + const points = [a, b]; 97 + 98 + if (rot === 0) { 99 + const translatedPoints = points.map((p) => ({ x: p.x + x, y: p.y + y })); 100 + return Box2Ops.fromPoints(translatedPoints); 101 + } 102 + 103 + const rotatedPoints = points.map((p) => rotatePoint(p, rot)); 104 + const translatedPoints = rotatedPoints.map((p) => ({ x: p.x + x, y: p.y + y })); 105 + return Box2Ops.fromPoints(translatedPoints); 106 + } 107 + 108 + /** 109 + * Get bounds for a text shape 110 + */ 111 + function textBounds(shape: TextShape): Box2 { 112 + const { fontSize, w } = shape.props; 113 + const { x, y, rot } = shape; 114 + 115 + const width = w ?? fontSize * 10; 116 + const height = fontSize * 1.2; 117 + 118 + if (rot === 0) { 119 + return Box2Ops.create(x, y, x + width, y + height); 120 + } 121 + 122 + const corners = [{ x: 0, y: 0 }, { x: width, y: 0 }, { x: width, y: height }, { x: 0, y: height }]; 123 + 124 + const rotatedCorners = corners.map((corner) => rotatePoint(corner, rot)); 125 + const translatedCorners = rotatedCorners.map((corner) => ({ x: corner.x + x, y: corner.y + y })); 126 + return Box2Ops.fromPoints(translatedCorners); 127 + } 128 + 129 + /** 130 + * Rotate a point around the origin 131 + * 132 + * @param p - Point to rotate 133 + * @param theta - Rotation angle in radians 134 + * @returns Rotated point 135 + */ 136 + function rotatePoint(p: Vec2, theta: number): Vec2 { 137 + const cos = Math.cos(theta); 138 + const sin = Math.sin(theta); 139 + return { x: p.x * cos - p.y * sin, y: p.x * sin + p.y * cos }; 140 + } 141 + 142 + /** 143 + * Check if a point is inside a rectangle shape 144 + * 145 + * @param p - Point in world coordinates 146 + * @param shape - Rectangle shape 147 + * @returns True if point is inside the rectangle 148 + */ 149 + export function pointInRect(p: Vec2, shape: RectShape): boolean { 150 + const { x, y, rot } = shape; 151 + const { w, h } = shape.props; 152 + const localP = worldToLocal(p, x, y, rot); 153 + return localP.x >= 0 && localP.x <= w && localP.y >= 0 && localP.y <= h; 154 + } 155 + 156 + /** 157 + * Check if a point is inside an ellipse shape 158 + * 159 + * @param p - Point in world coordinates 160 + * @param shape - Ellipse shape 161 + * @returns True if point is inside the ellipse 162 + */ 163 + export function pointInEllipse(p: Vec2, shape: EllipseShape): boolean { 164 + const { x, y, rot } = shape; 165 + const { w, h } = shape.props; 166 + 167 + const localP = worldToLocal(p, x, y, rot); 168 + 169 + const centerX = w / 2; 170 + const centerY = h / 2; 171 + const radiusX = w / 2; 172 + const radiusY = h / 2; 173 + 174 + const dx = localP.x - centerX; 175 + const dy = localP.y - centerY; 176 + return (dx * dx) / (radiusX * radiusX) + (dy * dy) / (radiusY * radiusY) <= 1; 177 + } 178 + 179 + /** 180 + * Check if a point is near a line segment 181 + * 182 + * @param p - Point to test 183 + * @param a - Start point of segment 184 + * @param b - End point of segment 185 + * @param tolerance - Maximum distance from segment to be considered "near" 186 + * @returns True if point is within tolerance distance of the segment 187 + */ 188 + export function pointNearSegment(p: Vec2, a: Vec2, b: Vec2, tolerance: number): boolean { 189 + const ab = Vec2Ops.sub(b, a); 190 + const ap = Vec2Ops.sub(p, a); 191 + const abLengthSq = Vec2Ops.lenSq(ab); 192 + 193 + if (abLengthSq === 0) { 194 + return Vec2Ops.dist(p, a) <= tolerance; 195 + } 196 + 197 + const t = Math.max(0, Math.min(1, Vec2Ops.dot(ap, ab) / abLengthSq)); 198 + const projection = Vec2Ops.add(a, Vec2Ops.mulScalar(ab, t)); 199 + const distance = Vec2Ops.dist(p, projection); 200 + return distance <= tolerance; 201 + } 202 + 203 + /** 204 + * Check if a point is near a line or arrow shape 205 + * 206 + * @param p - Point in world coordinates 207 + * @param shape - Line or arrow shape 208 + * @param tolerance - Maximum distance from line to be considered a hit 209 + * @returns True if point is near the line 210 + */ 211 + export function pointNearLine(p: Vec2, shape: LineShape | ArrowShape, tolerance = 5): boolean { 212 + const { x, y, rot } = shape; 213 + const { a, b } = shape.props; 214 + const localP = worldToLocal(p, x, y, rot); 215 + return pointNearSegment(localP, a, b, tolerance); 216 + } 217 + 218 + /** 219 + * Check if a point is inside a text shape 220 + * 221 + * @param p - Point in world coordinates 222 + * @param shape - Text shape 223 + * @returns True if point is inside the text bounds 224 + */ 225 + export function pointInText(p: Vec2, shape: TextShape): boolean { 226 + const { x, y, rot } = shape; 227 + const { fontSize, w } = shape.props; 228 + const localP = worldToLocal(p, x, y, rot); 229 + const width = w ?? fontSize * 10; 230 + const height = fontSize * 1.2; 231 + return localP.x >= 0 && localP.x <= width && localP.y >= 0 && localP.y <= height; 232 + } 233 + 234 + /** 235 + * Transform a point from world coordinates to shape-local coordinates 236 + * 237 + * @param p - Point in world coordinates 238 + * @param shapeX - Shape x position 239 + * @param shapeY - Shape y position 240 + * @param shapeRot - Shape rotation in radians 241 + * @returns Point in shape-local coordinates 242 + */ 243 + function worldToLocal(p: Vec2, shapeX: number, shapeY: number, shapeRot: number): Vec2 { 244 + const translated = { x: p.x - shapeX, y: p.y - shapeY }; 245 + 246 + if (shapeRot === 0) { 247 + return translated; 248 + } 249 + 250 + return rotatePoint(translated, -shapeRot); 251 + } 252 + 253 + /** 254 + * Perform hit testing to find which shape is under a point 255 + * 256 + * Uses reverse order (topmost shape wins) based on page.shapeIds order. 257 + * Line and arrow shapes use a tolerance for easier selection. 258 + * 259 + * @param state - Editor state 260 + * @param worldPoint - Point to test in world coordinates 261 + * @param tolerance - Tolerance for line/arrow hit testing (default: 5) 262 + * @returns Shape ID of the topmost shape under the point, or null if no hit 263 + */ 264 + export function hitTestPoint(state: EditorState, worldPoint: Vec2, tolerance = 5): string | null { 265 + const shapes = getShapesOnCurrentPage(state); 266 + 267 + for (let index = shapes.length - 1; index >= 0; index--) { 268 + const shape = shapes[index]; 269 + 270 + switch (shape.type) { 271 + case "rect": { 272 + if (pointInRect(worldPoint, shape)) { 273 + return shape.id; 274 + } 275 + break; 276 + } 277 + case "ellipse": { 278 + if (pointInEllipse(worldPoint, shape)) { 279 + return shape.id; 280 + } 281 + break; 282 + } 283 + case "line": 284 + case "arrow": { 285 + if (pointNearLine(worldPoint, shape, tolerance)) { 286 + return shape.id; 287 + } 288 + break; 289 + } 290 + case "text": { 291 + if (pointInText(worldPoint, shape)) { 292 + return shape.id; 293 + } 294 + break; 295 + } 296 + } 297 + } 298 + 299 + return null; 300 + }
+1
packages/core/src/index.ts
··· 1 1 export * from "./camera"; 2 + export * from "./geom"; 2 3 export * from "./math"; 3 4 export * from "./model"; 4 5 export * from "./reactivity";
+696
packages/core/tests/geom.test.ts
··· 1 + import { describe, expect, it } from "vitest"; 2 + import { 3 + hitTestPoint, 4 + PageRecord, 5 + pointInEllipse, 6 + pointInRect, 7 + pointNearSegment, 8 + shapeBounds, 9 + ShapeRecord, 10 + Store, 11 + } from "../src"; 12 + 13 + describe("Geometry", () => { 14 + describe("shapeBounds", () => { 15 + it("should return correct bounds for rect without rotation", () => { 16 + const rect = ShapeRecord.createRect("page:1", 100, 200, { w: 50, h: 30, fill: "", stroke: "", radius: 0 }); 17 + 18 + const bounds = shapeBounds(rect); 19 + 20 + expect(bounds.min).toEqual({ x: 100, y: 200 }); 21 + expect(bounds.max).toEqual({ x: 150, y: 230 }); 22 + }); 23 + 24 + it("should return correct bounds for rect with rotation", () => { 25 + const rect = ShapeRecord.createRect("page:1", 100, 100, { w: 100, h: 50, fill: "", stroke: "", radius: 0 }); 26 + rect.rot = Math.PI / 4; 27 + 28 + const bounds = shapeBounds(rect); 29 + 30 + expect(bounds.min.x).toBeDefined(); 31 + expect(bounds.min.y).toBeDefined(); 32 + expect(bounds.max.x).toBeGreaterThan(bounds.min.x); 33 + expect(bounds.max.y).toBeGreaterThan(bounds.min.y); 34 + }); 35 + 36 + it("should return correct bounds for ellipse without rotation", () => { 37 + const ellipse = ShapeRecord.createEllipse("page:1", 50, 50, { w: 100, h: 80, fill: "", stroke: "" }); 38 + 39 + const bounds = shapeBounds(ellipse); 40 + 41 + expect(bounds.min).toEqual({ x: 50, y: 50 }); 42 + expect(bounds.max).toEqual({ x: 150, y: 130 }); 43 + }); 44 + 45 + it("should return correct bounds for line", () => { 46 + const line = ShapeRecord.createLine("page:1", 10, 10, { 47 + a: { x: 0, y: 0 }, 48 + b: { x: 100, y: 50 }, 49 + stroke: "", 50 + width: 2, 51 + }); 52 + 53 + const bounds = shapeBounds(line); 54 + 55 + expect(bounds.min).toEqual({ x: 10, y: 10 }); 56 + expect(bounds.max).toEqual({ x: 110, y: 60 }); 57 + }); 58 + 59 + it("should return correct bounds for arrow", () => { 60 + const arrow = ShapeRecord.createArrow("page:1", 20, 30, { 61 + a: { x: 10, y: 10 }, 62 + b: { x: 50, y: 60 }, 63 + stroke: "", 64 + width: 2, 65 + }); 66 + 67 + const bounds = shapeBounds(arrow); 68 + 69 + expect(bounds.min).toEqual({ x: 30, y: 40 }); 70 + expect(bounds.max).toEqual({ x: 70, y: 90 }); 71 + }); 72 + 73 + it("should return correct bounds for text", () => { 74 + const text = ShapeRecord.createText("page:1", 100, 100, { 75 + text: "Hello", 76 + fontSize: 16, 77 + fontFamily: "Arial", 78 + color: "#000", 79 + w: 100, 80 + }); 81 + 82 + const bounds = shapeBounds(text); 83 + 84 + expect(bounds.min).toEqual({ x: 100, y: 100 }); 85 + expect(bounds.max.x).toBeCloseTo(200, 1); 86 + expect(bounds.max.y).toBeCloseTo(119.2, 1); 87 + }); 88 + 89 + it("should return correct bounds for text without explicit width", () => { 90 + const text = ShapeRecord.createText("page:1", 100, 100, { 91 + text: "Hello", 92 + fontSize: 20, 93 + fontFamily: "Arial", 94 + color: "#000", 95 + }); 96 + 97 + const bounds = shapeBounds(text); 98 + 99 + expect(bounds.min).toEqual({ x: 100, y: 100 }); 100 + expect(bounds.max.x).toBeCloseTo(300, 1); 101 + expect(bounds.max.y).toBeCloseTo(124, 1); 102 + }); 103 + }); 104 + 105 + describe("pointInRect", () => { 106 + it("should return true for point inside rect", () => { 107 + const rect = ShapeRecord.createRect("page:1", 100, 100, { w: 100, h: 50, fill: "", stroke: "", radius: 0 }); 108 + 109 + expect(pointInRect({ x: 150, y: 125 }, rect)).toBe(true); 110 + expect(pointInRect({ x: 100, y: 100 }, rect)).toBe(true); 111 + expect(pointInRect({ x: 200, y: 150 }, rect)).toBe(true); 112 + }); 113 + 114 + it("should return false for point outside rect", () => { 115 + const rect = ShapeRecord.createRect("page:1", 100, 100, { w: 100, h: 50, fill: "", stroke: "", radius: 0 }); 116 + 117 + expect(pointInRect({ x: 99, y: 125 }, rect)).toBe(false); 118 + expect(pointInRect({ x: 201, y: 125 }, rect)).toBe(false); 119 + expect(pointInRect({ x: 150, y: 99 }, rect)).toBe(false); 120 + expect(pointInRect({ x: 150, y: 151 }, rect)).toBe(false); 121 + }); 122 + 123 + it("should handle rotated rectangles", () => { 124 + const rect = ShapeRecord.createRect("page:1", 0, 0, { w: 100, h: 50, fill: "", stroke: "", radius: 0 }); 125 + rect.rot = Math.PI / 4; 126 + 127 + const centerInLocal = { x: 50, y: 25 }; 128 + const cos = Math.cos(Math.PI / 4); 129 + const sin = Math.sin(Math.PI / 4); 130 + const centerPoint = { 131 + x: centerInLocal.x * cos - centerInLocal.y * sin, 132 + y: centerInLocal.x * sin + centerInLocal.y * cos, 133 + }; 134 + expect(pointInRect(centerPoint, rect)).toBe(true); 135 + 136 + const farPoint = { x: 200, y: 200 }; 137 + expect(pointInRect(farPoint, rect)).toBe(false); 138 + }); 139 + }); 140 + 141 + describe("pointInEllipse", () => { 142 + it("should return true for point inside ellipse", () => { 143 + const ellipse = ShapeRecord.createEllipse("page:1", 100, 100, { w: 100, h: 80, fill: "", stroke: "" }); 144 + 145 + expect(pointInEllipse({ x: 150, y: 140 }, ellipse)).toBe(true); 146 + }); 147 + 148 + it("should return false for point outside ellipse", () => { 149 + const ellipse = ShapeRecord.createEllipse("page:1", 100, 100, { w: 100, h: 80, fill: "", stroke: "" }); 150 + 151 + expect(pointInEllipse({ x: 100, y: 100 }, ellipse)).toBe(false); 152 + expect(pointInEllipse({ x: 200, y: 180 }, ellipse)).toBe(false); 153 + }); 154 + 155 + it("should handle point at center of ellipse", () => { 156 + const ellipse = ShapeRecord.createEllipse("page:1", 100, 100, { w: 100, h: 80, fill: "", stroke: "" }); 157 + 158 + const center = { x: 150, y: 140 }; 159 + expect(pointInEllipse(center, ellipse)).toBe(true); 160 + }); 161 + 162 + it("should handle rotated ellipses", () => { 163 + const ellipse = ShapeRecord.createEllipse("page:1", 0, 0, { w: 100, h: 50, fill: "", stroke: "" }); 164 + ellipse.rot = Math.PI / 2; 165 + 166 + const centerInLocal = { x: 50, y: 25 }; 167 + const cos = Math.cos(Math.PI / 2); 168 + const sin = Math.sin(Math.PI / 2); 169 + const centerPoint = { 170 + x: centerInLocal.x * cos - centerInLocal.y * sin, 171 + y: centerInLocal.x * sin + centerInLocal.y * cos, 172 + }; 173 + expect(pointInEllipse(centerPoint, ellipse)).toBe(true); 174 + }); 175 + }); 176 + 177 + describe("pointNearSegment", () => { 178 + it("should return true for point on segment", () => { 179 + const a = { x: 0, y: 0 }; 180 + const b = { x: 100, y: 0 }; 181 + const p = { x: 50, y: 0 }; 182 + 183 + expect(pointNearSegment(p, a, b, 5)).toBe(true); 184 + }); 185 + 186 + it("should return true for point near segment within tolerance", () => { 187 + const a = { x: 0, y: 0 }; 188 + const b = { x: 100, y: 0 }; 189 + const p = { x: 50, y: 3 }; 190 + 191 + expect(pointNearSegment(p, a, b, 5)).toBe(true); 192 + }); 193 + 194 + it("should return false for point far from segment", () => { 195 + const a = { x: 0, y: 0 }; 196 + const b = { x: 100, y: 0 }; 197 + const p = { x: 50, y: 10 }; 198 + 199 + expect(pointNearSegment(p, a, b, 5)).toBe(false); 200 + }); 201 + 202 + it("should handle diagonal segments", () => { 203 + const a = { x: 0, y: 0 }; 204 + const b = { x: 100, y: 100 }; 205 + const p = { x: 50, y: 50 }; 206 + 207 + expect(pointNearSegment(p, a, b, 5)).toBe(true); 208 + }); 209 + 210 + it("should handle points beyond segment endpoints", () => { 211 + const a = { x: 0, y: 0 }; 212 + const b = { x: 100, y: 0 }; 213 + 214 + const beforeStart = { x: -10, y: 0 }; 215 + expect(pointNearSegment(beforeStart, a, b, 15)).toBe(true); 216 + 217 + const afterEnd = { x: 110, y: 0 }; 218 + expect(pointNearSegment(afterEnd, a, b, 15)).toBe(true); 219 + }); 220 + 221 + it("should handle zero-length segments", () => { 222 + const a = { x: 50, y: 50 }; 223 + const b = { x: 50, y: 50 }; 224 + const p = { x: 52, y: 52 }; 225 + 226 + expect(pointNearSegment(p, a, b, 5)).toBe(true); 227 + }); 228 + }); 229 + 230 + describe("pointNearSegment - edge cases", () => { 231 + it("should handle very small tolerance", () => { 232 + const a = { x: 0, y: 0 }; 233 + const b = { x: 100, y: 0 }; 234 + const p = { x: 50, y: 0.1 }; 235 + 236 + expect(pointNearSegment(p, a, b, 0.05)).toBe(false); 237 + expect(pointNearSegment(p, a, b, 0.2)).toBe(true); 238 + }); 239 + 240 + it("should handle negative coordinates", () => { 241 + const a = { x: -100, y: -100 }; 242 + const b = { x: -50, y: -50 }; 243 + const p = { x: -75, y: -75 }; 244 + 245 + expect(pointNearSegment(p, a, b, 5)).toBe(true); 246 + }); 247 + 248 + it("should handle vertical segments", () => { 249 + const a = { x: 50, y: 0 }; 250 + const b = { x: 50, y: 100 }; 251 + const p = { x: 52, y: 50 }; 252 + 253 + expect(pointNearSegment(p, a, b, 5)).toBe(true); 254 + expect(pointNearSegment(p, a, b, 1)).toBe(false); 255 + }); 256 + 257 + it("should handle horizontal segments", () => { 258 + const a = { x: 0, y: 50 }; 259 + const b = { x: 100, y: 50 }; 260 + const p = { x: 50, y: 52 }; 261 + 262 + expect(pointNearSegment(p, a, b, 5)).toBe(true); 263 + expect(pointNearSegment(p, a, b, 1)).toBe(false); 264 + }); 265 + 266 + it("should handle very long segments", () => { 267 + const a = { x: 0, y: 0 }; 268 + const b = { x: 10_000, y: 10_000 }; 269 + const p = { x: 5000, y: 5003 }; 270 + 271 + expect(pointNearSegment(p, a, b, 5)).toBe(true); 272 + }); 273 + 274 + it("should handle point exactly at endpoint", () => { 275 + const a = { x: 0, y: 0 }; 276 + const b = { x: 100, y: 100 }; 277 + 278 + expect(pointNearSegment(a, a, b, 0.1)).toBe(true); 279 + expect(pointNearSegment(b, a, b, 0.1)).toBe(true); 280 + }); 281 + }); 282 + 283 + describe("pointInRect - edge cases", () => { 284 + it("should handle point exactly on rect boundary", () => { 285 + const rect = ShapeRecord.createRect("page:1", 100, 100, { w: 100, h: 50, fill: "", stroke: "", radius: 0 }); 286 + 287 + expect(pointInRect({ x: 100, y: 125 }, rect)).toBe(true); 288 + expect(pointInRect({ x: 200, y: 125 }, rect)).toBe(true); 289 + expect(pointInRect({ x: 150, y: 100 }, rect)).toBe(true); 290 + expect(pointInRect({ x: 150, y: 150 }, rect)).toBe(true); 291 + }); 292 + 293 + it("should handle zero-size rectangles", () => { 294 + const rect = ShapeRecord.createRect("page:1", 100, 100, { w: 0, h: 0, fill: "", stroke: "", radius: 0 }); 295 + 296 + expect(pointInRect({ x: 100, y: 100 }, rect)).toBe(true); 297 + expect(pointInRect({ x: 100.1, y: 100 }, rect)).toBe(false); 298 + }); 299 + 300 + it("should handle negative coordinates", () => { 301 + const rect = ShapeRecord.createRect("page:1", -100, -100, { w: 50, h: 50, fill: "", stroke: "", radius: 0 }); 302 + 303 + expect(pointInRect({ x: -75, y: -75 }, rect)).toBe(true); 304 + expect(pointInRect({ x: -101, y: -75 }, rect)).toBe(false); 305 + }); 306 + 307 + it("should handle very small rectangles", () => { 308 + const rect = ShapeRecord.createRect("page:1", 100, 100, { w: 0.1, h: 0.1, fill: "", stroke: "", radius: 0 }); 309 + 310 + expect(pointInRect({ x: 100.05, y: 100.05 }, rect)).toBe(true); 311 + expect(pointInRect({ x: 100.2, y: 100.05 }, rect)).toBe(false); 312 + }); 313 + 314 + it("should handle 90 degree rotation", () => { 315 + const rect = ShapeRecord.createRect("page:1", 0, 0, { w: 100, h: 50, fill: "", stroke: "", radius: 0 }); 316 + rect.rot = Math.PI / 2; 317 + 318 + const center = { x: -25, y: 50 }; 319 + expect(pointInRect(center, rect)).toBe(true); 320 + }); 321 + 322 + it("should handle 180 degree rotation", () => { 323 + const rect = ShapeRecord.createRect("page:1", 0, 0, { w: 100, h: 50, fill: "", stroke: "", radius: 0 }); 324 + rect.rot = Math.PI; 325 + 326 + const center = { x: -50, y: -25 }; 327 + expect(pointInRect(center, rect)).toBe(true); 328 + }); 329 + }); 330 + 331 + describe("pointInEllipse - edge cases", () => { 332 + it("should handle point exactly on ellipse boundary", () => { 333 + const ellipse = ShapeRecord.createEllipse("page:1", 100, 100, { w: 100, h: 80, fill: "", stroke: "" }); 334 + 335 + const rightEdge = { x: 200, y: 140 }; 336 + expect(pointInEllipse(rightEdge, ellipse)).toBe(true); 337 + }); 338 + 339 + it("should handle very small ellipse", () => { 340 + const ellipse = ShapeRecord.createEllipse("page:1", 100, 100, { w: 0.2, h: 0.2, fill: "", stroke: "" }); 341 + 342 + expect(pointInEllipse({ x: 100.1, y: 100.1 }, ellipse)).toBe(true); 343 + expect(pointInEllipse({ x: 100.2, y: 100.1 }, ellipse)).toBe(false); 344 + }); 345 + 346 + it("should handle circle (equal width and height)", () => { 347 + const circle = ShapeRecord.createEllipse("page:1", 100, 100, { w: 100, h: 100, fill: "", stroke: "" }); 348 + 349 + expect(pointInEllipse({ x: 150, y: 150 }, circle)).toBe(true); 350 + expect(pointInEllipse({ x: 200, y: 150 }, circle)).toBe(true); 351 + }); 352 + 353 + it("should handle negative coordinates", () => { 354 + const ellipse = ShapeRecord.createEllipse("page:1", -100, -100, { w: 100, h: 80, fill: "", stroke: "" }); 355 + 356 + expect(pointInEllipse({ x: -50, y: -60 }, ellipse)).toBe(true); 357 + }); 358 + 359 + it("should handle very flat ellipse", () => { 360 + const ellipse = ShapeRecord.createEllipse("page:1", 100, 100, { w: 200, h: 10, fill: "", stroke: "" }); 361 + 362 + const center = { x: 200, y: 105 }; 363 + expect(pointInEllipse(center, ellipse)).toBe(true); 364 + 365 + const farOutside = { x: 200, y: 120 }; 366 + expect(pointInEllipse(farOutside, ellipse)).toBe(false); 367 + }); 368 + 369 + it("should handle very tall ellipse", () => { 370 + const ellipse = ShapeRecord.createEllipse("page:1", 100, 100, { w: 10, h: 200, fill: "", stroke: "" }); 371 + 372 + const center = { x: 105, y: 200 }; 373 + expect(pointInEllipse(center, ellipse)).toBe(true); 374 + 375 + const farOutside = { x: 120, y: 200 }; 376 + expect(pointInEllipse(farOutside, ellipse)).toBe(false); 377 + }); 378 + }); 379 + 380 + describe("shapeBounds - edge cases", () => { 381 + it("should handle negative width/height gracefully", () => { 382 + const rect = ShapeRecord.createRect("page:1", 100, 100, { w: -50, h: -30, fill: "", stroke: "", radius: 0 }); 383 + 384 + const bounds = shapeBounds(rect); 385 + expect(bounds.min.x).toBeDefined(); 386 + expect(bounds.max.x).toBeDefined(); 387 + expect(bounds.min.y).toBeDefined(); 388 + expect(bounds.max.y).toBeDefined(); 389 + }); 390 + 391 + it("should handle zero-size shapes", () => { 392 + const rect = ShapeRecord.createRect("page:1", 100, 100, { w: 0, h: 0, fill: "", stroke: "", radius: 0 }); 393 + 394 + const bounds = shapeBounds(rect); 395 + expect(bounds.min).toEqual({ x: 100, y: 100 }); 396 + expect(bounds.max).toEqual({ x: 100, y: 100 }); 397 + }); 398 + 399 + it("should handle line with same start and end points", () => { 400 + const line = ShapeRecord.createLine("page:1", 100, 100, { 401 + a: { x: 0, y: 0 }, 402 + b: { x: 0, y: 0 }, 403 + stroke: "", 404 + width: 2, 405 + }); 406 + 407 + const bounds = shapeBounds(line); 408 + expect(bounds.min).toEqual({ x: 100, y: 100 }); 409 + expect(bounds.max).toEqual({ x: 100, y: 100 }); 410 + }); 411 + 412 + it("should handle multiple rotations", () => { 413 + const rect = ShapeRecord.createRect("page:1", 100, 100, { w: 100, h: 50, fill: "", stroke: "", radius: 0 }); 414 + rect.rot = Math.PI * 2; 415 + 416 + const bounds = shapeBounds(rect); 417 + expect(bounds.min.x).toBeCloseTo(100, 1); 418 + expect(bounds.min.y).toBeCloseTo(100, 1); 419 + expect(bounds.max.x).toBeCloseTo(200, 1); 420 + expect(bounds.max.y).toBeCloseTo(150, 1); 421 + }); 422 + 423 + it("should handle very large shapes", () => { 424 + const rect = ShapeRecord.createRect("page:1", 0, 0, { w: 100_000, h: 100_000, fill: "", stroke: "", radius: 0 }); 425 + 426 + const bounds = shapeBounds(rect); 427 + expect(bounds.max.x).toBe(100_000); 428 + expect(bounds.max.y).toBe(100_000); 429 + }); 430 + 431 + it("should handle line with rotation", () => { 432 + const line = ShapeRecord.createLine("page:1", 100, 100, { 433 + a: { x: 0, y: 0 }, 434 + b: { x: 100, y: 0 }, 435 + stroke: "", 436 + width: 2, 437 + }); 438 + line.rot = Math.PI / 2; 439 + 440 + const bounds = shapeBounds(line); 441 + expect(bounds.min.x).toBeCloseTo(100, 1); 442 + expect(bounds.max.y).toBeCloseTo(200, 1); 443 + }); 444 + }); 445 + 446 + describe("hitTestPoint", () => { 447 + it("should return shape id for point inside rect", () => { 448 + const store = new Store(); 449 + const page = PageRecord.create("Page 1", "page:1"); 450 + const rect = ShapeRecord.createRect("page:1", 100, 100, { 451 + w: 100, 452 + h: 50, 453 + fill: "#ff0000", 454 + stroke: "#000000", 455 + radius: 0, 456 + }, "shape:1"); 457 + 458 + store.setState((state) => ({ 459 + ...state, 460 + doc: { pages: { [page.id]: { ...page, shapeIds: [rect.id] } }, shapes: { [rect.id]: rect }, bindings: {} }, 461 + ui: { ...state.ui, currentPageId: page.id }, 462 + })); 463 + 464 + const state = store.getState(); 465 + const hitId = hitTestPoint(state, { x: 150, y: 125 }); 466 + 467 + expect(hitId).toBe("shape:1"); 468 + }); 469 + 470 + it("should return null for point outside all shapes", () => { 471 + const store = new Store(); 472 + const page = PageRecord.create("Page 1", "page:1"); 473 + const rect = ShapeRecord.createRect("page:1", 100, 100, { 474 + w: 100, 475 + h: 50, 476 + fill: "#ff0000", 477 + stroke: "#000000", 478 + radius: 0, 479 + }, "shape:1"); 480 + 481 + store.setState((state) => ({ 482 + ...state, 483 + doc: { pages: { [page.id]: { ...page, shapeIds: [rect.id] } }, shapes: { [rect.id]: rect }, bindings: {} }, 484 + ui: { ...state.ui, currentPageId: page.id }, 485 + })); 486 + 487 + const state = store.getState(); 488 + const hitId = hitTestPoint(state, { x: 50, y: 50 }); 489 + 490 + expect(hitId).toBeNull(); 491 + }); 492 + 493 + it("should return topmost shape for overlapping shapes", () => { 494 + const store = new Store(); 495 + const page = PageRecord.create("Page 1", "page:1"); 496 + const rect1 = ShapeRecord.createRect("page:1", 100, 100, { 497 + w: 200, 498 + h: 200, 499 + fill: "#ff0000", 500 + stroke: "#000000", 501 + radius: 0, 502 + }, "shape:1"); 503 + const rect2 = ShapeRecord.createRect("page:1", 150, 150, { 504 + w: 100, 505 + h: 100, 506 + fill: "#00ff00", 507 + stroke: "#000000", 508 + radius: 0, 509 + }, "shape:2"); 510 + 511 + store.setState((state) => ({ 512 + ...state, 513 + doc: { 514 + pages: { [page.id]: { ...page, shapeIds: [rect1.id, rect2.id] } }, 515 + shapes: { [rect1.id]: rect1, [rect2.id]: rect2 }, 516 + bindings: {}, 517 + }, 518 + ui: { ...state.ui, currentPageId: page.id }, 519 + })); 520 + 521 + const state = store.getState(); 522 + const hitId = hitTestPoint(state, { x: 200, y: 200 }); 523 + 524 + expect(hitId).toBe("shape:2"); 525 + }); 526 + 527 + it("should hit test ellipse shapes", () => { 528 + const store = new Store(); 529 + const page = PageRecord.create("Page 1", "page:1"); 530 + const ellipse = ShapeRecord.createEllipse("page:1", 100, 100, { 531 + w: 100, 532 + h: 80, 533 + fill: "#00ff00", 534 + stroke: "#000000", 535 + }, "shape:1"); 536 + 537 + store.setState((state) => ({ 538 + ...state, 539 + doc: { 540 + pages: { [page.id]: { ...page, shapeIds: [ellipse.id] } }, 541 + shapes: { [ellipse.id]: ellipse }, 542 + bindings: {}, 543 + }, 544 + ui: { ...state.ui, currentPageId: page.id }, 545 + })); 546 + 547 + const state = store.getState(); 548 + const hitId = hitTestPoint(state, { x: 150, y: 140 }); 549 + 550 + expect(hitId).toBe("shape:1"); 551 + }); 552 + 553 + it("should hit test line shapes with tolerance", () => { 554 + const store = new Store(); 555 + const page = PageRecord.create("Page 1", "page:1"); 556 + const line = ShapeRecord.createLine("page:1", 100, 100, { 557 + a: { x: 0, y: 0 }, 558 + b: { x: 100, y: 100 }, 559 + stroke: "#000000", 560 + width: 2, 561 + }, "shape:1"); 562 + 563 + store.setState((state) => ({ 564 + ...state, 565 + doc: { pages: { [page.id]: { ...page, shapeIds: [line.id] } }, shapes: { [line.id]: line }, bindings: {} }, 566 + ui: { ...state.ui, currentPageId: page.id }, 567 + })); 568 + 569 + const state = store.getState(); 570 + const hitId = hitTestPoint(state, { x: 150, y: 152 }, 5); 571 + 572 + expect(hitId).toBe("shape:1"); 573 + }); 574 + 575 + it("should hit test arrow shapes with tolerance", () => { 576 + const store = new Store(); 577 + const page = PageRecord.create("Page 1", "page:1"); 578 + const arrow = ShapeRecord.createArrow("page:1", 100, 100, { 579 + a: { x: 0, y: 0 }, 580 + b: { x: 100, y: 0 }, 581 + stroke: "#000000", 582 + width: 2, 583 + }, "shape:1"); 584 + 585 + store.setState((state) => ({ 586 + ...state, 587 + doc: { pages: { [page.id]: { ...page, shapeIds: [arrow.id] } }, shapes: { [arrow.id]: arrow }, bindings: {} }, 588 + ui: { ...state.ui, currentPageId: page.id }, 589 + })); 590 + 591 + const state = store.getState(); 592 + const hitId = hitTestPoint(state, { x: 150, y: 103 }, 5); 593 + 594 + expect(hitId).toBe("shape:1"); 595 + }); 596 + 597 + it("should hit test text shapes", () => { 598 + const store = new Store(); 599 + const page = PageRecord.create("Page 1", "page:1"); 600 + const text = ShapeRecord.createText("page:1", 100, 100, { 601 + text: "Hello", 602 + fontSize: 16, 603 + fontFamily: "Arial", 604 + color: "#000000", 605 + w: 100, 606 + }, "shape:1"); 607 + 608 + store.setState((state) => ({ 609 + ...state, 610 + doc: { pages: { [page.id]: { ...page, shapeIds: [text.id] } }, shapes: { [text.id]: text }, bindings: {} }, 611 + ui: { ...state.ui, currentPageId: page.id }, 612 + })); 613 + 614 + const state = store.getState(); 615 + const hitId = hitTestPoint(state, { x: 150, y: 110 }); 616 + 617 + expect(hitId).toBe("shape:1"); 618 + }); 619 + 620 + it("should respect tolerance parameter for lines", () => { 621 + const store = new Store(); 622 + const page = PageRecord.create("Page 1", "page:1"); 623 + const line = ShapeRecord.createLine("page:1", 0, 0, { 624 + a: { x: 0, y: 0 }, 625 + b: { x: 100, y: 0 }, 626 + stroke: "#000000", 627 + width: 2, 628 + }, "shape:1"); 629 + 630 + store.setState((state) => ({ 631 + ...state, 632 + doc: { pages: { [page.id]: { ...page, shapeIds: [line.id] } }, shapes: { [line.id]: line }, bindings: {} }, 633 + ui: { ...state.ui, currentPageId: page.id }, 634 + })); 635 + 636 + const state = store.getState(); 637 + 638 + const hitWithin = hitTestPoint(state, { x: 50, y: 3 }, 5); 639 + expect(hitWithin).toBe("shape:1"); 640 + 641 + const hitOutside = hitTestPoint(state, { x: 50, y: 8 }, 5); 642 + expect(hitOutside).toBeNull(); 643 + 644 + const hitWithLargeTolerance = hitTestPoint(state, { x: 50, y: 8 }, 10); 645 + expect(hitWithLargeTolerance).toBe("shape:1"); 646 + }); 647 + 648 + it("should return null when no page is selected", () => { 649 + const store = new Store(); 650 + const state = store.getState(); 651 + const hitId = hitTestPoint(state, { x: 150, y: 125 }); 652 + 653 + expect(hitId).toBeNull(); 654 + }); 655 + 656 + it("should handle multiple shape types on same page", () => { 657 + const store = new Store(); 658 + const page = PageRecord.create("Page 1", "page:1"); 659 + const rect = ShapeRecord.createRect("page:1", 0, 0, { 660 + w: 100, 661 + h: 100, 662 + fill: "#ff0000", 663 + stroke: "#000000", 664 + radius: 0, 665 + }, "shape:1"); 666 + const ellipse = ShapeRecord.createEllipse("page:1", 200, 0, { 667 + w: 100, 668 + h: 100, 669 + fill: "#00ff00", 670 + stroke: "#000000", 671 + }, "shape:2"); 672 + const line = ShapeRecord.createLine("page:1", 0, 200, { 673 + a: { x: 0, y: 0 }, 674 + b: { x: 100, y: 100 }, 675 + stroke: "#000000", 676 + width: 2, 677 + }, "shape:3"); 678 + 679 + store.setState((state) => ({ 680 + ...state, 681 + doc: { 682 + pages: { [page.id]: { ...page, shapeIds: [rect.id, ellipse.id, line.id] } }, 683 + shapes: { [rect.id]: rect, [ellipse.id]: ellipse, [line.id]: line }, 684 + bindings: {}, 685 + }, 686 + ui: { ...state.ui, currentPageId: page.id }, 687 + })); 688 + 689 + const state = store.getState(); 690 + 691 + expect(hitTestPoint(state, { x: 50, y: 50 })).toBe("shape:1"); 692 + expect(hitTestPoint(state, { x: 250, y: 50 })).toBe("shape:2"); 693 + expect(hitTestPoint(state, { x: 50, y: 252 }, 5)).toBe("shape:3"); 694 + }); 695 + }); 696 + });
+32 -5
packages/core/tests/index.test.ts
··· 1 - import { expect, test } from 'vitest' 2 - import { fn } from '../src' 1 + import { describe, expect, it } from "vitest"; 2 + import * as core from "../src"; 3 + 4 + describe("Core exports", () => { 5 + it("should export math types and functions", () => { 6 + expect(core.Vec2).toBeDefined(); 7 + expect(core.Box2).toBeDefined(); 8 + expect(core.Mat3).toBeDefined(); 9 + }); 10 + 11 + it("should export camera functions", () => { 12 + expect(core.Camera).toBeDefined(); 13 + }); 14 + 15 + it("should export model types and functions", () => { 16 + expect(core.ShapeRecord).toBeDefined(); 17 + expect(core.PageRecord).toBeDefined(); 18 + expect(core.Document).toBeDefined(); 19 + }); 3 20 4 - test('fn', () => { 5 - expect(fn()).toBe('Hello, tsdown!') 6 - }) 21 + it("should export reactivity functions", () => { 22 + expect(core.Store).toBeDefined(); 23 + expect(core.getShapesOnCurrentPage).toBeDefined(); 24 + }); 25 + 26 + it("should export geometry functions", () => { 27 + expect(core.shapeBounds).toBeDefined(); 28 + expect(core.pointInRect).toBeDefined(); 29 + expect(core.pointInEllipse).toBeDefined(); 30 + expect(core.pointNearSegment).toBeDefined(); 31 + expect(core.hitTestPoint).toBeDefined(); 32 + }); 33 + });
+13
packages/core/vitest.config.ts
··· 1 + import { defineConfig } from "vitest/config"; 2 + 3 + export default defineConfig({ 4 + test: { 5 + ui: false, 6 + coverage: { 7 + provider: "v8", 8 + reporter: ["text", "html", "json"], 9 + thresholds: { lines: 75, functions: 75, branches: 75, statements: 75 }, 10 + exclude: ["**/node_modules/**", "**/dist/**", "**/*.test.ts", "**/*.config.ts", "**/tests/**"], 11 + }, 12 + }, 13 + });
+1 -1
packages/renderer/vitest.config.ts
··· 1 1 import { defineConfig } from "vitest/config"; 2 2 3 - export default defineConfig({ test: { environment: "jsdom", globals: true } }); 3 + export default defineConfig({ test: { environment: "jsdom", globals: true, ui: false } });
+849 -8
pnpm-lock.yaml
··· 30 30 specifier: ^8.50.0 31 31 version: 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 32 32 33 + apps/web: 34 + devDependencies: 35 + '@eslint/compat': 36 + specifier: ^1.4.0 37 + version: 1.4.1(eslint@9.39.2(jiti@2.6.1)) 38 + '@eslint/js': 39 + specifier: ^9.39.1 40 + version: 9.39.2 41 + '@sveltejs/adapter-static': 42 + specifier: ^3.0.10 43 + version: 3.0.10(@sveltejs/kit@2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.46.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2))) 44 + '@sveltejs/kit': 45 + specifier: ^2.49.1 46 + version: 2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.46.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)) 47 + '@sveltejs/vite-plugin-svelte': 48 + specifier: ^6.2.1 49 + version: 6.2.1(svelte@5.46.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)) 50 + '@types/node': 51 + specifier: ^24 52 + version: 24.10.4 53 + '@vitest/browser-playwright': 54 + specifier: ^4.0.15 55 + version: 4.0.16(playwright@1.57.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.16) 56 + eslint: 57 + specifier: ^9.39.1 58 + version: 9.39.2(jiti@2.6.1) 59 + eslint-config-prettier: 60 + specifier: ^10.1.8 61 + version: 10.1.8(eslint@9.39.2(jiti@2.6.1)) 62 + eslint-plugin-svelte: 63 + specifier: ^3.13.1 64 + version: 3.13.1(eslint@9.39.2(jiti@2.6.1))(svelte@5.46.0) 65 + globals: 66 + specifier: ^16.5.0 67 + version: 16.5.0 68 + playwright: 69 + specifier: ^1.57.0 70 + version: 1.57.0 71 + prettier: 72 + specifier: ^3.7.4 73 + version: 3.7.4 74 + prettier-plugin-svelte: 75 + specifier: ^3.4.0 76 + version: 3.4.1(prettier@3.7.4)(svelte@5.46.0) 77 + svelte: 78 + specifier: ^5.45.6 79 + version: 5.46.0 80 + svelte-check: 81 + specifier: ^4.3.4 82 + version: 4.3.5(picomatch@4.0.3)(svelte@5.46.0)(typescript@5.9.3) 83 + typescript: 84 + specifier: ^5.9.3 85 + version: 5.9.3 86 + typescript-eslint: 87 + specifier: ^8.48.1 88 + version: 8.50.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 89 + vite: 90 + specifier: ^7.2.6 91 + version: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2) 92 + vite-plugin-devtools-json: 93 + specifier: ^1.0.0 94 + version: 1.0.0(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)) 95 + vitest: 96 + specifier: ^4.0.15 97 + version: 4.0.16(@types/node@24.10.4)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@27.3.0)(yaml@2.8.2) 98 + vitest-browser-svelte: 99 + specifier: ^2.0.1 100 + version: 2.0.1(svelte@5.46.0)(vitest@4.0.16) 101 + 33 102 packages/core: 34 103 dependencies: 35 104 rxjs: ··· 42 111 '@types/node': 43 112 specifier: ^25.0.3 44 113 version: 25.0.3 114 + '@vitest/coverage-v8': 115 + specifier: ^4.0.16 116 + version: 4.0.16(@vitest/browser@4.0.16(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.16))(vitest@4.0.16) 45 117 bumpp: 46 118 specifier: ^10.3.2 47 - version: 10.3.2 119 + version: 10.3.2(magicast@0.5.1) 48 120 tsdown: 49 121 specifier: ^0.18.1 50 122 version: 0.18.1(typescript@5.9.3) ··· 53 125 version: 5.9.3 54 126 vitest: 55 127 specifier: ^4.0.16 56 - version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(jsdom@27.3.0)(yaml@2.8.2) 128 + version: 4.0.16(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@27.3.0)(yaml@2.8.2) 57 129 58 130 packages/renderer: 59 131 dependencies: ··· 69 141 version: 25.0.3 70 142 bumpp: 71 143 specifier: ^10.3.2 72 - version: 10.3.2 144 + version: 10.3.2(magicast@0.5.1) 73 145 jsdom: 74 146 specifier: ^27.3.0 75 147 version: 27.3.0 ··· 81 153 version: 5.9.3 82 154 vitest: 83 155 specifier: ^4.0.16 84 - version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(jsdom@27.3.0)(yaml@2.8.2) 156 + version: 4.0.16(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@27.3.0)(yaml@2.8.2) 85 157 86 158 packages: 87 159 ··· 118 190 resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} 119 191 engines: {node: '>=6.9.0'} 120 192 193 + '@bcoe/v8-coverage@1.0.2': 194 + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} 195 + engines: {node: '>=18'} 196 + 121 197 '@csstools/color-helpers@5.1.0': 122 198 resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} 123 199 engines: {node: '>=18'} ··· 370 446 resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} 371 447 engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 372 448 449 + '@eslint/compat@1.4.1': 450 + resolution: {integrity: sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==} 451 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 452 + peerDependencies: 453 + eslint: ^8.40 || 9 454 + peerDependenciesMeta: 455 + eslint: 456 + optional: true 457 + 373 458 '@eslint/config-array@0.21.1': 374 459 resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} 375 460 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} ··· 417 502 '@jridgewell/gen-mapping@0.3.13': 418 503 resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} 419 504 505 + '@jridgewell/remapping@2.3.5': 506 + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} 507 + 420 508 '@jridgewell/resolve-uri@3.1.2': 421 509 resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 422 510 engines: {node: '>=6.0.0'} ··· 432 520 433 521 '@oxc-project/types@0.103.0': 434 522 resolution: {integrity: sha512-bkiYX5kaXWwUessFRSoXFkGIQTmc6dLGdxuRTrC+h8PSnIdZyuXHHlLAeTmOue5Br/a0/a7dHH0Gca6eXn9MKg==} 523 + 524 + '@polka/url@1.0.0-next.29': 525 + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} 435 526 436 527 '@quansync/fs@1.0.0': 437 528 resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} ··· 629 720 '@standard-schema/spec@1.1.0': 630 721 resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} 631 722 723 + '@sveltejs/acorn-typescript@1.0.8': 724 + resolution: {integrity: sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA==} 725 + peerDependencies: 726 + acorn: ^8.9.0 727 + 728 + '@sveltejs/adapter-static@3.0.10': 729 + resolution: {integrity: sha512-7D9lYFWJmB7zxZyTE/qxjksvMqzMuYrrsyh1f4AlZqeZeACPRySjbC3aFiY55wb1tWUaKOQG9PVbm74JcN2Iew==} 730 + peerDependencies: 731 + '@sveltejs/kit': ^2.0.0 732 + 733 + '@sveltejs/kit@2.49.2': 734 + resolution: {integrity: sha512-Vp3zX/qlwerQmHMP6x0Ry1oY7eKKRcOWGc2P59srOp4zcqyn+etJyQpELgOi4+ZSUgteX8Y387NuwruLgGXLUQ==} 735 + engines: {node: '>=18.13'} 736 + hasBin: true 737 + peerDependencies: 738 + '@opentelemetry/api': ^1.0.0 739 + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 740 + svelte: ^4.0.0 || ^5.0.0-next.0 741 + vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 742 + peerDependenciesMeta: 743 + '@opentelemetry/api': 744 + optional: true 745 + 746 + '@sveltejs/vite-plugin-svelte-inspector@5.0.1': 747 + resolution: {integrity: sha512-ubWshlMk4bc8mkwWbg6vNvCeT7lGQojE3ijDh3QTR6Zr/R+GXxsGbyH4PExEPpiFmqPhYiVSVmHBjUcVc1JIrA==} 748 + engines: {node: ^20.19 || ^22.12 || >=24} 749 + peerDependencies: 750 + '@sveltejs/vite-plugin-svelte': ^6.0.0-next.0 751 + svelte: ^5.0.0 752 + vite: ^6.3.0 || ^7.0.0 753 + 754 + '@sveltejs/vite-plugin-svelte@6.2.1': 755 + resolution: {integrity: sha512-YZs/OSKOQAQCnJvM/P+F1URotNnYNeU3P2s4oIpzm1uFaqUEqRxUB0g5ejMjEb5Gjb9/PiBI5Ktrq4rUUF8UVQ==} 756 + engines: {node: ^20.19 || ^22.12 || >=24} 757 + peerDependencies: 758 + svelte: ^5.0.0 759 + vite: ^6.3.0 || ^7.0.0 760 + 632 761 '@tybys/wasm-util@0.10.1': 633 762 resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} 634 763 635 764 '@types/chai@5.2.3': 636 765 resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} 637 766 767 + '@types/cookie@0.6.0': 768 + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} 769 + 638 770 '@types/deep-eql@4.0.2': 639 771 resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} 640 772 ··· 646 778 647 779 '@types/json-schema@7.0.15': 648 780 resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 781 + 782 + '@types/node@24.10.4': 783 + resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==} 649 784 650 785 '@types/node@25.0.3': 651 786 resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==} ··· 712 847 resolution: {integrity: sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==} 713 848 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 714 849 850 + '@vitest/browser-playwright@4.0.16': 851 + resolution: {integrity: sha512-I2Fy/ANdphi1yI46d15o0M1M4M0UJrUiVKkH5oKeRZZCdPg0fw/cfTKZzv9Ge9eobtJYp4BGblMzXdXH0vcl5g==} 852 + peerDependencies: 853 + playwright: '*' 854 + vitest: 4.0.16 855 + 856 + '@vitest/browser@4.0.16': 857 + resolution: {integrity: sha512-t4toy8X/YTnjYEPoY0pbDBg3EvDPg1elCDrfc+VupPHwoN/5/FNQ8Z+xBYIaEnOE2vVEyKwqYBzZ9h9rJtZVcg==} 858 + peerDependencies: 859 + vitest: 4.0.16 860 + 861 + '@vitest/coverage-v8@4.0.16': 862 + resolution: {integrity: sha512-2rNdjEIsPRzsdu6/9Eq0AYAzYdpP6Bx9cje9tL3FE5XzXRQF1fNU9pe/1yE8fCrS0HD+fBtt6gLPh6LI57tX7A==} 863 + peerDependencies: 864 + '@vitest/browser': 4.0.16 865 + vitest: 4.0.16 866 + peerDependenciesMeta: 867 + '@vitest/browser': 868 + optional: true 869 + 715 870 '@vitest/expect@4.0.16': 716 871 resolution: {integrity: sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==} 717 872 ··· 772 927 args-tokenizer@0.3.0: 773 928 resolution: {integrity: sha512-xXAd7G2Mll5W8uo37GETpQ2VrE84M181Z7ugHFGQnJZ50M2mbOv0osSZ9VsSgPfJQ+LVG0prSi0th+ELMsno7Q==} 774 929 930 + aria-query@5.3.2: 931 + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} 932 + engines: {node: '>= 0.4'} 933 + 775 934 assertion-error@2.0.1: 776 935 resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} 777 936 engines: {node: '>=12'} ··· 780 939 resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==} 781 940 engines: {node: '>=20.19.0'} 782 941 942 + ast-v8-to-istanbul@0.3.9: 943 + resolution: {integrity: sha512-dSC6tJeOJxbZrPzPbv5mMd6CMiQ1ugaVXXPRad2fXUSsy1kstFn9XQWemV9VW7Y7kpxgQ/4WMoZfwdH8XSU48w==} 944 + 945 + axobject-query@4.1.0: 946 + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} 947 + engines: {node: '>= 0.4'} 948 + 783 949 balanced-match@1.0.2: 784 950 resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 785 951 ··· 843 1009 change-case@5.4.4: 844 1010 resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} 845 1011 1012 + chokidar@4.0.3: 1013 + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} 1014 + engines: {node: '>= 14.16.0'} 1015 + 846 1016 chokidar@5.0.0: 847 1017 resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} 848 1018 engines: {node: '>= 20.19.0'} ··· 858 1028 resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} 859 1029 engines: {node: '>=4'} 860 1030 1031 + clsx@2.1.1: 1032 + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} 1033 + engines: {node: '>=6'} 1034 + 861 1035 color-convert@2.0.1: 862 1036 resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 863 1037 engines: {node: '>=7.0.0'} ··· 875 1049 resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} 876 1050 engines: {node: ^14.18.0 || >=16.10.0} 877 1051 1052 + cookie@0.6.0: 1053 + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} 1054 + engines: {node: '>= 0.6'} 1055 + 878 1056 core-js-compat@3.47.0: 879 1057 resolution: {integrity: sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==} 880 1058 ··· 886 1064 resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} 887 1065 engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} 888 1066 1067 + cssesc@3.0.0: 1068 + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} 1069 + engines: {node: '>=4'} 1070 + hasBin: true 1071 + 889 1072 cssstyle@5.3.5: 890 1073 resolution: {integrity: sha512-GlsEptulso7Jg0VaOZ8BXQi3AkYM5BOJKEO/rjMidSCq70FkIC5y0eawrCXeYzxgt3OCf4Ls+eoxN+/05vN0Ag==} 891 1074 engines: {node: '>=20'} ··· 909 1092 deep-is@0.1.4: 910 1093 resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 911 1094 1095 + deepmerge@4.3.1: 1096 + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} 1097 + engines: {node: '>=0.10.0'} 1098 + 912 1099 defu@6.1.4: 913 1100 resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} 914 1101 915 1102 destr@2.0.5: 916 1103 resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} 1104 + 1105 + devalue@5.6.1: 1106 + resolution: {integrity: sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==} 917 1107 918 1108 dotenv@17.2.3: 919 1109 resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} ··· 963 1153 resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} 964 1154 engines: {node: '>=10'} 965 1155 1156 + eslint-config-prettier@10.1.8: 1157 + resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} 1158 + hasBin: true 1159 + peerDependencies: 1160 + eslint: '>=7.0.0' 1161 + 1162 + eslint-plugin-svelte@3.13.1: 1163 + resolution: {integrity: sha512-Ng+kV/qGS8P/isbNYVE3sJORtubB+yLEcYICMkUWNaDTb0SwZni/JhAYXh/Dz/q2eThUwWY0VMPZ//KYD1n3eQ==} 1164 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} 1165 + peerDependencies: 1166 + eslint: ^8.57.1 || ^9.0.0 1167 + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 1168 + peerDependenciesMeta: 1169 + svelte: 1170 + optional: true 1171 + 966 1172 eslint-plugin-unicorn@62.0.0: 967 1173 resolution: {integrity: sha512-HIlIkGLkvf29YEiS/ImuDZQbP12gWyx5i3C6XrRxMvVdqMroCI9qoVYCoIl17ChN+U89pn9sVwLxhIWj5nEc7g==} 968 1174 engines: {node: ^20.10.0 || >=21.0.0} ··· 991 1197 jiti: 992 1198 optional: true 993 1199 1200 + esm-env@1.2.2: 1201 + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} 1202 + 994 1203 espree@10.4.0: 995 1204 resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} 996 1205 engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} ··· 998 1207 esquery@1.6.0: 999 1208 resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} 1000 1209 engines: {node: '>=0.10'} 1210 + 1211 + esrap@2.2.1: 1212 + resolution: {integrity: sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg==} 1001 1213 1002 1214 esrecurse@4.3.0: 1003 1215 resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} ··· 1058 1270 flatted@3.3.3: 1059 1271 resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} 1060 1272 1273 + fsevents@2.3.2: 1274 + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} 1275 + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 1276 + os: [darwin] 1277 + 1061 1278 fsevents@2.3.3: 1062 1279 resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 1063 1280 engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} ··· 1092 1309 html-encoding-sniffer@4.0.0: 1093 1310 resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} 1094 1311 engines: {node: '>=18'} 1312 + 1313 + html-escaper@2.0.2: 1314 + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} 1095 1315 1096 1316 http-proxy-agent@7.0.2: 1097 1317 resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} ··· 1144 1364 is-potential-custom-element-name@1.0.1: 1145 1365 resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} 1146 1366 1367 + is-reference@3.0.3: 1368 + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} 1369 + 1147 1370 isexe@2.0.0: 1148 1371 resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 1149 1372 1373 + istanbul-lib-coverage@3.2.2: 1374 + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} 1375 + engines: {node: '>=8'} 1376 + 1377 + istanbul-lib-report@3.0.1: 1378 + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} 1379 + engines: {node: '>=10'} 1380 + 1381 + istanbul-lib-source-maps@5.0.6: 1382 + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} 1383 + engines: {node: '>=10'} 1384 + 1385 + istanbul-reports@3.2.0: 1386 + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} 1387 + engines: {node: '>=8'} 1388 + 1150 1389 jiti@2.6.1: 1151 1390 resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} 1152 1391 hasBin: true 1392 + 1393 + js-tokens@9.0.1: 1394 + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} 1153 1395 1154 1396 js-yaml@4.1.1: 1155 1397 resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} ··· 1184 1426 keyv@4.5.4: 1185 1427 resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} 1186 1428 1429 + kleur@4.1.5: 1430 + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} 1431 + engines: {node: '>=6'} 1432 + 1433 + known-css-properties@0.37.0: 1434 + resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} 1435 + 1187 1436 levn@0.4.1: 1188 1437 resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} 1189 1438 engines: {node: '>= 0.8.0'} 1190 1439 1440 + lilconfig@2.1.0: 1441 + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} 1442 + engines: {node: '>=10'} 1443 + 1444 + locate-character@3.0.0: 1445 + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} 1446 + 1191 1447 locate-path@6.0.0: 1192 1448 resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} 1193 1449 engines: {node: '>=10'} ··· 1202 1458 magic-string@0.30.21: 1203 1459 resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} 1204 1460 1461 + magicast@0.5.1: 1462 + resolution: {integrity: sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==} 1463 + 1464 + make-dir@4.0.0: 1465 + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} 1466 + engines: {node: '>=10'} 1467 + 1205 1468 mdn-data@2.12.2: 1206 1469 resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} 1207 1470 ··· 1211 1474 minimatch@9.0.5: 1212 1475 resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 1213 1476 engines: {node: '>=16 || 14 >=14.17'} 1477 + 1478 + mri@1.2.0: 1479 + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} 1480 + engines: {node: '>=4'} 1481 + 1482 + mrmime@2.0.1: 1483 + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} 1484 + engines: {node: '>=10'} 1214 1485 1215 1486 ms@2.1.3: 1216 1487 resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} ··· 1286 1557 resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} 1287 1558 engines: {node: '>=12'} 1288 1559 1560 + pixelmatch@7.1.0: 1561 + resolution: {integrity: sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==} 1562 + hasBin: true 1563 + 1289 1564 pkg-types@2.3.0: 1290 1565 resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} 1291 1566 1567 + playwright-core@1.57.0: 1568 + resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==} 1569 + engines: {node: '>=18'} 1570 + hasBin: true 1571 + 1572 + playwright@1.57.0: 1573 + resolution: {integrity: sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==} 1574 + engines: {node: '>=18'} 1575 + hasBin: true 1576 + 1292 1577 pluralize@8.0.0: 1293 1578 resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} 1294 1579 engines: {node: '>=4'} 1295 1580 1581 + pngjs@7.0.0: 1582 + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} 1583 + engines: {node: '>=14.19.0'} 1584 + 1585 + postcss-load-config@3.1.4: 1586 + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} 1587 + engines: {node: '>= 10'} 1588 + peerDependencies: 1589 + postcss: '>=8.0.9' 1590 + ts-node: '>=9.0.0' 1591 + peerDependenciesMeta: 1592 + postcss: 1593 + optional: true 1594 + ts-node: 1595 + optional: true 1596 + 1597 + postcss-safe-parser@7.0.1: 1598 + resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} 1599 + engines: {node: '>=18.0'} 1600 + peerDependencies: 1601 + postcss: ^8.4.31 1602 + 1603 + postcss-scss@4.0.9: 1604 + resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} 1605 + engines: {node: '>=12.0'} 1606 + peerDependencies: 1607 + postcss: ^8.4.29 1608 + 1609 + postcss-selector-parser@7.1.1: 1610 + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} 1611 + engines: {node: '>=4'} 1612 + 1296 1613 postcss@8.5.6: 1297 1614 resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} 1298 1615 engines: {node: ^10 || ^12 || >=14} ··· 1300 1617 prelude-ls@1.2.1: 1301 1618 resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 1302 1619 engines: {node: '>= 0.8.0'} 1620 + 1621 + prettier-plugin-svelte@3.4.1: 1622 + resolution: {integrity: sha512-xL49LCloMoZRvSwa6IEdN2GV6cq2IqpYGstYtMT+5wmml1/dClEoI0MZR78MiVPpu6BdQFfN0/y73yO6+br5Pg==} 1623 + peerDependencies: 1624 + prettier: ^3.0.0 1625 + svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0 1626 + 1627 + prettier@3.7.4: 1628 + resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} 1629 + engines: {node: '>=14'} 1630 + hasBin: true 1303 1631 1304 1632 punycode@2.3.1: 1305 1633 resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} ··· 1311 1639 rc9@2.1.2: 1312 1640 resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} 1313 1641 1642 + readdirp@4.1.2: 1643 + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} 1644 + engines: {node: '>= 14.18.0'} 1645 + 1314 1646 readdirp@5.0.0: 1315 1647 resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} 1316 1648 engines: {node: '>= 20.19.0'} ··· 1366 1698 rxjs@7.8.2: 1367 1699 resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} 1368 1700 1701 + sade@1.8.1: 1702 + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} 1703 + engines: {node: '>=6'} 1704 + 1369 1705 safer-buffer@2.1.2: 1370 1706 resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} 1371 1707 ··· 1378 1714 engines: {node: '>=10'} 1379 1715 hasBin: true 1380 1716 1717 + set-cookie-parser@2.7.2: 1718 + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} 1719 + 1381 1720 shebang-command@2.0.0: 1382 1721 resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1383 1722 engines: {node: '>=8'} ··· 1389 1728 siginfo@2.0.0: 1390 1729 resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} 1391 1730 1731 + sirv@3.0.2: 1732 + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} 1733 + engines: {node: '>=18'} 1734 + 1392 1735 source-map-js@1.2.1: 1393 1736 resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 1394 1737 engines: {node: '>=0.10.0'} ··· 1411 1754 resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} 1412 1755 engines: {node: '>=8'} 1413 1756 1757 + svelte-check@4.3.5: 1758 + resolution: {integrity: sha512-e4VWZETyXaKGhpkxOXP+B/d0Fp/zKViZoJmneZWe/05Y2aqSKj3YN2nLfYPJBQ87WEiY4BQCQ9hWGu9mPT1a1Q==} 1759 + engines: {node: '>= 18.0.0'} 1760 + hasBin: true 1761 + peerDependencies: 1762 + svelte: ^4.0.0 || ^5.0.0-next.0 1763 + typescript: '>=5.0.0' 1764 + 1765 + svelte-eslint-parser@1.4.1: 1766 + resolution: {integrity: sha512-1eqkfQ93goAhjAXxZiu1SaKI9+0/sxp4JIWQwUpsz7ybehRE5L8dNuz7Iry7K22R47p5/+s9EM+38nHV2OlgXA==} 1767 + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0, pnpm: 10.24.0} 1768 + peerDependencies: 1769 + svelte: ^3.37.0 || ^4.0.0 || ^5.0.0 1770 + peerDependenciesMeta: 1771 + svelte: 1772 + optional: true 1773 + 1774 + svelte@5.46.0: 1775 + resolution: {integrity: sha512-ZhLtvroYxUxr+HQJfMZEDRsGsmU46x12RvAv/zi9584f5KOX7bUrEbhPJ7cKFmUvZTJXi/CFZUYwDC6M1FigPw==} 1776 + engines: {node: '>=18'} 1777 + 1414 1778 symbol-tree@3.2.4: 1415 1779 resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} 1416 1780 ··· 1435 1799 tldts@7.0.19: 1436 1800 resolution: {integrity: sha512-8PWx8tvC4jDB39BQw1m4x8y5MH1BcQ5xHeL2n7UVFulMPH/3Q0uiamahFJ3lXA0zO2SUyRXuVVbWSDmstlt9YA==} 1437 1801 hasBin: true 1802 + 1803 + totalist@3.0.1: 1804 + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} 1805 + engines: {node: '>=6'} 1438 1806 1439 1807 tough-cookie@6.0.0: 1440 1808 resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} ··· 1523 1891 uri-js@4.4.1: 1524 1892 resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1525 1893 1894 + util-deprecate@1.0.2: 1895 + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 1896 + 1897 + uuid@11.1.0: 1898 + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} 1899 + hasBin: true 1900 + 1526 1901 uuid@13.0.0: 1527 1902 resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==} 1528 1903 hasBin: true 1904 + 1905 + vite-plugin-devtools-json@1.0.0: 1906 + resolution: {integrity: sha512-MobvwqX76Vqt/O4AbnNMNWoXWGrKUqZbphCUle/J2KXH82yKQiunOeKnz/nqEPosPsoWWPP9FtNuPBSYpiiwkw==} 1907 + peerDependencies: 1908 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 1529 1909 1530 1910 vite@7.3.0: 1531 1911 resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==} ··· 1566 1946 optional: true 1567 1947 yaml: 1568 1948 optional: true 1949 + 1950 + vitefu@1.1.1: 1951 + resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} 1952 + peerDependencies: 1953 + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 1954 + peerDependenciesMeta: 1955 + vite: 1956 + optional: true 1957 + 1958 + vitest-browser-svelte@2.0.1: 1959 + resolution: {integrity: sha512-z7GFio7vxaOolY+xwPUMEKuwL4KcPzB8+bepA9F0Phqag/TJ4j7IAGSwm4Y/FBh7KznsP+7aEIllMay0qDpFXw==} 1960 + peerDependencies: 1961 + svelte: ^3 || ^4 || ^5 || ^5.0.0-next.0 1962 + vitest: ^4.0.0 1569 1963 1570 1964 vitest@4.0.16: 1571 1965 resolution: {integrity: sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==} ··· 1654 2048 xmlchars@2.2.0: 1655 2049 resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} 1656 2050 2051 + yaml@1.10.2: 2052 + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} 2053 + engines: {node: '>= 6'} 2054 + 1657 2055 yaml@2.8.2: 1658 2056 resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} 1659 2057 engines: {node: '>= 14.6'} ··· 1662 2060 yocto-queue@0.1.0: 1663 2061 resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 1664 2062 engines: {node: '>=10'} 2063 + 2064 + zimmerframe@1.1.4: 2065 + resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} 1665 2066 1666 2067 snapshots: 1667 2068 ··· 1705 2106 dependencies: 1706 2107 '@babel/helper-string-parser': 7.27.1 1707 2108 '@babel/helper-validator-identifier': 7.28.5 2109 + 2110 + '@bcoe/v8-coverage@1.0.2': {} 1708 2111 1709 2112 '@csstools/color-helpers@5.1.0': {} 1710 2113 ··· 1856 2259 1857 2260 '@eslint-community/regexpp@4.12.2': {} 1858 2261 2262 + '@eslint/compat@1.4.1(eslint@9.39.2(jiti@2.6.1))': 2263 + dependencies: 2264 + '@eslint/core': 0.17.0 2265 + optionalDependencies: 2266 + eslint: 9.39.2(jiti@2.6.1) 2267 + 1859 2268 '@eslint/config-array@0.21.1': 1860 2269 dependencies: 1861 2270 '@eslint/object-schema': 2.1.7 ··· 1911 2320 '@jridgewell/sourcemap-codec': 1.5.5 1912 2321 '@jridgewell/trace-mapping': 0.3.31 1913 2322 2323 + '@jridgewell/remapping@2.3.5': 2324 + dependencies: 2325 + '@jridgewell/gen-mapping': 0.3.13 2326 + '@jridgewell/trace-mapping': 0.3.31 2327 + 1914 2328 '@jridgewell/resolve-uri@3.1.2': {} 1915 2329 1916 2330 '@jridgewell/sourcemap-codec@1.5.5': {} ··· 1928 2342 optional: true 1929 2343 1930 2344 '@oxc-project/types@0.103.0': {} 2345 + 2346 + '@polka/url@1.0.0-next.29': {} 1931 2347 1932 2348 '@quansync/fs@1.0.0': 1933 2349 dependencies: ··· 2044 2460 2045 2461 '@standard-schema/spec@1.1.0': {} 2046 2462 2463 + '@sveltejs/acorn-typescript@1.0.8(acorn@8.15.0)': 2464 + dependencies: 2465 + acorn: 8.15.0 2466 + 2467 + '@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.46.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)))': 2468 + dependencies: 2469 + '@sveltejs/kit': 2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.46.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)) 2470 + 2471 + '@sveltejs/kit@2.49.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.46.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2))': 2472 + dependencies: 2473 + '@standard-schema/spec': 1.1.0 2474 + '@sveltejs/acorn-typescript': 1.0.8(acorn@8.15.0) 2475 + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.46.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)) 2476 + '@types/cookie': 0.6.0 2477 + acorn: 8.15.0 2478 + cookie: 0.6.0 2479 + devalue: 5.6.1 2480 + esm-env: 1.2.2 2481 + kleur: 4.1.5 2482 + magic-string: 0.30.21 2483 + mrmime: 2.0.1 2484 + sade: 1.8.1 2485 + set-cookie-parser: 2.7.2 2486 + sirv: 3.0.2 2487 + svelte: 5.46.0 2488 + vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2) 2489 + 2490 + '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.46.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2))': 2491 + dependencies: 2492 + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.46.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)) 2493 + debug: 4.4.3 2494 + svelte: 5.46.0 2495 + vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2) 2496 + transitivePeerDependencies: 2497 + - supports-color 2498 + 2499 + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2))': 2500 + dependencies: 2501 + '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)))(svelte@5.46.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)) 2502 + debug: 4.4.3 2503 + deepmerge: 4.3.1 2504 + magic-string: 0.30.21 2505 + svelte: 5.46.0 2506 + vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2) 2507 + vitefu: 1.1.1(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)) 2508 + transitivePeerDependencies: 2509 + - supports-color 2510 + 2047 2511 '@tybys/wasm-util@0.10.1': 2048 2512 dependencies: 2049 2513 tslib: 2.8.1 ··· 2054 2518 '@types/deep-eql': 4.0.2 2055 2519 assertion-error: 2.0.1 2056 2520 2521 + '@types/cookie@0.6.0': {} 2522 + 2057 2523 '@types/deep-eql@4.0.2': {} 2058 2524 2059 2525 '@types/estree@1.0.8': {} ··· 2065 2531 parse5: 7.3.0 2066 2532 2067 2533 '@types/json-schema@7.0.15': {} 2534 + 2535 + '@types/node@24.10.4': 2536 + dependencies: 2537 + undici-types: 7.16.0 2068 2538 2069 2539 '@types/node@25.0.3': 2070 2540 dependencies: ··· 2163 2633 '@typescript-eslint/types': 8.50.0 2164 2634 eslint-visitor-keys: 4.2.1 2165 2635 2636 + '@vitest/browser-playwright@4.0.16(playwright@1.57.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.16)': 2637 + dependencies: 2638 + '@vitest/browser': 4.0.16(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.16) 2639 + '@vitest/mocker': 4.0.16(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)) 2640 + playwright: 1.57.0 2641 + tinyrainbow: 3.0.3 2642 + vitest: 4.0.16(@types/node@24.10.4)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@27.3.0)(yaml@2.8.2) 2643 + transitivePeerDependencies: 2644 + - bufferutil 2645 + - msw 2646 + - utf-8-validate 2647 + - vite 2648 + 2649 + '@vitest/browser-playwright@4.0.16(playwright@1.57.0)(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.16)': 2650 + dependencies: 2651 + '@vitest/browser': 4.0.16(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.16) 2652 + '@vitest/mocker': 4.0.16(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(yaml@2.8.2)) 2653 + playwright: 1.57.0 2654 + tinyrainbow: 3.0.3 2655 + vitest: 4.0.16(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@27.3.0)(yaml@2.8.2) 2656 + transitivePeerDependencies: 2657 + - bufferutil 2658 + - msw 2659 + - utf-8-validate 2660 + - vite 2661 + optional: true 2662 + 2663 + '@vitest/browser@4.0.16(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.16)': 2664 + dependencies: 2665 + '@vitest/mocker': 4.0.16(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)) 2666 + '@vitest/utils': 4.0.16 2667 + magic-string: 0.30.21 2668 + pixelmatch: 7.1.0 2669 + pngjs: 7.0.0 2670 + sirv: 3.0.2 2671 + tinyrainbow: 3.0.3 2672 + vitest: 4.0.16(@types/node@24.10.4)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@27.3.0)(yaml@2.8.2) 2673 + ws: 8.18.3 2674 + transitivePeerDependencies: 2675 + - bufferutil 2676 + - msw 2677 + - utf-8-validate 2678 + - vite 2679 + 2680 + '@vitest/browser@4.0.16(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.16)': 2681 + dependencies: 2682 + '@vitest/mocker': 4.0.16(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(yaml@2.8.2)) 2683 + '@vitest/utils': 4.0.16 2684 + magic-string: 0.30.21 2685 + pixelmatch: 7.1.0 2686 + pngjs: 7.0.0 2687 + sirv: 3.0.2 2688 + tinyrainbow: 3.0.3 2689 + vitest: 4.0.16(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@27.3.0)(yaml@2.8.2) 2690 + ws: 8.18.3 2691 + transitivePeerDependencies: 2692 + - bufferutil 2693 + - msw 2694 + - utf-8-validate 2695 + - vite 2696 + optional: true 2697 + 2698 + '@vitest/coverage-v8@4.0.16(@vitest/browser@4.0.16(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.16))(vitest@4.0.16)': 2699 + dependencies: 2700 + '@bcoe/v8-coverage': 1.0.2 2701 + '@vitest/utils': 4.0.16 2702 + ast-v8-to-istanbul: 0.3.9 2703 + istanbul-lib-coverage: 3.2.2 2704 + istanbul-lib-report: 3.0.1 2705 + istanbul-lib-source-maps: 5.0.6 2706 + istanbul-reports: 3.2.0 2707 + magicast: 0.5.1 2708 + obug: 2.1.1 2709 + std-env: 3.10.0 2710 + tinyrainbow: 3.0.3 2711 + vitest: 4.0.16(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@27.3.0)(yaml@2.8.2) 2712 + optionalDependencies: 2713 + '@vitest/browser': 4.0.16(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.16) 2714 + transitivePeerDependencies: 2715 + - supports-color 2716 + 2166 2717 '@vitest/expect@4.0.16': 2167 2718 dependencies: 2168 2719 '@standard-schema/spec': 1.1.0 ··· 2172 2723 chai: 6.2.1 2173 2724 tinyrainbow: 3.0.3 2174 2725 2726 + '@vitest/mocker@4.0.16(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2))': 2727 + dependencies: 2728 + '@vitest/spy': 4.0.16 2729 + estree-walker: 3.0.3 2730 + magic-string: 0.30.21 2731 + optionalDependencies: 2732 + vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2) 2733 + 2175 2734 '@vitest/mocker@4.0.16(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(yaml@2.8.2))': 2176 2735 dependencies: 2177 2736 '@vitest/spy': 4.0.16 ··· 2227 2786 2228 2787 args-tokenizer@0.3.0: {} 2229 2788 2789 + aria-query@5.3.2: {} 2790 + 2230 2791 assertion-error@2.0.1: {} 2231 2792 2232 2793 ast-kit@2.2.0: 2233 2794 dependencies: 2234 2795 '@babel/parser': 7.28.5 2235 2796 pathe: 2.0.3 2797 + 2798 + ast-v8-to-istanbul@0.3.9: 2799 + dependencies: 2800 + '@jridgewell/trace-mapping': 0.3.31 2801 + estree-walker: 3.0.3 2802 + js-tokens: 9.0.1 2803 + 2804 + axobject-query@4.1.0: {} 2236 2805 2237 2806 balanced-match@1.0.2: {} 2238 2807 ··· 2263 2832 2264 2833 builtin-modules@5.0.0: {} 2265 2834 2266 - bumpp@10.3.2: 2835 + bumpp@10.3.2(magicast@0.5.1): 2267 2836 dependencies: 2268 2837 ansis: 4.2.0 2269 2838 args-tokenizer: 0.3.0 2270 - c12: 3.3.3 2839 + c12: 3.3.3(magicast@0.5.1) 2271 2840 cac: 6.7.14 2272 2841 escalade: 3.2.0 2273 2842 jsonc-parser: 3.3.1 ··· 2279 2848 transitivePeerDependencies: 2280 2849 - magicast 2281 2850 2282 - c12@3.3.3: 2851 + c12@3.3.3(magicast@0.5.1): 2283 2852 dependencies: 2284 2853 chokidar: 5.0.0 2285 2854 confbox: 0.2.2 ··· 2293 2862 perfect-debounce: 2.0.0 2294 2863 pkg-types: 2.3.0 2295 2864 rc9: 2.1.2 2865 + optionalDependencies: 2866 + magicast: 0.5.1 2296 2867 2297 2868 cac@6.7.14: {} 2298 2869 ··· 2309 2880 2310 2881 change-case@5.4.4: {} 2311 2882 2883 + chokidar@4.0.3: 2884 + dependencies: 2885 + readdirp: 4.1.2 2886 + 2312 2887 chokidar@5.0.0: 2313 2888 dependencies: 2314 2889 readdirp: 5.0.0 ··· 2323 2898 dependencies: 2324 2899 escape-string-regexp: 1.0.5 2325 2900 2901 + clsx@2.1.1: {} 2902 + 2326 2903 color-convert@2.0.1: 2327 2904 dependencies: 2328 2905 color-name: 1.1.4 ··· 2334 2911 confbox@0.2.2: {} 2335 2912 2336 2913 consola@3.4.2: {} 2914 + 2915 + cookie@0.6.0: {} 2337 2916 2338 2917 core-js-compat@3.47.0: 2339 2918 dependencies: ··· 2350 2929 mdn-data: 2.12.2 2351 2930 source-map-js: 1.2.1 2352 2931 2932 + cssesc@3.0.0: {} 2933 + 2353 2934 cssstyle@5.3.5: 2354 2935 dependencies: 2355 2936 '@asamuzakjp/css-color': 4.1.1 ··· 2368 2949 decimal.js@10.6.0: {} 2369 2950 2370 2951 deep-is@0.1.4: {} 2952 + 2953 + deepmerge@4.3.1: {} 2371 2954 2372 2955 defu@6.1.4: {} 2373 2956 2374 2957 destr@2.0.5: {} 2375 2958 2959 + devalue@5.6.1: {} 2960 + 2376 2961 dotenv@17.2.3: {} 2377 2962 2378 2963 dprint@0.50.2: ··· 2432 3017 2433 3018 escape-string-regexp@4.0.0: {} 2434 3019 3020 + eslint-config-prettier@10.1.8(eslint@9.39.2(jiti@2.6.1)): 3021 + dependencies: 3022 + eslint: 9.39.2(jiti@2.6.1) 3023 + 3024 + eslint-plugin-svelte@3.13.1(eslint@9.39.2(jiti@2.6.1))(svelte@5.46.0): 3025 + dependencies: 3026 + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.2(jiti@2.6.1)) 3027 + '@jridgewell/sourcemap-codec': 1.5.5 3028 + eslint: 9.39.2(jiti@2.6.1) 3029 + esutils: 2.0.3 3030 + globals: 16.5.0 3031 + known-css-properties: 0.37.0 3032 + postcss: 8.5.6 3033 + postcss-load-config: 3.1.4(postcss@8.5.6) 3034 + postcss-safe-parser: 7.0.1(postcss@8.5.6) 3035 + semver: 7.7.3 3036 + svelte-eslint-parser: 1.4.1(svelte@5.46.0) 3037 + optionalDependencies: 3038 + svelte: 5.46.0 3039 + transitivePeerDependencies: 3040 + - ts-node 3041 + 2435 3042 eslint-plugin-unicorn@62.0.0(eslint@9.39.2(jiti@2.6.1)): 2436 3043 dependencies: 2437 3044 '@babel/helper-validator-identifier': 7.28.5 ··· 2504 3111 transitivePeerDependencies: 2505 3112 - supports-color 2506 3113 3114 + esm-env@1.2.2: {} 3115 + 2507 3116 espree@10.4.0: 2508 3117 dependencies: 2509 3118 acorn: 8.15.0 ··· 2513 3122 esquery@1.6.0: 2514 3123 dependencies: 2515 3124 estraverse: 5.3.0 3125 + 3126 + esrap@2.2.1: 3127 + dependencies: 3128 + '@jridgewell/sourcemap-codec': 1.5.5 2516 3129 2517 3130 esrecurse@4.3.0: 2518 3131 dependencies: ··· 2557 3170 keyv: 4.5.4 2558 3171 2559 3172 flatted@3.3.3: {} 3173 + 3174 + fsevents@2.3.2: 3175 + optional: true 2560 3176 2561 3177 fsevents@2.3.3: 2562 3178 optional: true ··· 2590 3206 dependencies: 2591 3207 whatwg-encoding: 3.1.1 2592 3208 3209 + html-escaper@2.0.2: {} 3210 + 2593 3211 http-proxy-agent@7.0.2: 2594 3212 dependencies: 2595 3213 agent-base: 7.1.4 ··· 2635 3253 2636 3254 is-potential-custom-element-name@1.0.1: {} 2637 3255 3256 + is-reference@3.0.3: 3257 + dependencies: 3258 + '@types/estree': 1.0.8 3259 + 2638 3260 isexe@2.0.0: {} 2639 3261 3262 + istanbul-lib-coverage@3.2.2: {} 3263 + 3264 + istanbul-lib-report@3.0.1: 3265 + dependencies: 3266 + istanbul-lib-coverage: 3.2.2 3267 + make-dir: 4.0.0 3268 + supports-color: 7.2.0 3269 + 3270 + istanbul-lib-source-maps@5.0.6: 3271 + dependencies: 3272 + '@jridgewell/trace-mapping': 0.3.31 3273 + debug: 4.4.3 3274 + istanbul-lib-coverage: 3.2.2 3275 + transitivePeerDependencies: 3276 + - supports-color 3277 + 3278 + istanbul-reports@3.2.0: 3279 + dependencies: 3280 + html-escaper: 2.0.2 3281 + istanbul-lib-report: 3.0.1 3282 + 2640 3283 jiti@2.6.1: {} 3284 + 3285 + js-tokens@9.0.1: {} 2641 3286 2642 3287 js-yaml@4.1.1: 2643 3288 dependencies: ··· 2684 3329 dependencies: 2685 3330 json-buffer: 3.0.1 2686 3331 3332 + kleur@4.1.5: {} 3333 + 3334 + known-css-properties@0.37.0: {} 3335 + 2687 3336 levn@0.4.1: 2688 3337 dependencies: 2689 3338 prelude-ls: 1.2.1 2690 3339 type-check: 0.4.0 2691 3340 3341 + lilconfig@2.1.0: {} 3342 + 3343 + locate-character@3.0.0: {} 3344 + 2692 3345 locate-path@6.0.0: 2693 3346 dependencies: 2694 3347 p-locate: 5.0.0 ··· 2701 3354 dependencies: 2702 3355 '@jridgewell/sourcemap-codec': 1.5.5 2703 3356 3357 + magicast@0.5.1: 3358 + dependencies: 3359 + '@babel/parser': 7.28.5 3360 + '@babel/types': 7.28.5 3361 + source-map-js: 1.2.1 3362 + 3363 + make-dir@4.0.0: 3364 + dependencies: 3365 + semver: 7.7.3 3366 + 2704 3367 mdn-data@2.12.2: {} 2705 3368 2706 3369 minimatch@3.1.2: ··· 2710 3373 minimatch@9.0.5: 2711 3374 dependencies: 2712 3375 brace-expansion: 2.0.2 3376 + 3377 + mri@1.2.0: {} 3378 + 3379 + mrmime@2.0.1: {} 2713 3380 2714 3381 ms@2.1.3: {} 2715 3382 ··· 2776 3443 2777 3444 picomatch@4.0.3: {} 2778 3445 3446 + pixelmatch@7.1.0: 3447 + dependencies: 3448 + pngjs: 7.0.0 3449 + 2779 3450 pkg-types@2.3.0: 2780 3451 dependencies: 2781 3452 confbox: 0.2.2 2782 3453 exsolve: 1.0.8 2783 3454 pathe: 2.0.3 2784 3455 3456 + playwright-core@1.57.0: {} 3457 + 3458 + playwright@1.57.0: 3459 + dependencies: 3460 + playwright-core: 1.57.0 3461 + optionalDependencies: 3462 + fsevents: 2.3.2 3463 + 2785 3464 pluralize@8.0.0: {} 2786 3465 3466 + pngjs@7.0.0: {} 3467 + 3468 + postcss-load-config@3.1.4(postcss@8.5.6): 3469 + dependencies: 3470 + lilconfig: 2.1.0 3471 + yaml: 1.10.2 3472 + optionalDependencies: 3473 + postcss: 8.5.6 3474 + 3475 + postcss-safe-parser@7.0.1(postcss@8.5.6): 3476 + dependencies: 3477 + postcss: 8.5.6 3478 + 3479 + postcss-scss@4.0.9(postcss@8.5.6): 3480 + dependencies: 3481 + postcss: 8.5.6 3482 + 3483 + postcss-selector-parser@7.1.1: 3484 + dependencies: 3485 + cssesc: 3.0.0 3486 + util-deprecate: 1.0.2 3487 + 2787 3488 postcss@8.5.6: 2788 3489 dependencies: 2789 3490 nanoid: 3.3.11 ··· 2792 3493 2793 3494 prelude-ls@1.2.1: {} 2794 3495 3496 + prettier-plugin-svelte@3.4.1(prettier@3.7.4)(svelte@5.46.0): 3497 + dependencies: 3498 + prettier: 3.7.4 3499 + svelte: 5.46.0 3500 + 3501 + prettier@3.7.4: {} 3502 + 2795 3503 punycode@2.3.1: {} 2796 3504 2797 3505 quansync@1.0.0: {} ··· 2800 3508 dependencies: 2801 3509 defu: 6.1.4 2802 3510 destr: 2.0.5 3511 + 3512 + readdirp@4.1.2: {} 2803 3513 2804 3514 readdirp@5.0.0: {} 2805 3515 ··· 2882 3592 dependencies: 2883 3593 tslib: 2.8.1 2884 3594 3595 + sade@1.8.1: 3596 + dependencies: 3597 + mri: 1.2.0 3598 + 2885 3599 safer-buffer@2.1.2: {} 2886 3600 2887 3601 saxes@6.0.0: ··· 2890 3604 2891 3605 semver@7.7.3: {} 2892 3606 3607 + set-cookie-parser@2.7.2: {} 3608 + 2893 3609 shebang-command@2.0.0: 2894 3610 dependencies: 2895 3611 shebang-regex: 3.0.0 ··· 2898 3614 2899 3615 siginfo@2.0.0: {} 2900 3616 3617 + sirv@3.0.2: 3618 + dependencies: 3619 + '@polka/url': 1.0.0-next.29 3620 + mrmime: 2.0.1 3621 + totalist: 3.0.1 3622 + 2901 3623 source-map-js@1.2.1: {} 2902 3624 2903 3625 stackback@0.0.2: {} ··· 2912 3634 dependencies: 2913 3635 has-flag: 4.0.0 2914 3636 3637 + svelte-check@4.3.5(picomatch@4.0.3)(svelte@5.46.0)(typescript@5.9.3): 3638 + dependencies: 3639 + '@jridgewell/trace-mapping': 0.3.31 3640 + chokidar: 4.0.3 3641 + fdir: 6.5.0(picomatch@4.0.3) 3642 + picocolors: 1.1.1 3643 + sade: 1.8.1 3644 + svelte: 5.46.0 3645 + typescript: 5.9.3 3646 + transitivePeerDependencies: 3647 + - picomatch 3648 + 3649 + svelte-eslint-parser@1.4.1(svelte@5.46.0): 3650 + dependencies: 3651 + eslint-scope: 8.4.0 3652 + eslint-visitor-keys: 4.2.1 3653 + espree: 10.4.0 3654 + postcss: 8.5.6 3655 + postcss-scss: 4.0.9(postcss@8.5.6) 3656 + postcss-selector-parser: 7.1.1 3657 + optionalDependencies: 3658 + svelte: 5.46.0 3659 + 3660 + svelte@5.46.0: 3661 + dependencies: 3662 + '@jridgewell/remapping': 2.3.5 3663 + '@jridgewell/sourcemap-codec': 1.5.5 3664 + '@sveltejs/acorn-typescript': 1.0.8(acorn@8.15.0) 3665 + '@types/estree': 1.0.8 3666 + acorn: 8.15.0 3667 + aria-query: 5.3.2 3668 + axobject-query: 4.1.0 3669 + clsx: 2.1.1 3670 + devalue: 5.6.1 3671 + esm-env: 1.2.2 3672 + esrap: 2.2.1 3673 + is-reference: 3.0.3 3674 + locate-character: 3.0.0 3675 + magic-string: 0.30.21 3676 + zimmerframe: 1.1.4 3677 + 2915 3678 symbol-tree@3.2.4: {} 2916 3679 2917 3680 tinybench@2.9.0: {} ··· 2931 3694 dependencies: 2932 3695 tldts-core: 7.0.19 2933 3696 3697 + totalist@3.0.1: {} 3698 + 2934 3699 tough-cookie@6.0.0: 2935 3700 dependencies: 2936 3701 tldts: 7.0.19 ··· 3012 3777 dependencies: 3013 3778 punycode: 2.3.1 3014 3779 3780 + util-deprecate@1.0.2: {} 3781 + 3782 + uuid@11.1.0: {} 3783 + 3015 3784 uuid@13.0.0: {} 3016 3785 3786 + vite-plugin-devtools-json@1.0.0(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)): 3787 + dependencies: 3788 + uuid: 11.1.0 3789 + vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2) 3790 + 3791 + vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2): 3792 + dependencies: 3793 + esbuild: 0.27.2 3794 + fdir: 6.5.0(picomatch@4.0.3) 3795 + picomatch: 4.0.3 3796 + postcss: 8.5.6 3797 + rollup: 4.54.0 3798 + tinyglobby: 0.2.15 3799 + optionalDependencies: 3800 + '@types/node': 24.10.4 3801 + fsevents: 2.3.3 3802 + jiti: 2.6.1 3803 + yaml: 2.8.2 3804 + 3017 3805 vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(yaml@2.8.2): 3018 3806 dependencies: 3019 3807 esbuild: 0.27.2 ··· 3028 3816 jiti: 2.6.1 3029 3817 yaml: 2.8.2 3030 3818 3031 - vitest@4.0.16(@types/node@25.0.3)(jiti@2.6.1)(jsdom@27.3.0)(yaml@2.8.2): 3819 + vitefu@1.1.1(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)): 3820 + optionalDependencies: 3821 + vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2) 3822 + 3823 + vitest-browser-svelte@2.0.1(svelte@5.46.0)(vitest@4.0.16): 3824 + dependencies: 3825 + svelte: 5.46.0 3826 + vitest: 4.0.16(@types/node@24.10.4)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@27.3.0)(yaml@2.8.2) 3827 + 3828 + vitest@4.0.16(@types/node@24.10.4)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@27.3.0)(yaml@2.8.2): 3829 + dependencies: 3830 + '@vitest/expect': 4.0.16 3831 + '@vitest/mocker': 4.0.16(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2)) 3832 + '@vitest/pretty-format': 4.0.16 3833 + '@vitest/runner': 4.0.16 3834 + '@vitest/snapshot': 4.0.16 3835 + '@vitest/spy': 4.0.16 3836 + '@vitest/utils': 4.0.16 3837 + es-module-lexer: 1.7.0 3838 + expect-type: 1.3.0 3839 + magic-string: 0.30.21 3840 + obug: 2.1.1 3841 + pathe: 2.0.3 3842 + picomatch: 4.0.3 3843 + std-env: 3.10.0 3844 + tinybench: 2.9.0 3845 + tinyexec: 1.0.2 3846 + tinyglobby: 0.2.15 3847 + tinyrainbow: 3.0.3 3848 + vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2) 3849 + why-is-node-running: 2.3.0 3850 + optionalDependencies: 3851 + '@types/node': 24.10.4 3852 + '@vitest/browser-playwright': 4.0.16(playwright@1.57.0)(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.16) 3853 + jsdom: 27.3.0 3854 + transitivePeerDependencies: 3855 + - jiti 3856 + - less 3857 + - lightningcss 3858 + - msw 3859 + - sass 3860 + - sass-embedded 3861 + - stylus 3862 + - sugarss 3863 + - terser 3864 + - tsx 3865 + - yaml 3866 + 3867 + vitest@4.0.16(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(jsdom@27.3.0)(yaml@2.8.2): 3032 3868 dependencies: 3033 3869 '@vitest/expect': 4.0.16 3034 3870 '@vitest/mocker': 4.0.16(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(yaml@2.8.2)) ··· 3052 3888 why-is-node-running: 2.3.0 3053 3889 optionalDependencies: 3054 3890 '@types/node': 25.0.3 3891 + '@vitest/browser-playwright': 4.0.16(playwright@1.57.0)(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(yaml@2.8.2))(vitest@4.0.16) 3055 3892 jsdom: 27.3.0 3056 3893 transitivePeerDependencies: 3057 3894 - jiti ··· 3100 3937 3101 3938 xmlchars@2.2.0: {} 3102 3939 3940 + yaml@1.10.2: {} 3941 + 3103 3942 yaml@2.8.2: {} 3104 3943 3105 3944 yocto-queue@0.1.0: {} 3945 + 3946 + zimmerframe@1.1.4: {}
+1
pnpm-workspace.yaml
··· 1 1 packages: 2 2 - packages/* 3 + - apps/* 3 4 4 5 onlyBuiltDependencies: 5 6 - dprint