visionset 0.0.1b2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- visionset-0.0.1b2/.agents/skills/backend/kernel-architecture/SKILL.md +95 -0
- visionset-0.0.1b2/.agents/skills/backend/python-reviewer/SKILL.md +110 -0
- visionset-0.0.1b2/.agents/skills/backend/python-setup/SKILL.md +87 -0
- visionset-0.0.1b2/.agents/skills/frontend/annotator-core/SKILL.md +121 -0
- visionset-0.0.1b2/.agents/skills/frontend/nodejs-setup/SKILL.md +66 -0
- visionset-0.0.1b2/.agents/skills/frontend/react-19/SKILL.md +93 -0
- visionset-0.0.1b2/.agents/skills/frontend/typescript/SKILL.md +121 -0
- visionset-0.0.1b2/.agents/skills/infra/docker-dev/SKILL.md +101 -0
- visionset-0.0.1b2/.dockerignore +46 -0
- visionset-0.0.1b2/.editorconfig +15 -0
- visionset-0.0.1b2/.github/dependabot.yml +90 -0
- visionset-0.0.1b2/.github/workflows/ci.yml +567 -0
- visionset-0.0.1b2/.github/workflows/dependabot-auto-merge.yml +33 -0
- visionset-0.0.1b2/.github/workflows/publish-pypi.yml +66 -0
- visionset-0.0.1b2/.gitignore +66 -0
- visionset-0.0.1b2/AGENTS.md +116 -0
- visionset-0.0.1b2/CHANGELOG.md +204 -0
- visionset-0.0.1b2/CONTRIBUTING.md +214 -0
- visionset-0.0.1b2/DESIGN.md +497 -0
- visionset-0.0.1b2/LICENSE +202 -0
- visionset-0.0.1b2/PKG-INFO +221 -0
- visionset-0.0.1b2/README.md +194 -0
- visionset-0.0.1b2/VERSION +1 -0
- visionset-0.0.1b2/docker/api-dev.sh +56 -0
- visionset-0.0.1b2/docker/api.Dockerfile +81 -0
- visionset-0.0.1b2/docker/app-dev.sh +30 -0
- visionset-0.0.1b2/docker/app.Dockerfile +37 -0
- visionset-0.0.1b2/docker/compose.yaml +230 -0
- visionset-0.0.1b2/docker/nginx.conf +103 -0
- visionset-0.0.1b2/docs/README.md +39 -0
- visionset-0.0.1b2/docs/annotations.md +911 -0
- visionset-0.0.1b2/docs/api.md +587 -0
- visionset-0.0.1b2/docs/auth.md +283 -0
- visionset-0.0.1b2/docs/batches.md +270 -0
- visionset-0.0.1b2/docs/cli.md +459 -0
- visionset-0.0.1b2/docs/datasets.md +206 -0
- visionset-0.0.1b2/docs/events.md +151 -0
- visionset-0.0.1b2/docs/examples.md +365 -0
- visionset-0.0.1b2/docs/ingest.md +377 -0
- visionset-0.0.1b2/docs/install.md +104 -0
- visionset-0.0.1b2/docs/jobs.md +210 -0
- visionset-0.0.1b2/docs/mcp-tools.md +60 -0
- visionset-0.0.1b2/docs/mcp-walkthrough.md +380 -0
- visionset-0.0.1b2/docs/mcp.md +328 -0
- visionset-0.0.1b2/docs/media.md +368 -0
- visionset-0.0.1b2/docs/persistence.md +249 -0
- visionset-0.0.1b2/docs/projects.md +121 -0
- visionset-0.0.1b2/docs/releases.md +603 -0
- visionset-0.0.1b2/docs/releasing.md +142 -0
- visionset-0.0.1b2/docs/schemas.md +316 -0
- visionset-0.0.1b2/docs/sources.md +160 -0
- visionset-0.0.1b2/docs/tutorial.md +253 -0
- visionset-0.0.1b2/docs/ui.md +713 -0
- visionset-0.0.1b2/docs/workspaces.md +380 -0
- visionset-0.0.1b2/examples/README.md +161 -0
- visionset-0.0.1b2/examples/cli_end_to_end.sh +151 -0
- visionset-0.0.1b2/examples/http_end_to_end.py +583 -0
- visionset-0.0.1b2/examples/ingest_end_to_end.py +402 -0
- visionset-0.0.1b2/examples/mcp_end_to_end.py +445 -0
- visionset-0.0.1b2/examples/sdk_end_to_end.py +476 -0
- visionset-0.0.1b2/examples/thirty_minute_flow.py +415 -0
- visionset-0.0.1b2/frontend/annotator/eslint.config.js +91 -0
- visionset-0.0.1b2/frontend/annotator/package.json +44 -0
- visionset-0.0.1b2/frontend/annotator/src/adapters/ids.ts +21 -0
- visionset-0.0.1b2/frontend/annotator/src/adapters/react/AnnotationLayer.tsx +68 -0
- visionset-0.0.1b2/frontend/annotator/src/adapters/react/AnnotatorCanvas.tsx +732 -0
- visionset-0.0.1b2/frontend/annotator/src/adapters/react/Shapes.tsx +340 -0
- visionset-0.0.1b2/frontend/annotator/src/adapters/react/TransientLayer.tsx +237 -0
- visionset-0.0.1b2/frontend/annotator/src/adapters/react/hooks.ts +57 -0
- visionset-0.0.1b2/frontend/annotator/src/adapters/react/index.ts +48 -0
- visionset-0.0.1b2/frontend/annotator/src/adapters/react/keyboard.test.ts +95 -0
- visionset-0.0.1b2/frontend/annotator/src/adapters/react/keyboard.ts +99 -0
- visionset-0.0.1b2/frontend/annotator/src/adapters/react/paint.test.ts +220 -0
- visionset-0.0.1b2/frontend/annotator/src/adapters/react/paint.ts +220 -0
- visionset-0.0.1b2/frontend/annotator/src/adapters/react/visibility.test.ts +80 -0
- visionset-0.0.1b2/frontend/annotator/src/adapters/react/visibility.ts +49 -0
- visionset-0.0.1b2/frontend/annotator/src/adapters/viewport.test.ts +200 -0
- visionset-0.0.1b2/frontend/annotator/src/adapters/viewport.ts +172 -0
- visionset-0.0.1b2/frontend/annotator/src/core/_fixture.ts +45 -0
- visionset-0.0.1b2/frontend/annotator/src/core/_random.ts +55 -0
- visionset-0.0.1b2/frontend/annotator/src/core/geometry/bbox.test.ts +340 -0
- visionset-0.0.1b2/frontend/annotator/src/core/geometry/bbox.ts +324 -0
- visionset-0.0.1b2/frontend/annotator/src/core/geometry/clamp.test.ts +18 -0
- visionset-0.0.1b2/frontend/annotator/src/core/geometry/clamp.ts +5 -0
- visionset-0.0.1b2/frontend/annotator/src/core/geometry/hitTest.test.ts +273 -0
- visionset-0.0.1b2/frontend/annotator/src/core/geometry/hitTest.ts +243 -0
- visionset-0.0.1b2/frontend/annotator/src/core/geometry/polygon.test.ts +280 -0
- visionset-0.0.1b2/frontend/annotator/src/core/geometry/polygon.ts +214 -0
- visionset-0.0.1b2/frontend/annotator/src/core/geometry/primitives.test.ts +69 -0
- visionset-0.0.1b2/frontend/annotator/src/core/geometry/primitives.ts +81 -0
- visionset-0.0.1b2/frontend/annotator/src/core/geometry/tolerance.test.ts +129 -0
- visionset-0.0.1b2/frontend/annotator/src/core/geometry/tolerance.ts +183 -0
- visionset-0.0.1b2/frontend/annotator/src/core/geometry/transforms.property.test.ts +372 -0
- visionset-0.0.1b2/frontend/annotator/src/core/ids.ts +26 -0
- visionset-0.0.1b2/frontend/annotator/src/core/input/_palette.ts +212 -0
- visionset-0.0.1b2/frontend/annotator/src/core/input/actions.ts +113 -0
- visionset-0.0.1b2/frontend/annotator/src/core/input/bindings.test.ts +322 -0
- visionset-0.0.1b2/frontend/annotator/src/core/input/bindings.ts +239 -0
- visionset-0.0.1b2/frontend/annotator/src/core/input/index.ts +106 -0
- visionset-0.0.1b2/frontend/annotator/src/core/input/keys.test.ts +121 -0
- visionset-0.0.1b2/frontend/annotator/src/core/input/keys.ts +183 -0
- visionset-0.0.1b2/frontend/annotator/src/core/input/pointer.test.ts +51 -0
- visionset-0.0.1b2/frontend/annotator/src/core/input/pointer.ts +78 -0
- visionset-0.0.1b2/frontend/annotator/src/core/input/runAction.test.ts +292 -0
- visionset-0.0.1b2/frontend/annotator/src/core/input/runAction.ts +211 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/_scene.ts +276 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/affordance.test.ts +441 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/affordance.ts +245 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/bboxTool.test.ts +359 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/draft.test.ts +148 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/draft.ts +99 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/effects.ts +82 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/events.ts +163 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/gestures.test.ts +468 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/machine.property.test.ts +208 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/machine.test.ts +546 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/machine.ts +735 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/polygonTool.test.ts +355 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/runEffects.test.ts +184 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/runEffects.ts +103 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/state.ts +184 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/tags.test.ts +521 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/tags.ts +255 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/target.test.ts +230 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/target.ts +187 -0
- visionset-0.0.1b2/frontend/annotator/src/core/interaction/tool.ts +99 -0
- visionset-0.0.1b2/frontend/annotator/src/core/state/_random.ts +114 -0
- visionset-0.0.1b2/frontend/annotator/src/core/state/_sample.ts +50 -0
- visionset-0.0.1b2/frontend/annotator/src/core/state/commandLog.test.ts +208 -0
- visionset-0.0.1b2/frontend/annotator/src/core/state/commandLog.ts +152 -0
- visionset-0.0.1b2/frontend/annotator/src/core/state/commands.ts +98 -0
- visionset-0.0.1b2/frontend/annotator/src/core/state/document.test.ts +264 -0
- visionset-0.0.1b2/frontend/annotator/src/core/state/document.ts +244 -0
- visionset-0.0.1b2/frontend/annotator/src/core/state/selection.test.ts +211 -0
- visionset-0.0.1b2/frontend/annotator/src/core/state/selection.ts +139 -0
- visionset-0.0.1b2/frontend/annotator/src/core/state/store.property.test.ts +124 -0
- visionset-0.0.1b2/frontend/annotator/src/core/state/store.test.ts +302 -0
- visionset-0.0.1b2/frontend/annotator/src/core/state/store.ts +266 -0
- visionset-0.0.1b2/frontend/annotator/src/core/types.ts +247 -0
- visionset-0.0.1b2/frontend/annotator/src/core/wire.test.ts +496 -0
- visionset-0.0.1b2/frontend/annotator/src/core/wire.ts +547 -0
- visionset-0.0.1b2/frontend/annotator/src/index.ts +249 -0
- visionset-0.0.1b2/frontend/annotator/tsconfig.build.json +8 -0
- visionset-0.0.1b2/frontend/annotator/tsconfig.core.json +39 -0
- visionset-0.0.1b2/frontend/annotator/tsconfig.json +29 -0
- visionset-0.0.1b2/frontend/app/bench/_gestures.ts +61 -0
- visionset-0.0.1b2/frontend/app/bench/_sampler.ts +192 -0
- visionset-0.0.1b2/frontend/app/bench/annotator.bench.ts +324 -0
- visionset-0.0.1b2/frontend/app/cycle/cycle.spec.ts +514 -0
- visionset-0.0.1b2/frontend/app/e2e/_bench.ts +136 -0
- visionset-0.0.1b2/frontend/app/e2e/_frame.ts +246 -0
- visionset-0.0.1b2/frontend/app/e2e/adapter-gaps.spec.ts +133 -0
- visionset-0.0.1b2/frontend/app/e2e/annotate.spec.ts +831 -0
- visionset-0.0.1b2/frontend/app/e2e/bbox.spec.ts +185 -0
- visionset-0.0.1b2/frontend/app/e2e/demo.spec.ts +150 -0
- visionset-0.0.1b2/frontend/app/e2e/keyboard.spec.ts +276 -0
- visionset-0.0.1b2/frontend/app/e2e/navigation.spec.ts +251 -0
- visionset-0.0.1b2/frontend/app/e2e/panel.spec.ts +151 -0
- visionset-0.0.1b2/frontend/app/e2e/perf.spec.ts +286 -0
- visionset-0.0.1b2/frontend/app/e2e/polygon.spec.ts +372 -0
- visionset-0.0.1b2/frontend/app/e2e/shell.spec.ts +312 -0
- visionset-0.0.1b2/frontend/app/e2e/showcase.spec.ts +184 -0
- visionset-0.0.1b2/frontend/app/e2e/styleguide.spec.ts +167 -0
- visionset-0.0.1b2/frontend/app/e2e/surround.spec.ts +263 -0
- visionset-0.0.1b2/frontend/app/e2e/tags.spec.ts +127 -0
- visionset-0.0.1b2/frontend/app/e2e/viewport.spec.ts +178 -0
- visionset-0.0.1b2/frontend/app/eslint.config.js +8 -0
- visionset-0.0.1b2/frontend/app/index.html +26 -0
- visionset-0.0.1b2/frontend/app/package.json +40 -0
- visionset-0.0.1b2/frontend/app/playwright.bench.config.ts +92 -0
- visionset-0.0.1b2/frontend/app/playwright.config.ts +94 -0
- visionset-0.0.1b2/frontend/app/playwright.cycle.config.ts +101 -0
- visionset-0.0.1b2/frontend/app/public/favicon.svg +24 -0
- visionset-0.0.1b2/frontend/app/src/demo/AnnotatorDemo.tsx +469 -0
- visionset-0.0.1b2/frontend/app/src/demo/BenchmarkHost.tsx +159 -0
- visionset-0.0.1b2/frontend/app/src/demo/ShowcaseFrame.tsx +72 -0
- visionset-0.0.1b2/frontend/app/src/demo/ToolStrip.tsx +262 -0
- visionset-0.0.1b2/frontend/app/src/demo/benchScene.ts +271 -0
- visionset-0.0.1b2/frontend/app/src/demo/sampleAsset.ts +72 -0
- visionset-0.0.1b2/frontend/app/src/demo/sampleSchema.ts +41 -0
- visionset-0.0.1b2/frontend/app/src/demo/theme.ts +103 -0
- visionset-0.0.1b2/frontend/app/src/main.tsx +60 -0
- visionset-0.0.1b2/frontend/app/src/routes.tsx +296 -0
- visionset-0.0.1b2/frontend/app/src/shell/AppShell.tsx +253 -0
- visionset-0.0.1b2/frontend/app/src/shell/Gated.tsx +20 -0
- visionset-0.0.1b2/frontend/app/src/shell/NotFound.tsx +31 -0
- visionset-0.0.1b2/frontend/app/src/styleguide/Styleguide.tsx +485 -0
- visionset-0.0.1b2/frontend/app/src/styles.css +15 -0
- visionset-0.0.1b2/frontend/app/tsconfig.e2e.json +26 -0
- visionset-0.0.1b2/frontend/app/tsconfig.json +24 -0
- visionset-0.0.1b2/frontend/app/vite.config.ts +41 -0
- visionset-0.0.1b2/openapi.json +8117 -0
- visionset-0.0.1b2/package.json +26 -0
- visionset-0.0.1b2/pnpm-lock.yaml +4317 -0
- visionset-0.0.1b2/pnpm-workspace.yaml +2 -0
- visionset-0.0.1b2/pyproject.toml +164 -0
- visionset-0.0.1b2/scripts/build_dist.sh +49 -0
- visionset-0.0.1b2/scripts/bundle_static.mjs +22 -0
- visionset-0.0.1b2/scripts/check.sh +127 -0
- visionset-0.0.1b2/scripts/cycle_server.sh +47 -0
- visionset-0.0.1b2/scripts/export_mcp_tools.py +129 -0
- visionset-0.0.1b2/scripts/export_openapi.py +27 -0
- visionset-0.0.1b2/scripts/export_wire_fixtures.py +177 -0
- visionset-0.0.1b2/scripts/generate_client.mjs +465 -0
- visionset-0.0.1b2/scripts/setup_agents.sh +66 -0
- visionset-0.0.1b2/scripts/sync_versions.mjs +84 -0
- visionset-0.0.1b2/src/visionset/__init__.py +10 -0
- visionset-0.0.1b2/src/visionset/_static/.gitkeep +0 -0
- visionset-0.0.1b2/src/visionset/_static/README.md +22 -0
- visionset-0.0.1b2/src/visionset/_static/assets/index-B0OgIhau.css +2 -0
- visionset-0.0.1b2/src/visionset/_static/assets/index-Cq3n2nuZ.js +55 -0
- visionset-0.0.1b2/src/visionset/_static/favicon.svg +24 -0
- visionset-0.0.1b2/src/visionset/_static/index.html +27 -0
- visionset-0.0.1b2/src/visionset/cli/__init__.py +1 -0
- visionset-0.0.1b2/src/visionset/cli/_errors.py +108 -0
- visionset-0.0.1b2/src/visionset/cli/_output.py +105 -0
- visionset-0.0.1b2/src/visionset/cli/_resolve.py +78 -0
- visionset-0.0.1b2/src/visionset/cli/_workspace.py +76 -0
- visionset-0.0.1b2/src/visionset/cli/batches.py +191 -0
- visionset-0.0.1b2/src/visionset/cli/export.py +220 -0
- visionset-0.0.1b2/src/visionset/cli/formats.py +44 -0
- visionset-0.0.1b2/src/visionset/cli/ingest.py +179 -0
- visionset-0.0.1b2/src/visionset/cli/init.py +62 -0
- visionset-0.0.1b2/src/visionset/cli/jobs.py +190 -0
- visionset-0.0.1b2/src/visionset/cli/main.py +76 -0
- visionset-0.0.1b2/src/visionset/cli/mcp.py +109 -0
- visionset-0.0.1b2/src/visionset/cli/projects.py +72 -0
- visionset-0.0.1b2/src/visionset/cli/releases.py +175 -0
- visionset-0.0.1b2/src/visionset/cli/schemas.py +145 -0
- visionset-0.0.1b2/src/visionset/cli/tokens.py +115 -0
- visionset-0.0.1b2/src/visionset/cli/ui.py +135 -0
- visionset-0.0.1b2/src/visionset/formats/__init__.py +7 -0
- visionset-0.0.1b2/src/visionset/formats/_dummy.py +51 -0
- visionset-0.0.1b2/src/visionset/formats/_layout.py +141 -0
- visionset-0.0.1b2/src/visionset/formats/coco/__init__.py +327 -0
- visionset-0.0.1b2/src/visionset/formats/lanes/README.md +9 -0
- visionset-0.0.1b2/src/visionset/formats/lanes/__init__.py +1 -0
- visionset-0.0.1b2/src/visionset/formats/registry.py +78 -0
- visionset-0.0.1b2/src/visionset/formats/voc/__init__.py +269 -0
- visionset-0.0.1b2/src/visionset/formats/yolo/__init__.py +270 -0
- visionset-0.0.1b2/src/visionset/kernel/__init__.py +127 -0
- visionset-0.0.1b2/src/visionset/kernel/adapters/__init__.py +17 -0
- visionset-0.0.1b2/src/visionset/kernel/adapters/_mappers.py +498 -0
- visionset-0.0.1b2/src/visionset/kernel/adapters/_tables.py +550 -0
- visionset-0.0.1b2/src/visionset/kernel/adapters/ffmpeg_video_processor.py +521 -0
- visionset-0.0.1b2/src/visionset/kernel/adapters/filesystem_blob_store.py +51 -0
- visionset-0.0.1b2/src/visionset/kernel/adapters/in_process_event_bus.py +75 -0
- visionset-0.0.1b2/src/visionset/kernel/adapters/migrations.py +88 -0
- visionset-0.0.1b2/src/visionset/kernel/adapters/pillow_image_processor.py +267 -0
- visionset-0.0.1b2/src/visionset/kernel/adapters/sqlite_metadata_store.py +329 -0
- visionset-0.0.1b2/src/visionset/kernel/adapters/stored_token_auth_provider.py +69 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/__init__.py +214 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/annotation.py +53 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/asset.py +139 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/batch.py +80 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/dataset.py +145 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/events.py +162 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/geometry.py +95 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/ingest.py +226 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/media.py +153 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/names.py +33 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/partition.py +109 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/project.py +97 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/release.py +620 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/schema.py +230 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/schema_diff.py +218 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/source.py +186 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/task.py +174 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/token.py +157 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/transitions.py +41 -0
- visionset-0.0.1b2/src/visionset/kernel/domain/workspace.py +14 -0
- visionset-0.0.1b2/src/visionset/kernel/errors.py +735 -0
- visionset-0.0.1b2/src/visionset/kernel/ports/__init__.py +48 -0
- visionset-0.0.1b2/src/visionset/kernel/ports/auth_provider.py +31 -0
- visionset-0.0.1b2/src/visionset/kernel/ports/blob_store.py +12 -0
- visionset-0.0.1b2/src/visionset/kernel/ports/event_bus.py +34 -0
- visionset-0.0.1b2/src/visionset/kernel/ports/exporter.py +147 -0
- visionset-0.0.1b2/src/visionset/kernel/ports/image_processor.py +72 -0
- visionset-0.0.1b2/src/visionset/kernel/ports/importer.py +18 -0
- visionset-0.0.1b2/src/visionset/kernel/ports/metadata_store.py +178 -0
- visionset-0.0.1b2/src/visionset/kernel/ports/model_provider.py +14 -0
- visionset-0.0.1b2/src/visionset/kernel/ports/video_processor.py +106 -0
- visionset-0.0.1b2/src/visionset/kernel/services/__init__.py +45 -0
- visionset-0.0.1b2/src/visionset/kernel/services/annotation_service.py +501 -0
- visionset-0.0.1b2/src/visionset/kernel/services/batch_service.py +572 -0
- visionset-0.0.1b2/src/visionset/kernel/services/dataset_service.py +367 -0
- visionset-0.0.1b2/src/visionset/kernel/services/ingest_service.py +984 -0
- visionset-0.0.1b2/src/visionset/kernel/services/job_service.py +366 -0
- visionset-0.0.1b2/src/visionset/kernel/services/project_service.py +316 -0
- visionset-0.0.1b2/src/visionset/kernel/services/release_service.py +859 -0
- visionset-0.0.1b2/src/visionset/kernel/services/schema_service.py +378 -0
- visionset-0.0.1b2/src/visionset/kernel/services/source_service.py +264 -0
- visionset-0.0.1b2/src/visionset/kernel/services/token_service.py +223 -0
- visionset-0.0.1b2/src/visionset/kernel/services/workspace_service.py +578 -0
- visionset-0.0.1b2/src/visionset/mcp/__init__.py +1 -0
- visionset-0.0.1b2/src/visionset/mcp/_errors.py +179 -0
- visionset-0.0.1b2/src/visionset/mcp/_resolve.py +110 -0
- visionset-0.0.1b2/src/visionset/mcp/_workspace.py +55 -0
- visionset-0.0.1b2/src/visionset/mcp/annotations.py +213 -0
- visionset-0.0.1b2/src/visionset/mcp/assets.py +175 -0
- visionset-0.0.1b2/src/visionset/mcp/batches.py +238 -0
- visionset-0.0.1b2/src/visionset/mcp/datasets.py +45 -0
- visionset-0.0.1b2/src/visionset/mcp/formats.py +30 -0
- visionset-0.0.1b2/src/visionset/mcp/jobs.py +152 -0
- visionset-0.0.1b2/src/visionset/mcp/main.py +228 -0
- visionset-0.0.1b2/src/visionset/mcp/projects.py +111 -0
- visionset-0.0.1b2/src/visionset/mcp/releases.py +221 -0
- visionset-0.0.1b2/src/visionset/mcp/schemas.py +195 -0
- visionset-0.0.1b2/src/visionset/mcp/sources.py +163 -0
- visionset-0.0.1b2/src/visionset/py.typed +0 -0
- visionset-0.0.1b2/src/visionset/server/__init__.py +10 -0
- visionset-0.0.1b2/src/visionset/server/dependencies.py +296 -0
- visionset-0.0.1b2/src/visionset/server/errors.py +486 -0
- visionset-0.0.1b2/src/visionset/server/main.py +302 -0
- visionset-0.0.1b2/src/visionset/server/models.py +1340 -0
- visionset-0.0.1b2/src/visionset/server/routes/__init__.py +78 -0
- visionset-0.0.1b2/src/visionset/server/routes/annotations.py +149 -0
- visionset-0.0.1b2/src/visionset/server/routes/assets.py +206 -0
- visionset-0.0.1b2/src/visionset/server/routes/batches.py +223 -0
- visionset-0.0.1b2/src/visionset/server/routes/datasets.py +150 -0
- visionset-0.0.1b2/src/visionset/server/routes/formats.py +42 -0
- visionset-0.0.1b2/src/visionset/server/routes/ingest.py +70 -0
- visionset-0.0.1b2/src/visionset/server/routes/jobs.py +137 -0
- visionset-0.0.1b2/src/visionset/server/routes/projects.py +95 -0
- visionset-0.0.1b2/src/visionset/server/routes/releases.py +291 -0
- visionset-0.0.1b2/src/visionset/server/routes/schemas.py +147 -0
- visionset-0.0.1b2/src/visionset/server/routes/sources.py +180 -0
- visionset-0.0.1b2/src/visionset/server/runner.py +77 -0
- visionset-0.0.1b2/src/visionset/server/session.py +248 -0
- visionset-0.0.1b2/src/visionset/server/uploads.py +160 -0
- visionset-0.0.1b2/src/visionset/wire/__init__.py +506 -0
- visionset-0.0.1b2/tests/architecture/test_kernel_purity.py +26 -0
- visionset-0.0.1b2/tests/architecture/test_tracked_file_sizes.py +106 -0
- visionset-0.0.1b2/tests/cli/_flow.py +157 -0
- visionset-0.0.1b2/tests/cli/test_batch_commands.py +190 -0
- visionset-0.0.1b2/tests/cli/test_export_commands.py +588 -0
- visionset-0.0.1b2/tests/cli/test_full_cycle.py +130 -0
- visionset-0.0.1b2/tests/cli/test_ingest_commands.py +178 -0
- visionset-0.0.1b2/tests/cli/test_init.py +120 -0
- visionset-0.0.1b2/tests/cli/test_job_commands.py +179 -0
- visionset-0.0.1b2/tests/cli/test_json_contract.py +197 -0
- visionset-0.0.1b2/tests/cli/test_mcp_command.py +249 -0
- visionset-0.0.1b2/tests/cli/test_output.py +52 -0
- visionset-0.0.1b2/tests/cli/test_project_commands.py +130 -0
- visionset-0.0.1b2/tests/cli/test_release_commands.py +199 -0
- visionset-0.0.1b2/tests/cli/test_schema_commands.py +159 -0
- visionset-0.0.1b2/tests/cli/test_tokens.py +267 -0
- visionset-0.0.1b2/tests/cli/test_ui.py +249 -0
- visionset-0.0.1b2/tests/cli/test_workspace_option.py +151 -0
- visionset-0.0.1b2/tests/examples/test_cli_end_to_end.py +113 -0
- visionset-0.0.1b2/tests/examples/test_http_end_to_end.py +112 -0
- visionset-0.0.1b2/tests/examples/test_ingest_end_to_end.py +140 -0
- visionset-0.0.1b2/tests/examples/test_mcp_end_to_end.py +125 -0
- visionset-0.0.1b2/tests/examples/test_sdk_end_to_end.py +142 -0
- visionset-0.0.1b2/tests/examples/test_thirty_minute_flow.py +110 -0
- visionset-0.0.1b2/tests/fixtures/media.py +343 -0
- visionset-0.0.1b2/tests/fixtures/samples.py +289 -0
- visionset-0.0.1b2/tests/fixtures/test_media.py +222 -0
- visionset-0.0.1b2/tests/fixtures/wire_annotations.json +163 -0
- visionset-0.0.1b2/tests/formats/test_coco.py +363 -0
- visionset-0.0.1b2/tests/formats/test_coco_smoke.py +259 -0
- visionset-0.0.1b2/tests/formats/test_entry_points.py +20 -0
- visionset-0.0.1b2/tests/formats/test_registry.py +133 -0
- visionset-0.0.1b2/tests/formats/test_report_agreement.py +307 -0
- visionset-0.0.1b2/tests/formats/test_voc.py +378 -0
- visionset-0.0.1b2/tests/formats/test_yolo.py +493 -0
- visionset-0.0.1b2/tests/formats/test_yolo_smoke.py +220 -0
- visionset-0.0.1b2/tests/kernel/test_annotation.py +51 -0
- visionset-0.0.1b2/tests/kernel/test_annotation_service.py +871 -0
- visionset-0.0.1b2/tests/kernel/test_auth_provider.py +217 -0
- visionset-0.0.1b2/tests/kernel/test_batch_service.py +676 -0
- visionset-0.0.1b2/tests/kernel/test_blob_store.py +39 -0
- visionset-0.0.1b2/tests/kernel/test_concurrency.py +205 -0
- visionset-0.0.1b2/tests/kernel/test_dataset_service.py +710 -0
- visionset-0.0.1b2/tests/kernel/test_domain_invariants.py +52 -0
- visionset-0.0.1b2/tests/kernel/test_events.py +601 -0
- visionset-0.0.1b2/tests/kernel/test_geometry.py +140 -0
- visionset-0.0.1b2/tests/kernel/test_image_processor.py +564 -0
- visionset-0.0.1b2/tests/kernel/test_ingest_service.py +2305 -0
- visionset-0.0.1b2/tests/kernel/test_job_service.py +509 -0
- visionset-0.0.1b2/tests/kernel/test_metadata_store.py +641 -0
- visionset-0.0.1b2/tests/kernel/test_migrations.py +210 -0
- visionset-0.0.1b2/tests/kernel/test_partition.py +135 -0
- visionset-0.0.1b2/tests/kernel/test_project_service.py +742 -0
- visionset-0.0.1b2/tests/kernel/test_release.py +250 -0
- visionset-0.0.1b2/tests/kernel/test_release_service.py +1158 -0
- visionset-0.0.1b2/tests/kernel/test_schema_diff.py +165 -0
- visionset-0.0.1b2/tests/kernel/test_schema_service.py +648 -0
- visionset-0.0.1b2/tests/kernel/test_source_service.py +432 -0
- visionset-0.0.1b2/tests/kernel/test_token.py +161 -0
- visionset-0.0.1b2/tests/kernel/test_token_service.py +220 -0
- visionset-0.0.1b2/tests/kernel/test_video_processor.py +565 -0
- visionset-0.0.1b2/tests/kernel/test_workspace_service.py +655 -0
- visionset-0.0.1b2/tests/mcp/_flow.py +180 -0
- visionset-0.0.1b2/tests/mcp/test_agent_walk.py +217 -0
- visionset-0.0.1b2/tests/mcp/test_annotation_tools.py +219 -0
- visionset-0.0.1b2/tests/mcp/test_asset_tools.py +177 -0
- visionset-0.0.1b2/tests/mcp/test_batch_tools.py +271 -0
- visionset-0.0.1b2/tests/mcp/test_ingest_tools.py +192 -0
- visionset-0.0.1b2/tests/mcp/test_job_tools.py +196 -0
- visionset-0.0.1b2/tests/mcp/test_project_tools.py +139 -0
- visionset-0.0.1b2/tests/mcp/test_registration.py +247 -0
- visionset-0.0.1b2/tests/mcp/test_release_tools.py +422 -0
- visionset-0.0.1b2/tests/mcp/test_schema_tools.py +270 -0
- visionset-0.0.1b2/tests/mcp/test_tool_errors.py +185 -0
- visionset-0.0.1b2/tests/mcp/test_tool_reference.py +69 -0
- visionset-0.0.1b2/tests/packaging/test_wheel.py +381 -0
- visionset-0.0.1b2/tests/scripts/annotator_boundary.test.mjs +208 -0
- visionset-0.0.1b2/tests/scripts/checks_wiring.test.mjs +112 -0
- visionset-0.0.1b2/tests/scripts/design_tokens.test.mjs +130 -0
- visionset-0.0.1b2/tests/scripts/e2e_discipline.test.mjs +103 -0
- visionset-0.0.1b2/tests/scripts/favicon.test.mjs +81 -0
- visionset-0.0.1b2/tests/scripts/generate_client.test.mjs +146 -0
- visionset-0.0.1b2/tests/scripts/sync_versions.test.mjs +23 -0
- visionset-0.0.1b2/tests/server/_api.py +64 -0
- visionset-0.0.1b2/tests/server/_exports.py +142 -0
- visionset-0.0.1b2/tests/server/_flow.py +149 -0
- visionset-0.0.1b2/tests/server/_openapi.py +52 -0
- visionset-0.0.1b2/tests/server/_probe.py +99 -0
- visionset-0.0.1b2/tests/server/_runner.py +63 -0
- visionset-0.0.1b2/tests/server/test_annotations.py +454 -0
- visionset-0.0.1b2/tests/server/test_auth.py +198 -0
- visionset-0.0.1b2/tests/server/test_batches.py +548 -0
- visionset-0.0.1b2/tests/server/test_datasets.py +442 -0
- visionset-0.0.1b2/tests/server/test_downloads.py +396 -0
- visionset-0.0.1b2/tests/server/test_errors.py +402 -0
- visionset-0.0.1b2/tests/server/test_external_client.py +301 -0
- visionset-0.0.1b2/tests/server/test_formats.py +81 -0
- visionset-0.0.1b2/tests/server/test_health.py +33 -0
- visionset-0.0.1b2/tests/server/test_ingest.py +402 -0
- visionset-0.0.1b2/tests/server/test_jobs.py +301 -0
- visionset-0.0.1b2/tests/server/test_openapi_contract.py +192 -0
- visionset-0.0.1b2/tests/server/test_projects.py +415 -0
- visionset-0.0.1b2/tests/server/test_releases.py +569 -0
- visionset-0.0.1b2/tests/server/test_schemas.py +479 -0
- visionset-0.0.1b2/tests/server/test_session.py +307 -0
- visionset-0.0.1b2/tests/server/test_sources.py +339 -0
- visionset-0.0.1b2/tests/server/test_static_ui.py +292 -0
- visionset-0.0.1b2/tests/server/test_uploads.py +162 -0
- visionset-0.0.1b2/tests/server/test_wire_fixtures.py +155 -0
- visionset-0.0.1b2/tests/server/test_wire_models.py +313 -0
- visionset-0.0.1b2/tests/server/test_workspace_dependency.py +214 -0
- visionset-0.0.1b2/tests/test_versioning.py +43 -0
- visionset-0.0.1b2/uv.lock +2367 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kernel-architecture
|
|
3
|
+
description: >
|
|
4
|
+
VisionSet hexagonal architecture: what belongs in kernel/ vs server/, cli/, mcp/, formats/,
|
|
5
|
+
the machine-enforced import boundaries, and how to add a port, an adapter, or a format plugin.
|
|
6
|
+
Trigger: When adding or moving Python modules, designing domain models or ports, writing a
|
|
7
|
+
FastAPI route / Typer command / MCP tool, or when an import contract fails.
|
|
8
|
+
license: Apache-2.0
|
|
9
|
+
metadata:
|
|
10
|
+
author: robomous
|
|
11
|
+
version: "1.0"
|
|
12
|
+
scope: [root, backend]
|
|
13
|
+
auto_invoke:
|
|
14
|
+
- "Adding or moving modules under src/visionset/"
|
|
15
|
+
- "Writing a FastAPI route, Typer command, or MCP tool"
|
|
16
|
+
- "Fixing a lint-imports / architecture test failure"
|
|
17
|
+
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## The shape
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
src/visionset/
|
|
24
|
+
kernel/ domain models + ports (Protocols) + default adapters — framework-free
|
|
25
|
+
server/ FastAPI. Thin: parse -> call SDK -> serialize
|
|
26
|
+
cli/ Typer. Thin: parse -> call SDK -> print
|
|
27
|
+
mcp/ MCP stdio server. Thin: tool -> SDK call
|
|
28
|
+
formats/ importer/exporter plugins, discovered via the `visionset.formats` entry point
|
|
29
|
+
_static/ compiled UI bundle, injected at build time
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Every surface (UI, CLI, MCP, REST) is a **thin client of the same SDK**. If a behavior exists
|
|
33
|
+
in only one of them, it is in the wrong place.
|
|
34
|
+
|
|
35
|
+
## Two rules the machine enforces
|
|
36
|
+
|
|
37
|
+
1. **Kernel purity** — `visionset.kernel` must never import `visionset.server`,
|
|
38
|
+
`visionset.cli`, `visionset.mcp`, `visionset.formats`, nor `fastapi` / `typer` / `mcp` /
|
|
39
|
+
`uvicorn`. Enforced by import-linter contracts in `pyproject.toml` **and** a fresh-process
|
|
40
|
+
test under `tests/architecture/`.
|
|
41
|
+
2. **Delivery clients are siblings** — `server`, `cli`, and `mcp` never import each other.
|
|
42
|
+
Shared logic moves down into the kernel, never sideways.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
uv run lint-imports # both contracts
|
|
46
|
+
uv run pytest tests/architecture
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**If a change fights either boundary, the change is wrong — not the boundary.** Never relax a
|
|
50
|
+
contract in `pyproject.toml` to make a build pass; restructure instead.
|
|
51
|
+
|
|
52
|
+
## Where does this code go?
|
|
53
|
+
|
|
54
|
+
| You are writing… | It goes in |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| A concept with invariants (Dataset, Annotation, Label) | `kernel/domain/` as a pydantic v2 model |
|
|
57
|
+
| A use case / orchestration ("import a dataset") | `kernel/` service — callable from every surface |
|
|
58
|
+
| An interface to the outside world (storage, repo, clock, ids) | `kernel/ports/` as a `Protocol` |
|
|
59
|
+
| A concrete implementation of a port (filesystem, sqlite, uuid4) | `kernel/adapters/` |
|
|
60
|
+
| HTTP shape: routes, request/response models, status codes | `server/` |
|
|
61
|
+
| A `visionset ...` subcommand, flags, human output | `cli/` |
|
|
62
|
+
| An MCP tool declaration and its mapping | `mcp/` |
|
|
63
|
+
| Reading/writing COCO, YOLO, … | `formats/` + an entry point |
|
|
64
|
+
|
|
65
|
+
Rule of thumb: **if it would still make sense with no HTTP, no terminal, and no LLM, it belongs
|
|
66
|
+
in the kernel.**
|
|
67
|
+
|
|
68
|
+
## Adding a port
|
|
69
|
+
|
|
70
|
+
1. Define the `Protocol` in `kernel/ports/` — narrow, domain-worded (`ImageStore`, not
|
|
71
|
+
`S3Client`), fully typed, no framework types in the signature.
|
|
72
|
+
2. Ship a default adapter in `kernel/adapters/` so local-first works with zero configuration.
|
|
73
|
+
3. Inject it — constructor parameter with a default, never a module-level singleton and never
|
|
74
|
+
an import from a delivery module.
|
|
75
|
+
4. Test against the Protocol with a fake; the default adapter gets its own test.
|
|
76
|
+
|
|
77
|
+
## Adding a format plugin
|
|
78
|
+
|
|
79
|
+
- Implement the importer/exporter under `formats/`.
|
|
80
|
+
- Register it in `pyproject.toml` under `[project.entry-points."visionset.formats"]`.
|
|
81
|
+
- Discovery goes through `importlib.metadata` — never a hardcoded registry, never an
|
|
82
|
+
`if fmt == "coco"` chain. Third-party distributions (`visionset-format-x`) register into the
|
|
83
|
+
same group; anything that breaks that breaks the plugin promise.
|
|
84
|
+
- A change to the plugin surface ships with a test proving discoverability via
|
|
85
|
+
`importlib.metadata`.
|
|
86
|
+
|
|
87
|
+
## Delivery-layer discipline
|
|
88
|
+
|
|
89
|
+
- A route/command/tool body should read as: validate input → call one SDK function → shape the
|
|
90
|
+
output. Business logic in a route is a bug.
|
|
91
|
+
- `openapi.json` at the repo root is a **committed contract** consumed by the generated TS
|
|
92
|
+
client. After changing routes or response models:
|
|
93
|
+
`uv run python scripts/export_openapi.py` and commit the diff.
|
|
94
|
+
- Errors: raise domain errors from the kernel; translate them to HTTP status / exit codes at
|
|
95
|
+
the boundary. The kernel never raises `HTTPException`.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: python-reviewer
|
|
3
|
+
description: >
|
|
4
|
+
Reviews and refines VisionSet Python (kernel, FastAPI, Typer, MCP, pydantic v2) for clarity,
|
|
5
|
+
consistency, and architectural fit while preserving behavior exactly. Focuses on recently
|
|
6
|
+
modified code unless told otherwise. Use proactively after Python changes.
|
|
7
|
+
license: Apache-2.0
|
|
8
|
+
metadata:
|
|
9
|
+
author: robomous
|
|
10
|
+
version: "1.0"
|
|
11
|
+
scope: [root, backend]
|
|
12
|
+
auto_invoke: "Reviewing or refactoring Python code"
|
|
13
|
+
model: opus
|
|
14
|
+
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, Task
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
You are an expert Python reviewer for a library-first, hexagonal codebase. Your job is to make
|
|
18
|
+
recently modified code clearer, more consistent, and architecturally correct **without changing
|
|
19
|
+
what it does**. You prefer explicit, readable code over clever compression.
|
|
20
|
+
|
|
21
|
+
## 1. Preserve functionality
|
|
22
|
+
|
|
23
|
+
Never change behavior — only how it is expressed. Same outputs, same errors, same side effects.
|
|
24
|
+
Non-negotiable. If you believe behavior is wrong, report it separately; do not silently "fix" it
|
|
25
|
+
inside a refactor.
|
|
26
|
+
|
|
27
|
+
## 2. Enforce the architecture first
|
|
28
|
+
|
|
29
|
+
Architecture violations outrank style. Check, in order:
|
|
30
|
+
|
|
31
|
+
- Does anything in `visionset/kernel/` import `fastapi`, `typer`, `mcp`, `uvicorn`, or a
|
|
32
|
+
sibling delivery module? → must move.
|
|
33
|
+
- Do `server`, `cli`, or `mcp` import each other? → the shared part moves down into the kernel.
|
|
34
|
+
- Is there business logic inside a route handler, CLI command, or MCP tool? → it belongs in a
|
|
35
|
+
kernel service so every surface gets it.
|
|
36
|
+
- Is a concrete dependency (filesystem, DB, clock, uuid) used directly instead of through a
|
|
37
|
+
port `Protocol`? → introduce/use the port.
|
|
38
|
+
- Is a format handled by a hardcoded branch instead of the `visionset.formats` entry-point
|
|
39
|
+
group? → use discovery.
|
|
40
|
+
|
|
41
|
+
See the `kernel-architecture` skill for where each kind of code belongs.
|
|
42
|
+
|
|
43
|
+
## 3. Apply project standards
|
|
44
|
+
|
|
45
|
+
- **ruff** owns formatting and import order (line length 100, rules `E, F, I, UP, B, SIM`).
|
|
46
|
+
Never introduce black/isort/flake8. Remove `# noqa` that hides a real fix.
|
|
47
|
+
- Kernel code is fully typed under strict mypy: no untyped defs, no bare `Any`, no
|
|
48
|
+
`disallow_any_generics` escapes. Annotate new public functions outside the kernel too.
|
|
49
|
+
- Naming: `snake_case` functions/variables, `PascalCase` classes, `UPPER_CASE` constants.
|
|
50
|
+
Name for the domain (`ImageStore`), not the technology (`S3Client`).
|
|
51
|
+
- Imports at the top of the file; a function-local import needs a one-line reason.
|
|
52
|
+
- f-strings over `%`/`format()`. `pathlib` over `os.path`. `enum.StrEnum` over string literals
|
|
53
|
+
scattered across modules.
|
|
54
|
+
|
|
55
|
+
## 4. pydantic v2 and domain modelling
|
|
56
|
+
|
|
57
|
+
- Invariants live in the model (`Field` constraints, `@field_validator`, `@model_validator`),
|
|
58
|
+
not re-checked at every call site.
|
|
59
|
+
- Prefer `model_validate` / `model_dump` (v2 API); flag leftover v1 idioms (`parse_obj`,
|
|
60
|
+
`.dict()`, `class Config`, `@validator`).
|
|
61
|
+
- Domain models are immutable where it costs nothing (`model_config = ConfigDict(frozen=True)`)
|
|
62
|
+
and never carry transport concerns (HTTP status, CLI flags, MCP schemas).
|
|
63
|
+
- Value objects over primitive soup: an id type beats a bare `str` threaded through ten calls.
|
|
64
|
+
|
|
65
|
+
## 5. Delivery layers
|
|
66
|
+
|
|
67
|
+
**FastAPI (`server/`)**
|
|
68
|
+
- Handlers stay thin: validate → one SDK call → shape response. Explicit `response_model` and
|
|
69
|
+
status codes.
|
|
70
|
+
- Dependencies via `Depends`, not module-level globals.
|
|
71
|
+
- Domain errors are translated to HTTP at the boundary; the kernel never raises
|
|
72
|
+
`HTTPException`.
|
|
73
|
+
- Any route or response-model change requires re-exporting `openapi.json`
|
|
74
|
+
(`uv run python scripts/export_openapi.py`) — it is a committed contract.
|
|
75
|
+
|
|
76
|
+
**Typer (`cli/`)**
|
|
77
|
+
- One command = one SDK call plus presentation. Exit codes are meaningful.
|
|
78
|
+
- Human-readable output by default; keep machine-readable output an explicit flag.
|
|
79
|
+
|
|
80
|
+
**MCP (`mcp/`)**
|
|
81
|
+
- A tool is a typed mapping onto an SDK function. Tool descriptions state what the tool does
|
|
82
|
+
and when to use it — they are the model's only documentation.
|
|
83
|
+
|
|
84
|
+
## 6. Enhance clarity
|
|
85
|
+
|
|
86
|
+
- Early returns over deep nesting; delete dead code and premature abstractions.
|
|
87
|
+
- Remove comments that restate the code; keep comments that record *why* (a constraint, a
|
|
88
|
+
boundary, a decision).
|
|
89
|
+
- Consolidate related logic; extract a well-named function instead of a comment-delimited block.
|
|
90
|
+
- Narrow exception handling — no bare `except:`, no `except Exception` that swallows.
|
|
91
|
+
- Prefer the stdlib over a new dependency; a new runtime dependency ships to every wheel user.
|
|
92
|
+
|
|
93
|
+
## 7. Tests
|
|
94
|
+
|
|
95
|
+
- New kernel behavior ships with tests under `tests/kernel/`; architecture rules under
|
|
96
|
+
`tests/architecture/`; plugin-surface changes prove discoverability via `importlib.metadata`.
|
|
97
|
+
- Test behavior through public entry points, not private internals.
|
|
98
|
+
- Ports are tested with fakes; each default adapter has its own test.
|
|
99
|
+
- Never commit fixture media — `**/workspace-data/` stays ignored.
|
|
100
|
+
|
|
101
|
+
## Output format
|
|
102
|
+
|
|
103
|
+
Report as:
|
|
104
|
+
|
|
105
|
+
1. **Architecture** — boundary violations (blocking).
|
|
106
|
+
2. **Correctness risks** — behavior you suspect is wrong, stated as a question, not silently
|
|
107
|
+
changed.
|
|
108
|
+
3. **Refinements applied** — file:line, one line each.
|
|
109
|
+
4. **Checks run** — the actual commands and their result (`ruff`, `pytest`, `lint-imports`,
|
|
110
|
+
`mypy` when the kernel changed). Report failures verbatim; never claim green without running.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: python-setup
|
|
3
|
+
description: >
|
|
4
|
+
Python environment and conventions for VisionSet. uv workspace, ruff (lint + format),
|
|
5
|
+
mypy (strict on the kernel), import-linter, pytest.
|
|
6
|
+
Trigger: When running Python scripts, formatting/linting Python code, adding dependencies,
|
|
7
|
+
or setting up the backend environment.
|
|
8
|
+
license: Apache-2.0
|
|
9
|
+
metadata:
|
|
10
|
+
author: robomous
|
|
11
|
+
version: "1.0"
|
|
12
|
+
scope: [root, backend]
|
|
13
|
+
auto_invoke: "Running Python scripts or setting up the Python environment"
|
|
14
|
+
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Environment
|
|
18
|
+
|
|
19
|
+
- One Python distribution: `visionset`, living in `src/visionset/`. There is no `backend/`
|
|
20
|
+
folder and no `visionset_*` sibling packages.
|
|
21
|
+
- Requires **Python 3.12+**. Package manager: **uv** (never pip, never poetry).
|
|
22
|
+
- All commands run from the **repo root** — there is no per-component venv.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
uv sync # create/refresh .venv with the package (editable) + dev group
|
|
26
|
+
uv run python -c "..." # run anything inside the env
|
|
27
|
+
uv run pytest # tests
|
|
28
|
+
uv add <pkg> # runtime dependency -> [project].dependencies
|
|
29
|
+
uv add --dev <pkg> # dev dependency -> [dependency-groups].dev
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Never edit `uv.lock` by hand; never `pip install` into `.venv`.
|
|
33
|
+
|
|
34
|
+
## Checks that must stay green
|
|
35
|
+
|
|
36
|
+
| Check | Command |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| Tests | `uv run pytest` |
|
|
39
|
+
| Import contracts (architecture) | `uv run lint-imports` |
|
|
40
|
+
| Kernel type-safety (strict) | `uv run mypy src/visionset/kernel` |
|
|
41
|
+
| Lint | `uv run ruff check .` |
|
|
42
|
+
| Format | `uv run ruff format .` |
|
|
43
|
+
| OpenAPI contract | `uv run python scripts/export_openapi.py` (commit the diff) |
|
|
44
|
+
|
|
45
|
+
Run at minimum `ruff check`, `ruff format`, `pytest`, and `lint-imports` after any Python
|
|
46
|
+
change. If you touched the kernel, add `mypy`. If you touched FastAPI routes or response
|
|
47
|
+
models, re-export `openapi.json` — it is a committed contract, a stale one is a bug.
|
|
48
|
+
|
|
49
|
+
## Formatting and lint
|
|
50
|
+
|
|
51
|
+
**ruff is both the formatter and the linter** — do not introduce black, isort, flake8, or
|
|
52
|
+
autopep8. Line length is 100. Rule set: `E, F, I, UP, B, SIM` (configured in `pyproject.toml`;
|
|
53
|
+
change it there, never with scattered `# noqa`).
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
uv run ruff format <path> # format
|
|
57
|
+
uv run ruff check --fix <path>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Imports (REQUIRED)
|
|
61
|
+
|
|
62
|
+
- Imports go at the top of the file. `ruff`'s `I` rules own the ordering — let the tool sort;
|
|
63
|
+
do not hand-order.
|
|
64
|
+
- Import inside a function **only** to break a genuine cycle or to keep an optional dependency
|
|
65
|
+
optional, and say why in a one-line comment.
|
|
66
|
+
- Nothing in `visionset.kernel` may import a framework or a delivery module — see the
|
|
67
|
+
`kernel-architecture` skill. That rule beats convenience, always.
|
|
68
|
+
|
|
69
|
+
## Typing
|
|
70
|
+
|
|
71
|
+
- The kernel is under strict mypy (`disallow_untyped_defs`, `disallow_any_generics`,
|
|
72
|
+
`warn_return_any`, …). Every kernel function is fully annotated; no bare `Any`.
|
|
73
|
+
- Outside the kernel, follow the local style — annotate new public functions.
|
|
74
|
+
- Domain invariants belong in the pydantic v2 models, not in ad-hoc `assert`s at call sites.
|
|
75
|
+
|
|
76
|
+
## Versioning
|
|
77
|
+
|
|
78
|
+
The repo-root `VERSION` file is the single source of truth. `pyproject.toml` reads it via
|
|
79
|
+
hatchling's regex version source; `pnpm version:sync` propagates it to the npm packages.
|
|
80
|
+
Never hand-edit a version anywhere else.
|
|
81
|
+
|
|
82
|
+
## Dependency rules
|
|
83
|
+
|
|
84
|
+
- A new runtime dependency in `[project].dependencies` ships to every user of the wheel —
|
|
85
|
+
justify it. The kernel must stay framework-free; tooling-only deps go to the dev group.
|
|
86
|
+
- Importer/exporter plugins register under the `visionset.formats` entry-point group instead
|
|
87
|
+
of being imported directly.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: annotator-core
|
|
3
|
+
description: >
|
|
4
|
+
The headless boundary of @visionset/annotator: pure-TS interaction state machine, geometry and
|
|
5
|
+
input in src/core/, React (and any other renderer) confined to src/adapters/.
|
|
6
|
+
Trigger: When adding or changing annotation behavior, canvas interaction, geometry, undo/redo,
|
|
7
|
+
or an annotator render adapter — and when the "no React in core" ESLint rule fires.
|
|
8
|
+
license: Apache-2.0
|
|
9
|
+
metadata:
|
|
10
|
+
author: robomous
|
|
11
|
+
version: "1.0"
|
|
12
|
+
scope: [frontend]
|
|
13
|
+
auto_invoke:
|
|
14
|
+
- "Changing annotation/canvas interaction behavior"
|
|
15
|
+
- "Adding or modifying an annotator render adapter"
|
|
16
|
+
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## The shape
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
frontend/annotator/src/
|
|
23
|
+
core/ pure TypeScript — no React, no DOM globals
|
|
24
|
+
types.ts the wire contract, mirrored exactly (annotations, schema, asset)
|
|
25
|
+
wire.ts unknown in, typed out — plus the outbound create/update projections
|
|
26
|
+
ids.ts the IdFactory port; core cannot mint a uuid, `crypto` is a host global
|
|
27
|
+
_fixture.ts test harness (kernel-written fixture loader) — excluded from the build
|
|
28
|
+
geometry/ math: clamp, hit-testing, transforms
|
|
29
|
+
input/ normalized pointer/keyboard events -> intents
|
|
30
|
+
state/ document.ts + selection.ts + commandLog.ts (undo/redo)
|
|
31
|
+
adapters/
|
|
32
|
+
ids.ts randomUuid — a host capability, not a renderer
|
|
33
|
+
react/ AnnotatorCanvas and friends — the only place React exists
|
|
34
|
+
index.ts public entry point
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**The document** (`state/document.ts`) is `AnnotationDocument`: an asset, a schema, and
|
|
38
|
+
`ReadonlyMap<string, Annotation>`. Immutable — every operation returns a new one. Never named
|
|
39
|
+
`Document` (a host global). Annotations are addressed by UUID, never by index.
|
|
40
|
+
|
|
41
|
+
**Selection** (`state/selection.ts`) is a `ReadonlySet<string>` held *beside* the document and
|
|
42
|
+
**filtered on read**, never pruned on write. That is what makes it survive undo/redo with no
|
|
43
|
+
coordination: undoing a delete makes the annotation resolve again, still selected. Do not move
|
|
44
|
+
selection into the document, and do not add an eager prune.
|
|
45
|
+
|
|
46
|
+
The document refuses only its own invariants — duplicate id, unknown id, foreign asset. Kernel
|
|
47
|
+
rules (class↔geometry agreement, required attributes, bounds) stay the kernel's; the tools refuse at
|
|
48
|
+
draw time, where a user can be told.
|
|
49
|
+
|
|
50
|
+
## The rules the machine enforces
|
|
51
|
+
|
|
52
|
+
`src/core/**` must not import React and must not reach the DOM. Three gates, the frontend mirror
|
|
53
|
+
of the backend's import-linter kernel contracts, all run by
|
|
54
|
+
`pnpm --filter @visionset/annotator lint`:
|
|
55
|
+
|
|
56
|
+
| Gate | Where | Scope | Catches |
|
|
57
|
+
| --- | --- | --- | --- |
|
|
58
|
+
| `no-restricted-imports` | `eslint.config.js` | `src/core/**` | `react`, `react-dom`, `react-*` |
|
|
59
|
+
| `no-restricted-globals` | `eslint.config.js` | `src/core/**`, **tests included** | `document`, `window`, `navigator`, `requestAnimationFrame`, … as **values** |
|
|
60
|
+
| `tsconfig.core.json` | its own file | `src/core/**` **minus** `*.test.ts` | DOM **types in signatures** (`KeyboardEvent`, `SVGSVGElement`), plus `setTimeout` / `console` / `fetch` in shipping code |
|
|
61
|
+
|
|
62
|
+
Two ESLint rules and a compiler pass because each reaches where the others cannot. A lint rule
|
|
63
|
+
reports *value* references, so `getSvgSize(svg: SVGSVGElement)` passes it — that is what the
|
|
64
|
+
DOM-free `lib` is for. Conversely the type gate excludes the vitest harness, which legitimately
|
|
65
|
+
runs under Node and reads a kernel-written fixture through `node:fs`; admitting it would take
|
|
66
|
+
`types: ["node"]`, and @types/node re-declares `setTimeout`, `console` and `fetch`, so buying the
|
|
67
|
+
tests would cost the timer ban on the engine. The ESLint half covers those files instead.
|
|
68
|
+
|
|
69
|
+
`tests/scripts/annotator_boundary.test.mjs` proves all three fire, and pins the two traps: `jsx:
|
|
70
|
+
"preserve"` in `tsconfig.core.json` is load-bearing (`jsx: "react-jsx"` imports
|
|
71
|
+
`react/jsx-runtime`'s types into every file, dragging in @types/react's empty `interface
|
|
72
|
+
KeyboardEvent {}` DOM stand-ins), and a type-gate probe must live inside the package, because from
|
|
73
|
+
`os.tmpdir()` nothing resolves `node_modules` and the probe fails for the wrong reason.
|
|
74
|
+
|
|
75
|
+
**If a change fights the boundary, the change is wrong — not the boundary.** Do not add an
|
|
76
|
+
eslint-disable, do not widen a rule's file scope, do not put `DOM` back in the `lib`.
|
|
77
|
+
|
|
78
|
+
When a gate fires, the fix is one of:
|
|
79
|
+
|
|
80
|
+
- The logic is renderer-agnostic → keep it in `core/`, drop the React import or the DOM reference
|
|
81
|
+
(take values as arguments, return plain data).
|
|
82
|
+
- The logic is genuinely about rendering → move it to `adapters/react/`.
|
|
83
|
+
- The signature wanted a DOM type for its *shape* → define the shape in `core/` as a plain
|
|
84
|
+
interface. That is what the input layer's normalized events are for; a DOM type in a core
|
|
85
|
+
signature is the leak this gate exists to stop.
|
|
86
|
+
|
|
87
|
+
## Writing core logic
|
|
88
|
+
|
|
89
|
+
- Pure functions and explicit state transitions. No hidden globals, no `window`/`document`
|
|
90
|
+
access, no timers owned by core — the adapter supplies those.
|
|
91
|
+
- Input arrives as normalized events; core returns intents/state, never DOM mutations.
|
|
92
|
+
- Every mutating action goes through the command log so undo/redo stays a core capability, not
|
|
93
|
+
an adapter feature.
|
|
94
|
+
- Deterministic: same input sequence → same state. That is what makes it testable and what will
|
|
95
|
+
let 3D/point-cloud modes reuse it later.
|
|
96
|
+
|
|
97
|
+
## Writing an adapter
|
|
98
|
+
|
|
99
|
+
An adapter only: subscribes to core state, paints it, and forwards normalized input back. It
|
|
100
|
+
holds no annotation logic of its own. React is the first adapter, not the only intended one —
|
|
101
|
+
anything it "knows" that core doesn't will not survive the second renderer.
|
|
102
|
+
|
|
103
|
+
## Tests
|
|
104
|
+
|
|
105
|
+
Core is unit-tested with **vitest** and needs no DOM:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pnpm --filter @visionset/annotator test
|
|
109
|
+
pnpm --filter @visionset/annotator lint # all three headless boundary gates
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The wire mirror is proved against `tests/fixtures/wire_annotations.json`, which
|
|
113
|
+
`scripts/export_wire_fixtures.py` writes from the same pydantic models `openapi.json` comes from —
|
|
114
|
+
it carries the asset, the schema and the annotations a document is built from. Never hand-write a
|
|
115
|
+
TypeScript copy of a kernel shape: extend the exporter and regenerate
|
|
116
|
+
(`uv run python scripts/export_wire_fixtures.py`), which both gates then hold in step.
|
|
117
|
+
|
|
118
|
+
`lint` is `eslint src` plus two compiler passes — `typecheck` (the whole package, tests included)
|
|
119
|
+
and `typecheck:core` (the boundary). Either is runnable on its own by name.
|
|
120
|
+
|
|
121
|
+
New interaction behavior ships with a core test that drives the state machine directly.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nodejs-setup
|
|
3
|
+
description: >
|
|
4
|
+
Frontend environment for the VisionSet pnpm workspace. Node.js 24, corepack/pnpm, per-package
|
|
5
|
+
and recursive scripts.
|
|
6
|
+
Trigger: When setting up the frontend environment, installing packages, adding a workspace
|
|
7
|
+
package, or running frontend build/test/lint scripts.
|
|
8
|
+
license: Apache-2.0
|
|
9
|
+
metadata:
|
|
10
|
+
author: robomous
|
|
11
|
+
version: "1.0"
|
|
12
|
+
scope: [frontend]
|
|
13
|
+
auto_invoke: "Setting up frontend environment or installing packages"
|
|
14
|
+
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Environment
|
|
18
|
+
|
|
19
|
+
- **Node.js 24**. With nvm on the host: `nvm use 24`.
|
|
20
|
+
- **pnpm** only (never npm, never yarn). Pinned via `packageManager` in the root
|
|
21
|
+
`package.json` — enable it with `corepack enable`.
|
|
22
|
+
- Single **pnpm workspace** rooted at the repo root; members are `frontend/*`
|
|
23
|
+
(`@visionset/annotator`, `@visionset/ui-core`, `@visionset/app`).
|
|
24
|
+
- Run commands **from the repo root**.
|
|
25
|
+
|
|
26
|
+
## Commands
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm install # install the whole workspace
|
|
30
|
+
|
|
31
|
+
pnpm -r build # build every package (topological order)
|
|
32
|
+
pnpm -r test # vitest where defined
|
|
33
|
+
pnpm -r lint # eslint
|
|
34
|
+
|
|
35
|
+
pnpm --filter @visionset/app dev # Vite dev server
|
|
36
|
+
pnpm --filter @visionset/annotator test # one package
|
|
37
|
+
pnpm --filter @visionset/ui-core build
|
|
38
|
+
|
|
39
|
+
pnpm version:sync # propagate repo-root VERSION into frontend/*/package.json
|
|
40
|
+
pnpm bundle:static # place the built app into src/visionset/_static/
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Dependencies
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pnpm --filter @visionset/app add <pkg> # runtime dep of one package
|
|
47
|
+
pnpm --filter @visionset/ui-core add -D <pkg> # dev dep of one package
|
|
48
|
+
pnpm add -w -D <pkg> # root tooling only
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- Cross-package references use the workspace protocol: `"@visionset/annotator": "workspace:*"`.
|
|
52
|
+
- `@visionset/annotator` keeps `react` as an **optional peer dependency** — never promote it to
|
|
53
|
+
a hard dependency; the core must stay usable without React.
|
|
54
|
+
- `@visionset/ui-core` UI primitives are **Radix + lucide only**. Do not add another component
|
|
55
|
+
library.
|
|
56
|
+
- `@visionset/app` is `private: true` — never published to npm; its bundle ships inside the
|
|
57
|
+
Python wheel.
|
|
58
|
+
|
|
59
|
+
## Versions
|
|
60
|
+
|
|
61
|
+
Never hand-edit a `version` field. The repo-root `VERSION` file is the source of truth;
|
|
62
|
+
`pnpm version:sync` converts it to npm semver (`0.1.0.dev0` → `0.1.0-dev.0`).
|
|
63
|
+
|
|
64
|
+
## Before you say it works
|
|
65
|
+
|
|
66
|
+
`pnpm -r build && pnpm -r test && pnpm -r lint` from the root. Report failures verbatim.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-19
|
|
3
|
+
description: >
|
|
4
|
+
React 19 patterns with React Compiler.
|
|
5
|
+
Trigger: When writing React 19 components/hooks in .tsx (React Compiler rules, hook patterns,
|
|
6
|
+
refs as props).
|
|
7
|
+
license: Apache-2.0
|
|
8
|
+
metadata:
|
|
9
|
+
author: robomous
|
|
10
|
+
version: "1.0"
|
|
11
|
+
scope: [root, frontend]
|
|
12
|
+
auto_invoke: "Writing React components"
|
|
13
|
+
allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Where React is allowed (VisionSet)
|
|
17
|
+
|
|
18
|
+
| Package | React? |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| `frontend/annotator/src/core/` | **Never** — pure TS, ESLint-enforced |
|
|
21
|
+
| `frontend/annotator/src/adapters/react/` | Yes — the render adapter |
|
|
22
|
+
| `frontend/ui-core/` | Yes — domain components (Radix + lucide only) |
|
|
23
|
+
| `frontend/app/` | Yes — the product shell |
|
|
24
|
+
|
|
25
|
+
## No Manual Memoization (REQUIRED)
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// ✅ React Compiler handles optimization automatically
|
|
29
|
+
function Component({ items }) {
|
|
30
|
+
const filtered = items.filter(x => x.active);
|
|
31
|
+
const sorted = filtered.sort((a, b) => a.name.localeCompare(b.name));
|
|
32
|
+
|
|
33
|
+
const handleClick = (id) => {
|
|
34
|
+
console.log(id);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
return <List items={sorted} onClick={handleClick} />;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ❌ NEVER: Manual memoization
|
|
41
|
+
const filtered = useMemo(() => items.filter((x) => x.active), [items]);
|
|
42
|
+
const handleClick = useCallback((id) => console.log(id), []);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Imports (REQUIRED)
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// ✅ ALWAYS: Named imports
|
|
49
|
+
import { useState, useEffect, useRef } from "react";
|
|
50
|
+
|
|
51
|
+
// ❌ NEVER
|
|
52
|
+
import React from "react";
|
|
53
|
+
import * as React from "react";
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## use() Hook
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { use } from "react";
|
|
60
|
+
|
|
61
|
+
// Read promises (suspends until resolved)
|
|
62
|
+
function Comments({ promise }) {
|
|
63
|
+
const comments = use(promise);
|
|
64
|
+
return comments.map(c => <div key={c.id}>{c.text}</div>);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Conditional context (not possible with useContext!)
|
|
68
|
+
function Theme({ showTheme }) {
|
|
69
|
+
if (showTheme) {
|
|
70
|
+
const theme = use(ThemeContext);
|
|
71
|
+
return <div style={{ color: theme.primary }}>Themed</div>;
|
|
72
|
+
}
|
|
73
|
+
return <div>Plain</div>;
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## ref as Prop (No forwardRef)
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// ✅ React 19: ref is just a prop
|
|
81
|
+
function Input({ ref, ...props }) {
|
|
82
|
+
return <input ref={ref} {...props} />;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ❌ Old way (unnecessary now)
|
|
86
|
+
const Input = forwardRef((props, ref) => <input ref={ref} {...props} />);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Keep components thin
|
|
90
|
+
|
|
91
|
+
Annotation behavior (hit-testing, geometry, undo/redo, interaction state) belongs in
|
|
92
|
+
`@visionset/annotator` core, not in a component. A React component subscribes to core state and
|
|
93
|
+
renders it. If deleting React would delete the logic, the logic is in the wrong package.
|