feat(editor): scaffold editor package
UI-only feature package for the ADR-029 editor rebuild: tsconfig rootDir '.', vitest jsdom base with '@' alias and honest L0 baseline thresholds, conformance ESLint active, React 19, @xyflow/react + zustand wired against the pre-approved 2026-07-12 library traces. Minimal feature.manifest.ts (empty useCases — no binders; declares requiredCores runner-protocol + baseline coverage band) so the editor participates in pnpm conformance without weakening any lint rule. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
This commit is contained in:
21
packages/editor/AGENTS.md
Normal file
21
packages/editor/AGENTS.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# @repo/editor
|
||||
|
||||
Owns the rebuilt Veect editor UI (ADR-029): the React Flow board shell, the iframe frame node that hosts the runner's preview-adapter origin, the editor-side selection overlay, the canvas-protocol client (pinned-`targetOrigin` postMessage both directions, ADR-028), and the minimal zustand store (registry + selection only — DesignDoc v1 arrives with the `design-doc-and-editor-foundation` epic).
|
||||
|
||||
**Boundary tag:** feature. May import from core (`core-runner-protocol`, `core-shared`), feature, and tooling packages. Imported by apps (`apps/web-next` hosts the board from story 10).
|
||||
|
||||
**Public surface:**
|
||||
|
||||
- `.` (root) — contracts only: canvas-protocol schemas + types, the feature manifest.
|
||||
- `./ui` — UI artifacts: board, frame node, selection overlay components, the editor store hook.
|
||||
|
||||
**Conventions:**
|
||||
|
||||
- UI-only feature package — `useCases` is empty in `src/feature.manifest.ts`; there are no DI binders. Manifest-first ordering applies the moment a use case appears.
|
||||
- Every component in `src/ui/` has sibling `.stories.tsx` + `.test.tsx` files (`component-must-have-story` / `component-must-have-test`) — no exemption zone (ADR-029).
|
||||
- Chrome (selection/hover rings) renders as editor-side overlays positioned from agent-reported geometry — never inside the iframe document (ADR-028).
|
||||
- Board pan/zoom and frame drag are pure React Flow transforms — no canvas-protocol round-trip on drag (<16 ms/frame budget).
|
||||
- The canvas-protocol message shapes agent ↔ editor are local zod schemas in `src/canvas-protocol/` for now; they reconcile with the adapter's agent script (walking-skeleton story 05) and `@repo/core-runner-protocol` in story 10. `render-frame` already comes from `@repo/core-runner-protocol`.
|
||||
- The prototype under `docs/product/reference/project/veect-codebase/` is reference only — never vendored (ADR-029).
|
||||
|
||||
**See:** `docs/decisions/adr-028-iframe-canvas.md`, `docs/decisions/adr-029-designdoc-v1-and-editor-rebuild.md`, `docs/work/epics/walking-skeleton/09-editor-package/_story.md`.
|
||||
3
packages/editor/eslint.config.js
Normal file
3
packages/editor/eslint.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import baseConfig from "@repo/core-eslint/base";
|
||||
|
||||
export default baseConfig;
|
||||
40
packages/editor/package.json
Normal file
40
packages/editor/package.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "@repo/editor",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./ui": "./src/ui/index.ts"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --noEmit",
|
||||
"lint": "eslint .",
|
||||
"test": "vitest run --passWithNoTests",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@repo/core-runner-protocol": "workspace:*",
|
||||
"@repo/core-shared": "workspace:*",
|
||||
"@xyflow/react": "^12.0.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"zod": "^3.24.0",
|
||||
"zustand": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/core-eslint": "workspace:*",
|
||||
"@repo/core-testing": "workspace:*",
|
||||
"@repo/core-typescript": "workspace:*",
|
||||
"@storybook/react": "^8.6.0",
|
||||
"@testing-library/jest-dom": "^6.5.0",
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@testing-library/user-event": "^14.5.0",
|
||||
"@types/react": "^19.0.0",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"jsdom": "^25.0.0",
|
||||
"typescript": "^5.8.0",
|
||||
"vitest": "^3.0.0"
|
||||
}
|
||||
}
|
||||
40
packages/editor/src/feature.manifest.test.ts
Normal file
40
packages/editor/src/feature.manifest.test.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
DEFAULT_COVERAGE_BANDS,
|
||||
getCoverageBands,
|
||||
} from "@repo/core-shared/conformance";
|
||||
import { editorManifest } from "@/feature.manifest";
|
||||
|
||||
describe("editorManifest", () => {
|
||||
it("declares the editor feature name", () => {
|
||||
expect(editorManifest.name).toBe("editor");
|
||||
});
|
||||
|
||||
it("declares no use cases — the editor is a UI-only feature (ADR-029)", () => {
|
||||
expect(editorManifest.useCases).toEqual({});
|
||||
});
|
||||
|
||||
it("requires the runner-protocol core (render-frame schema source)", () => {
|
||||
expect(editorManifest.requiredCores).toEqual(["runner-protocol"]);
|
||||
});
|
||||
|
||||
it("declares no realtime channels and no jobs", () => {
|
||||
expect(editorManifest.realtimeChannels).toEqual([]);
|
||||
expect(editorManifest.jobs).toEqual([]);
|
||||
});
|
||||
|
||||
it("declares the default baseline coverage band — drift guard against vitest.config.ts, which inherits the same numbers from nodeVitestConfig", () => {
|
||||
expect(editorManifest.coverage.bands.baseline).toEqual(
|
||||
DEFAULT_COVERAGE_BANDS.baseline,
|
||||
);
|
||||
});
|
||||
|
||||
it("resolves layer bands from defaults via getCoverageBands (no layer dirs exist yet)", () => {
|
||||
expect(getCoverageBands(editorManifest)).toEqual({
|
||||
baseline: DEFAULT_COVERAGE_BANDS.baseline,
|
||||
entities: DEFAULT_COVERAGE_BANDS.entities,
|
||||
"use-cases": DEFAULT_COVERAGE_BANDS["use-cases"],
|
||||
controllers: DEFAULT_COVERAGE_BANDS.controllers,
|
||||
});
|
||||
});
|
||||
});
|
||||
31
packages/editor/src/feature.manifest.ts
Normal file
31
packages/editor/src/feature.manifest.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { defineFeature } from "@repo/core-shared/conformance";
|
||||
|
||||
/**
|
||||
* The editor feature's conformance manifest.
|
||||
*
|
||||
* The editor is a UI-only feature package (ADR-028/029): it owns the React
|
||||
* Flow board, the iframe frame node, and the canvas-protocol client — no
|
||||
* Clean Architecture use cases, so `useCases` is empty and there are no
|
||||
* DI binders. `feature-must-have-manifest` / `usecase-must-be-wired` only
|
||||
* fire on use-case files and binders, so the manifest is declarative
|
||||
* headroom rather than a lint requirement — it exists so the editor
|
||||
* participates in `pnpm conformance` and declares its coverage bands
|
||||
* (ADR-020) and core dependency (`core-runner-protocol`) explicitly.
|
||||
*
|
||||
* When the editor grows request-path domain logic (e.g. design-doc
|
||||
* mutations), those use cases land here first — manifest-first ordering.
|
||||
*/
|
||||
export const editorManifest = defineFeature({
|
||||
name: "editor",
|
||||
requiredCores: ["runner-protocol"],
|
||||
useCases: {},
|
||||
realtimeChannels: [],
|
||||
jobs: [],
|
||||
coverage: {
|
||||
bands: {
|
||||
baseline: { statements: 80, branches: 75, functions: 80, lines: 80 },
|
||||
},
|
||||
},
|
||||
} as const);
|
||||
|
||||
export type EditorManifest = typeof editorManifest;
|
||||
4
packages/editor/src/index.ts
Normal file
4
packages/editor/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
// Public contract surface of @repo/editor (types, schemas, manifest).
|
||||
// UI artifacts (board, frame node, overlay, store hook) live behind
|
||||
// `@repo/editor/ui` — see src/ui/index.ts.
|
||||
export { editorManifest, type EditorManifest } from "./feature.manifest";
|
||||
5
packages/editor/src/ui/index.ts
Normal file
5
packages/editor/src/ui/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// Public UI surface of @repo/editor: the React Flow board, iframe frame
|
||||
// node, selection overlay, and the editor store hook land here as the
|
||||
// walking-skeleton slices ship. Apps import components/hooks from
|
||||
// `@repo/editor/ui` and contracts from `@repo/editor`.
|
||||
export {};
|
||||
13
packages/editor/tsconfig.json
Normal file
13
packages/editor/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "@repo/core-typescript/react-library.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": ".",
|
||||
"types": ["vitest/globals", "@testing-library/jest-dom"],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.stories.tsx", "**/*.stories.ts"]
|
||||
}
|
||||
4
packages/editor/turbo.json
Normal file
4
packages/editor/turbo.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": ["//"],
|
||||
"tags": ["feature"]
|
||||
}
|
||||
23
packages/editor/vitest.config.ts
Normal file
23
packages/editor/vitest.config.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import path from "node:path";
|
||||
import { mergeConfig } from "vitest/config";
|
||||
import { jsdomVitestConfig } from "@repo/core-typescript/vitest.base.jsdom";
|
||||
|
||||
// Coverage thresholds are the honest L0 baseline inherited from
|
||||
// `nodeVitestConfig` (80/75/80/80 — DEFAULT_COVERAGE_BANDS.baseline, ADR-020).
|
||||
// The editor has no entities / use-cases / controllers layers yet, so no
|
||||
// per-layer band globs apply; `feature.manifest.ts` declares the same
|
||||
// baseline band and `feature.manifest.test.ts` guards against drift.
|
||||
export default mergeConfig(jsdomVitestConfig, {
|
||||
resolve: {
|
||||
alias: { "@": path.resolve(__dirname, "./src") },
|
||||
},
|
||||
test: {
|
||||
coverage: {
|
||||
exclude: [
|
||||
// Storybook stories — excluded from vitest by design; exercised by
|
||||
// the Storybook test runner (`pnpm test:stories`).
|
||||
"src/**/*.stories.{ts,tsx}",
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user