Skip to content

Codex/v2 backup before v1 restore 20260706#463

Draft
cofly-io wants to merge 92 commits into
pascalorg:mainfrom
cofly-io:codex/v2-backup-before-v1-restore-20260706
Draft

Codex/v2 backup before v1 restore 20260706#463
cofly-io wants to merge 92 commits into
pascalorg:mainfrom
cofly-io:codex/v2-backup-before-v1-restore-20260706

Conversation

@cofly-io

@cofly-io cofly-io commented Jul 6, 2026

Copy link
Copy Markdown

What does this PR do?

How to test

Screenshots / screen recording

Checklist

  • I've tested this locally with bun dev
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch

cofly-io and others added 30 commits May 23, 2026 17:54
Introduce pipe nodes with plan/3D tools, shared segment drag utilities, and select-hold-drag UX for items, stairs, pipes, walls, and fences. Fix wall endpoint moves and WebGPU ghost preview rendering; add Chinese UI translations.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Remove react-scan from devDependencies (significant dev-mode overhead)
- Disable browserToTerminal logging in next.config
- Reduce zundo history limit from 50 to 20
- Optimize spatial grid sync subscription logic
- Remove unused post-processing code from viewer

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add the editor-side bridge, primitive node support, and catalog assets needed to exercise Articraft-generated scenes without depending on local-only runtime state.

Constraint: user asked to send current code to their fork first.

Rejected: committing .omx runtime state or the nested articraft working tree | those are local/runtime or separate-repo state, not durable editor repository content.

Confidence: medium

Scope-risk: broad

Directive: decide separately whether articraft/ should become a submodule or remain a local checkout before depending on it in fresh clones.

Tested: git diff --cached --check; bun test packages/core/src/lib/primitive-compose.test.ts

Not-tested: bun run format:check fails on repo-wide CRLF formatting differences, including files outside this staged change.
Repair the corrupted UTF-8 bytes in the primitive action-menu labels so Next can read the client component source.

Constraint: Next source parsing requires valid UTF-8 for TSX files.

Rejected: broad repo formatting pass | unrelated CRLF churn would obscure the targeted fix.

Confidence: high

Scope-risk: narrow

Tested: UTF-8 byte decode; git diff --check; bun run check-types in packages/editor
Keep primitive generation to the requested analyze and generate stages so the chat flow stops after creating the requested shapes.

Constraint: user asked to cancel the final check step in the three-step model flow.

Rejected: keeping reviewer prompt unused | dead prompt state would make the flow harder to reason about.

Confidence: high

Scope-risk: narrow

Tested: git diff --check; bun run check-types in packages/editor
Adds richer primitive/object composition, Articraft item conversion hooks, direct selected-drag behavior for geometry nodes, and safer WebGPU post-processing fallback so generated scenes are more realistic and editable.

Constraint: User requested pushing the accumulated local editor improvements to their GitHub remote.

Rejected: Commit .omx runtime state or nested articraft checkout | local runtime/vendor state should not be versioned here.

Confidence: medium

Scope-risk: broad

Directive: Keep container nodes such as building out of direct 3D drag subscriptions unless event bubbling is explicitly isolated.

Tested: bun run check-types; bun --cwd packages/editor check-types; bun --cwd packages/nodes build; bun --cwd apps/editor check-types; git diff --cached --check.

Not-tested: bun --cwd packages/core test currently fails existing stair-opening-sync assertions about manual stair holes.
Add generated geometry providers, richer primitive/assembly schema support, and editor affordances so generated 3D assets can be created, inspected, duplicated, and material-targeted consistently.

Constraint: Preserve package layering across core, nodes, viewer, editor, and app surfaces.

Rejected: Keep primitive tool panels as separate legacy tool entry points | generated and catalog-driven workflows now centralize this surface.

Confidence: medium

Scope-risk: broad

Directive: Future modifiers should keep AI/generated geometry semantics in core/node definitions and editor-only workflow state in editor/app layers.

Tested: not run during push-only handoff

Not-tested: lint, typecheck, and full test suite were not run before this repository push
Tighten generated part composition, validation behavior, and editor catalog affordances after the initial AI geometry workflow landed.

Constraint: Follow up on post-push working tree updates without rewriting the already-pushed history.

Rejected: Amend the prior pushed commit | preserving published history avoids a force push.

Confidence: medium

Scope-risk: moderate

Directive: Keep composition and semantic validation tests aligned with generated geometry behavior.

Tested: not run during push-only handoff

Not-tested: lint, typecheck, and full test suite were not run before this repository push
Move high-frequency complete objects toward assembly while keeping professional standard components in recipe-owned geometry. Add closed-form recipes for chain sprockets, 90-degree elbows, pillow block bearings, flexible couplings, and perforated plates, then route and document them through the AI geometry harness.

Constraint: Recipes are reserved for strongly parameterized, stable industrial components that should not be freely arranged by the LLM.

Rejected: Encoding whole equipment such as aircraft, cars, pumps, fans, and chimneys as recipes | Those remain better suited to family/assembly generation.

Confidence: high

Scope-risk: moderate

Directive: Keep future recipes limited to standard parts or precise mechanical templates; use assembly families for complete equipment.

Tested: bunx biome check --write targeted files; bun --filter @pascal-app/core build; bun test packages/core/src/lib/primitive-recipes.test.ts packages/editor/src/lib/ai-chat-harness/capability-planner.test.ts packages/editor/src/lib/ai-geometry-tool-executor.test.ts; bun run check-types

Not-tested: Full app runtime smoke in browser
Back up the current in-progress editor workspace before further iteration, including the industrial node and Chinese inspector work.

Constraint: User requested a personal-repo backup of the current code state.

Rejected: committing local run logs and generated harness output | they are volatile runtime artifacts rather than source.

Confidence: medium

Scope-risk: broad

Directive: Treat this as a WIP backup checkpoint; review and split before upstream PR work.

Tested: bunx biome check on edited inspector/panel/live-data files; node scripts/check-mojibake.mjs.

Not-tested: full package typecheck remains blocked by existing packages/editor/src/lib/ai-geometry-tool-executor.test.ts GeneratedGeometryArtifact.id typing error.
Preserve the current geometry/profile generation work while excluding local AI harness runtime logs from version control.

Constraint: User requested pushing the current workspace to origin after cleanup.

Rejected: Commit runtime .generated and .codex artifacts | these are local execution logs and would pollute the repository.

Confidence: medium

Scope-risk: broad

Directive: Split this large workspace checkpoint into focused follow-up PRs before upstream review.

Tested: bun run check:mojibake

Not-tested: Full monorepo test suite after staging all workspace changes.
Constraint: Backup requested before fixing the factory creation review findings.\nConfidence: medium\nScope-risk: broad\nDirective: Keep runtime logs and rendered scratch artifacts out of source commits.\nTested: Not run; backup commit only before targeted fixes.\nNot-tested: Full test suite and factory run smoke pending after follow-up fixes.
Keep template knowledge as defaults while preserving user-specified process topology and make factory run status follow the quality gate.\n\nConstraint: Factory creation must work when LLM planning falls back to deterministic templates.\nRejected: Always preferring complete templates | it erases explicit station counts such as multiple clinker lines and mills.\nConfidence: high\nScope-risk: moderate\nDirective: Treat profile/template data as defaults; user explicit topology and quality failures must remain visible in run results.\nTested: bun test apps/editor/lib/ai-harness-runs; bun run lint apps/editor/lib/ai-harness-runs/factory-planner.ts apps/editor/lib/ai-harness-runs/factory-planner.test.ts apps/editor/lib/ai-harness-runs/factory-runner.ts apps/editor/lib/ai-harness-runs/factory-runner.test.ts; bun run check-types in apps/editor; API smoke run_mqm3evpg_d95049c0 produced 28 stations, 4 cement mills, quality 100.\nNot-tested: Full Playwright visual factory render.
Factory-capable packs now declare their capability explicitly and must carry usable process templates plus factory architecture coverage. The scaffold and QA report the pack kind so generated industry packs cannot look production-ready when they only contain device profiles. Existing simulated cloud factory packs declare the capability and their zips were refreshed.

Constraint: Industry packs are uploaded as portable profile-pack zips and must be self-validating before cloud publication.

Rejected: Treating factory resources as optional hints | it allowed AI-generated packs to pile devices without supporting factory creation.

Confidence: high

Scope-risk: moderate

Directive: New factory-capable packs must keep station ids aligned between process templates and architecture modules.

Tested: bun test apps/editor/scripts/scaffold-industry-profile-pack.test.ts apps/editor/lib/profile-packs.test.ts --timeout 30000; bun run lint apps/editor/lib/profile-packs.ts apps/editor/lib/profile-packs.test.ts apps/editor/scripts/profile-pack-qa.ts apps/editor/scripts/scaffold-industry-profile-pack.ts apps/editor/scripts/scaffold-industry-profile-pack.test.ts; bun run check-types; profile-pack-qa validate-only for cement, electrolytic-aluminum, and water-treatment cloud zips.

Not-tested: Full UI cloud install flow was not rerun for this commit.
Factory generation now carries process-pack knowledge, port-aware primitive contracts, editable native process equipment, and placement context so generated plants can be reviewed and incrementally edited. The scaffolder and QA gates now reject unsupported multi-count factory schemas while validating generated factory-capable packs.

Constraint: Factory creation currently models one process template per request; pack specs must not encode quantity expansion fields.

Rejected: Template-level replication parameters | They made factory creation ambiguous and moved process-count logic into pack data instead of request planning.

Confidence: high

Scope-risk: moderate

Directive: Keep future pack additions single-template unless the factory planner contract is deliberately expanded first.

Tested: bun --cwd packages/editor check-types; bun --cwd apps/editor check-types; targeted Biome check; factory/process/scaffold QA tests (74 pass); primitive compose/generated-node tests (85 pass); core/viewer/nodes builds; profile-pack QA validate-only for cement, electrolytic-aluminum, refinery, discrete-manufacturing 0.1/0.2, process 0.1/0.2.

Not-tested: Full browser visual smoke for every generated industry pack.
Constraint: 阶段一聚焦 AI 工厂生成、设备生成、行业资源包和可编辑几何属性闭环。
Rejected: 保留离散制造/流程行业 v1+v2 双版本 | 开发期不需要多版本矩阵,统一以 v2 内容作为 0.1.0。
Confidence: high
Scope-risk: broad
Directive: 后续行业包应以已启用包和云端分发语义为准,避免资源目录与安装状态产生行为分裂。
Tested: bun test apps/editor/lib/ai-harness-runs/factory-planner.test.ts; bun test apps/editor/lib/ai-harness-runs/process-line-composer.test.ts -t refinery; bun test apps/editor/lib/ai-harness-runs/primitive-generation-service.test.ts; profile-pack-qa cement/refinery/discrete/process validate and targeted QA gates; biome check targeted files.
Not-tested: 未提交临时截图、日志、tmp 与 qa-artifacts 运行产物;未运行完整端到端浏览器截图套件。
Checkpoint the active editor state before starting the WebSocket-backed dynamic preview milestone, including the dynamic system planning document and the current UI/runtime work already present in the branch.

Constraint: User requested a pushed rollback point before beginning Milestone 1.

Rejected: Staging generated logs, screenshots, thumbnails, and tmp artifacts | They are local runtime evidence and would make the checkpoint noisy.

Confidence: medium

Scope-risk: broad

Directive: Use this commit as the rollback anchor before WebSocket/Data Widget implementation.

Tested: git diff --cached --check

Not-tested: Full test suite was not run for this checkpoint commit.
Captures the current local source changes for data widgets, dynamic previews, conveyor routing, and profile-pack work so the branch can be backed up to the user's fork.

Constraint: User asked to upload local code to their own repository; generated screenshots, tmp folders, logs, and QA artifacts were left unstaged.
Rejected: Committing every untracked artifact | would add large generated images and temporary outputs unrelated to source code.
Confidence: medium
Scope-risk: broad
Directive: Future cleanup should split this WIP snapshot into reviewable feature commits before merging upstream.
Tested: bunx biome check on edited data widget files; bun run build in packages/core; bun run build in packages/nodes; bun run check-types in packages/editor.
Not-tested: Full repository test suite and browser E2E were not rerun for the entire staged snapshot.
Capture the current local project state before pushing it to the personal fork, including AI harness, data display, material, steel-frame, and renderer work.

Constraint: Push requested to the configured personal origin repository.

Rejected: Including local logs, debug screenshots, backups, and scene thumbnails | runtime artifacts should stay out of source history.

Confidence: medium

Scope-risk: broad

Directive: Review feature slices separately before merging upstream because this commit intentionally preserves a large local workspace state.

Tested: bun run check-types in apps/editor; bun run build in packages/nodes during UI control updates.

Not-tested: Full repo build, e2e suite, and visual regression pass.
Keep the standalone app on focused editor/viewer subpaths and add a bounded dev server smoke entrypoint so local verification is repeatable without importing the package roots by default.

Constraint: apps/editor composes editor and viewer packages but should not depend on broad root exports for leaf integrations.

Rejected: split the viewer root component internals in this pass | earlier dev-server trials showed that broader split could destabilize startup.

Confidence: high

Scope-risk: moderate

Directive: Keep viewer subpath helpers free of editor-only concepts such as tools, floorplan state, paint state, and useEditor.

Tested: git diff --cached --check; node --check apps/editor/scripts/dev-server-smoke.mjs; bun run --cwd apps/editor check-types; bun run lint

Not-tested: full browser dev-server smoke after staging; previous smoke was run before the final subpath documentation cleanup.
Move the frame-driven elevator runtime system into viewer and replace core event/registry Object3D references with package-local scene object aliases so core no longer advertises Three or React Three Fiber as peers.

Constraint: packages/core must stay pure domain logic and cannot import Three.js, @react-three/fiber, or viewer runtime concepts.

Rejected: keep ElevatorRuntimeSystem exported from core | it requires useFrame and would preserve a viewer dependency in the core public API.

Confidence: high

Scope-risk: moderate

Directive: New frame-driven systems belong in viewer or nodes, while core should expose only pure state machines and serializable contracts.

Tested: git diff --cached --check; bun run --cwd packages/core build; bun run --cwd packages/viewer build

Not-tested: full app browser smoke for this isolated commit.
Add sceneSelection capability metadata to built-in node definitions and expose registry helpers so selection behavior can move away from hardcoded kind lists. Rename viewer hover highlighting from editor-specific modes to neutral intents shared by editor and viewer systems.

Constraint: viewer state must not encode editor-only mode names such as delete or paint-ready.

Rejected: keep selection behavior as hardcoded kind arrays | it hides node behavior outside the registry and makes plugin nodes harder to compose.

Confidence: high

Scope-risk: moderate

Directive: Future selectable kinds should declare sceneSelection in their NodeDefinition instead of adding kind checks to editor selection code.

Tested: bun test packages/core/src/registry/registry.test.ts; bun test packages/nodes/src/index.test.ts; bun run --cwd packages/viewer build; bun run --cwd packages/editor check-types; bun run --cwd packages/core build; bun run --cwd packages/nodes build; git diff --cached --check

Not-tested: full browser interaction smoke for delete/material-paint hover visuals.
Migrate editor and node integrations onto focused viewer/editor subpaths, remove legacy selection kind dispatch from the viewer selection manager, and trim placement/debug cleanup now covered by shared helpers.

Constraint: nodes may integrate with editor and viewer surfaces, but broad package roots should not be the default dependency for narrow store, material, geometry, or helper access.

Rejected: keep viewer selection subscribed to hardcoded kind arrays | registry sceneSelection metadata now owns that contract.

Confidence: high

Scope-risk: broad

Directive: Add new node selection, material, geometry, and store integration through registry metadata or explicit subpaths instead of widening package root imports.

Tested: bun test packages/nodes/src/shared/opening-click-draft.test.ts packages/nodes/src/index.test.ts; bun run --cwd packages/viewer build; bun run --cwd packages/nodes build; bun run --cwd packages/editor check-types; bun run lint; git diff --cached --check

Not-tested: browser interaction smoke for every moved tool and renderer.
Add thermal-power and appliance-assembly profile packs, carry architecture station position hints into process-line layout, map thermal-power station wording to profile contracts, and support cooling-tower part composition for generated industrial scenes.

Constraint: factory generation must prefer validated profile-pack contracts before falling back to generic primitive geometry for known industrial process stations.

Rejected: place thermal-power stations with generic parallel-bay fallback only | pack-provided station hints produce the intended reference layout and avoid quality-gate collisions.

Confidence: high

Scope-risk: broad

Directive: Keep profile-pack generated parents and station IDs validated through safety/apply tests when adding future packs.

Tested: bun apps/editor/scripts/profile-pack-qa.ts industry.appliance-assembly.basic@0.1.0 --validate-only; bun apps/editor/scripts/profile-pack-qa.ts industry.thermal-power.basic@0.1.0 --validate-only; bun test --timeout 20000 apps/editor/lib/ai-harness-runs/factory-planner.test.ts apps/editor/lib/ai-harness-runs/factory-runner.test.ts apps/editor/lib/ai-harness-runs/process-equipment-resolver.test.ts apps/editor/lib/ai-harness-runs/process-line-layout.test.ts packages/editor/src/lib/factory-scene-patch-apply.test.ts packages/editor/src/lib/factory-scene-patch-safety.test.ts packages/articraft-bridge/tests/scene-converter.test.ts; bun run --cwd packages/editor check-types; bun run --cwd packages/core build; bun run lint; git diff --cached --check

Not-tested: visual render QA for the committed profile-pack scenes.
Remove obsolete lint suppressions and stabilize IFC converter callbacks so hook dependencies stay explicit under the current Biome rules.

Constraint: cleanup should avoid changing converter behavior while satisfying lint and hook dependency checks.

Rejected: keep the noShadow nursery override | current Biome config no longer needs the broad rule escape hatch.

Confidence: high

Scope-risk: narrow

Directive: Prefer fixing hook dependencies and stale suppressions locally before widening global lint overrides.

Tested: bun run lint; bun run --cwd apps/editor check-types; git diff --cached --check

Not-tested: manual IFC converter browser flow.
Keep reusable geometry orientation and material cloning faithful to the authored output while preserving the current registry-driven package boundaries.

Constraint: Final validation surfaced uncommitted geometry and material fidelity fixes that need to travel with the cleanup branch.

Rejected: Leaving the fixes uncommitted | it would make the PR summary diverge from the validated working tree.

Confidence: high

Scope-risk: moderate

Directive: Keep future material fixes in viewer/nodes material helpers instead of leaking editor concepts into renderable node geometry.

Tested: bun run lint; package builds for core/viewer/nodes; packages/editor and apps/editor typechecks; targeted core/nodes tests

Not-tested: Full dev smoke after this commit will run as the final verification step.
Route common editor actions through registry-owned metadata and patch builders so the editor shell asks NodeDefinition capabilities instead of enumerating slab, ceiling, wall, pipe, and route-like kinds inline.

Constraint: Keep core/viewer free of editor store imports while still allowing plugin definitions to expose pure action metadata.

Rejected: A larger floating-menu rewrite | current scope only moves existing branch decisions behind registry metadata.

Confidence: high

Scope-risk: moderate

Directive: Add future action-menu or keyboard-nudge behavior on NodeDefinition instead of expanding editor-side kind lists.

Tested: bun run lint; packages/core viewer nodes builds; packages/editor and apps/editor typechecks; targeted nodes/viewer tests

Not-tested: Browser smoke for endpoint buttons after this small registry-routing pass.
…rs/grass) (pascalorg#457)

* feat(plugins): trees tracer-bullet plugin + minimal host panel surface

Ship one real first-party plugin and only the host surface it forces
into existence, proving the plugin contribution paths every future
plugin (mint.gg generator, Home Assistant, environments, room volume)
will reuse.

Host surface (core + editor):
- core: Plugin.panels + observable panelRegistry; loadPlugin routes
  panels (namespaced by plugin id, dup-throws like nodes).
- editor: AppSidebar merges panelRegistry into the icon rail via
  useSyncExternalStore; each plugin panel lazy-loaded behind an error
  boundary; host extraPanels keep precedence.
- editor: widen Tool to `KnownTool | (string & {})` so plugin tool ids
  (e.g. 'trees:tree') typecheck; dispatch already registry-first.

Trees plugin (packages/plugin-trees), structurally a third-party pack
(peer-deps on @pascal-app/*):
- trees:tree node — procedural low-poly geometry (oak/pine/birch/palm),
  free parametric inspector (preset/height/seed + Randomize), placement
  tool + ghost preview built from public primitives only.
- presets rail panel — panel -> plugin store -> def.tool -> SceneApi ->
  scene -> reactive read-back ("N planted").

Loading + docs:
- apps/editor: setPluginDiscovery([treesPlugin]) in bootstrap;
  transpilePackages + dep.
- wiki/architecture/plugin-authoring.md: panels field, error-boundary
  contract, styling, tool-from-panel note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(plugins): merge plugin panels into the v2 sidebar layout too

The community shell renders <Editor layoutVersion="v2" sidebarTabs={...}>,
a separate rail from AppSidebar. Merge registry panels into the v2
tabMap + tab bar (and thus mobile) so plugin panels show up there as
well, not only in the v1 AppSidebar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(plugin-trees): ez-tree geometry + instanced rendering + snapping

Replace the placeholder low-poly geometry with dgreenheck/ez-tree
(self-contained — bark/leaf textures inlined as base64, no assets to
host) and render the forest with true GPU instancing.

Rendering:
- def.system (system.tsx): one collective renderer that groups every
  trees:tree node by (preset, seed) variant and draws each variant as
  one InstancedMesh per ez-tree sub-mesh, composing the parent level's
  world matrix into per-instance matrices. ~1 draw call per variant.
- def.renderer (proxy-renderer.tsx): an invisible, raycastable per-node
  proxy so the host's existing selection / outline / zone machinery
  keeps working — no instanceId bookkeeping. (Outline highlights the
  proxy bbox; documented.)
- geometry per variant generated once by ez-tree and cached; seeds drawn
  from a bounded pool so trees actually share variants.

Presets remapped to ez-tree built-ins (oak/pine/aspen/ash/bush).

Placement now respects the active snap mode — isGridSnapActive() +
gridSnapStep + snapPointToGrid, like the built-in item/shelf tools.

transpilePackages += @dgreenheck/ez-tree in the editor app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(plugin-trees): true-silhouette hover/select + richer presets panel

Selection/hover now outlines the real tree shape, not the bbox. The
proxy splits into an outer group (stable invisible box collider +
pointer handlers, the raycast target) and an inner registered group that
mounts the real ez-tree geometry (invisible, non-raycasting) only while
the node is hovered or selected. The host outline pass reads the
registered inner group, so it traces the true silhouette; picking stays
on the steady box. Geometry for the highlight reuses the cached variant.

Panel: redesigned cards with gradient swatches + selected state, a
"planted" count chip, and a height slider that seeds the next tree's
height (a per-instance scale — never touches placed trees or instancing).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(plugin-trees): curated tree params + procedural flowers (sibling kind)

Tree params: expose foliageDensity, trunkThickness, and a leafless toggle
in the inspector and the panel brush. Each is folded into the instancing
variant key and mapped onto ez-tree options (radius scale, leaf count),
so editing one tree only re-buckets that tree — instancing degrades
gracefully, never worse than per-node.

Flowers: add a `trees:flower` sibling kind — simple procedural geometry
(stem + petals + center, merged per material), presets daisy / tulip /
lavender, instanced + selectable exactly like trees.

Refactor: extract the instanced renderer + selection proxy into a generic
`instanced.tsx` (InstancedKindSystem + KindProxy) and the snap/level/grid
placement wiring into `placement.tsx` (usePlacement). Trees and flowers
are now thin bindings — the template for future plant kinds.

Panel: a Trees / Flowers toggle, gradient preset cards, per-kind planted
count, and brush sliders.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(plugin-trees): native inspector controls in panel + preset thumbnails + ez-tree credit

- swap the panel's custom slider/checkbox/toggle for the host's exported
  SliderControl/ToggleControl/SegmentedControl so the brush matches the
  right-hand inspector pixel-for-pixel
- replaceable preset thumbnails (inline SVG data URIs, no asset hosting) render
  as <img> cards instead of gradient swatches
- credit footer linking @dgreenheck/ez-tree (Daniel Greenheck, MIT)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(plugin-trees): all ez-tree presets + type + leaf/branch colours, flower petal colour, grass kind

- tree presets now cover all of ez-tree's built-ins via species × size
  (Small/Medium/Large + Bush 1/2/3 + Trellis), plus a Deciduous/Evergreen type
- edit-only leaf & branch colour tints (leaves.tint / bark.tint), all folded
  into the instancing variant key
- flowers gain a per-flower petal colour (baked from preset at placement)
- new trees:grass instanced kind — procedural blade tufts (meadow/fescue/reed)
  with a per-tuft blade colour, reusing the generic instanced + placement core
- panel: Trees/Flowers/Grass segmented switch, size + type controls, native
  host controls throughout; credit links Daniel Greenheck's X

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(plugin-trees): tolerate nodes persisted before new fields existed

treeSpecOf/flowerPetalColor now default every geometry field, and hexToInt
guards undefined — trees/flowers placed before size/type/colour fields were
added load with neutral defaults instead of crashing the instanced renderer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(plugin-trees): shared always-on wind + pin credits to panel bottom

- wind.ts injects a sway into each material's begin_vertex driven by one shared
  uTime uniform (advanced per frame in the instanced system); USE_INSTANCING
  guards keep the same material valid for the non-instanced placement ghost.
  Applied to tree, flower, and grass materials — a whole scene sways like ez-tree.
- credits footer is now sticky to the bottom of the panel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(plugin-trees): rename rail panel Trees -> Nature (covers trees/flowers/grass)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(plugin-trees): rename panel header Plant -> Nature to match the rail

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: bake-exportable instanced plants + renderer-agnostic wind

Bake (GLB export): add a transient useViewer.isExporting flag. BakeExporter
flips it, waits for the commit, then exports. KindProxy watches it and, during
export, emits each plant's REAL visible geometry under scene-renderer (which the
exporter clones) instead of the invisible collider — so instanced kinds
(def.system) that live outside that subtree are captured. The collider box is
dropped during export so it doesn't bake as a phantom solid.

Wind: the previous onBeforeCompile wind was a no-op under the editor's WebGPU
renderer (WebGL-only hook). Replace it with a renderer-agnostic per-instance
base-pivot tilt animated in useFrame (which FrameLimiter drives continuously).
Reads as wind under any renderer; removes the dead wind.ts + applyWind calls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(plugin-trees): TSL vertex-bend wind (WebGPU), replacing CPU tilt

wind-node.ts adds a TSL positionNode that bends each plant proportional to
height above its base, phased per-instance (instanceIndex) and per-vertex, so
tips sway and roots stay planted — animated on the GPU via the renderer's time
node. ez-tree is untouched: its materials are copied into MeshStandardNodeMaterial
(toWindMaterial); flowers/grass build node materials directly (windStandardMaterial).
Instance matrices go back to static.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(plugin-trees): tree materials render black under wind (Phong not Standard)

ez-tree's bark/leaves are MeshPhongMaterial; copying them into a
MeshStandardNodeMaterial swapped the shading model (specular/shininess ->
roughness/metalness) and rendered black. Convert into the matching node
material type (Phong->Phong, Lambert->Lambert, else Standard) so map/color/
alphaTest/side are preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(plugin-trees): trees still black — build node material from map/color explicitly

Material.copy() doesn't transfer a classic material's map/color onto a node
material, so textured trees rendered black (flowers/grass were fine — they carry
no map). Re-create the ez-tree materials as MeshStandardNodeMaterial with map,
alphaMap, color, side, alphaTest passed explicitly in the constructor — the same
proven path the flower/grass materials use.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(plugin-trees): tree wind — per-leaf flutter (ez-tree style), static trunk

The whole-tree height-based bend was a rigid rotation about the base ('rotating
in place'). Replicate ez-tree's actual approach: scale sway by the leaf card's
uv.y and apply it ONLY to the 'leaves' material (multi-frequency wave, phased per
instance + per leaf) so leaves flutter from their attachment while bark/branches
stay static. Flowers/grass keep the gentle whole-plant stem bend.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(plugins): Plugin.onSceneLoad hook + viewer application; trees wind via it

Adds a broad, plugin-declared post-load scene hook so effects survive GLB export:
- core: Plugin.onSceneLoad (lazy, three-free) + observable sceneHookRegistry + loadPlugin routing.
- viewer: applyPluginSceneHooks util; PluginSceneHooksSystem re-runs it on the live
  scene as nodes change; GlbScene runs it on each loaded baked GLB.
- plugin-trees: declare onSceneLoad; wind is no longer welded into the materials —
  the hook re-attaches LEAF_FLUTTER/STEM_BEND by material name (leaves / flower-* /
  grass-*), so it now animates in the baked /viewer too, not just live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(plugin-trees): scale-invariant leaf wind (÷ modelScale) so baked GLB matches editor

The bake's quantiser renormalises each plant mesh to [-1,1] and moves real size
into a large node scale (~3 on trees). LEAF_FLUTTER added a constant local offset,
so in the baked GLB it was multiplied by that scale → ~3x over-swing. Divide the
sway by modelScale.x to keep a constant world-space amplitude; editor (scale 1)
is unchanged, baked now matches.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* revert: onSceneLoad wind approach → back to wind-in-materials

Reverts the Plugin.onSceneLoad hook + viewer application + modelScale compensation
(commits 893ca83, 6c9ee00). Superseded by the bake-policy 'replace' model
(plans/editor-plugin-trees-example.md Part D): trees render live in the viewer via
their own path, so wind is a plain positionNode on the material again — no hook,
no baked-geometry decoration, no coordinate-space fights.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(bake): per-kind bake policy (static/strip/replace)

Adds NodeDefinition.bake ('static' default | 'strip' | 'replace') + registry
helpers bakePolicyOf/kindsWithBakePolicy in core. Generalises the previously
hardcoded scan/guide handling:
- glb-export strips kinds with bake==='strip' from the artifact (was a
  scan/guide type check); 'replace' kinds stay baked (portable static snapshot).
- glb-reference-nodes selects rebuild candidates by policy instead of type.
- scan/guide declare bake:'strip'; trees/flower/grass declare bake:'replace'.

Viewer-side 'replace' swap (strip baked meshes + live-rebuild) is the next slice.
Part of plans/editor-plugin-trees-example.md Part D.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(bake): viewer 'replace' swap — hide baked meshes, re-render live

Adds NodeDefinition.bakeReplaceRenderer (falls back to renderer). The baked
/viewer now, for kinds with bake:'replace' and a loaded plugin:
- hides the static baked meshes (bakePolicyOf(kind)==='replace') in GlbScene's
  identity pass — capability-gated, so with no plugin the meshes stay;
- re-renders each node live via buildGlbReplaceNodes + the shared portal-into-
  baked-level path (GlbReferenceNode now prefers bakeReplaceRenderer), so trees
  ride level stacking automatically and wind runs in real coordinate space.

plugin-trees ships a hookless KindStatic (real geometry + wind materials, no
collider/selection) and per-kind static-renderer binds; tree/flower/grass point
bakeReplaceRenderer at them. Completes plans/editor-plugin-trees-example.md Part D.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* perf(viewer): memoize GlbReferenceNode(s) — skip forest re-render on camera move

GlbScene re-renders per frame during camera movement (hover raycast / walkthrough
HUD). The rebuilt nodes reconciled every frame — fine for 1-2 scans/guides, but a
bake:'replace' forest puts dozens of nodes here (profiler: KindStatic x56 6ms,
GlbReferenceNode x57 4ms per frame). memo at the node boundary skips the whole
subtree (incl. the plugin renderer) when (node, anchor) are unchanged; both props
are stable refs, so it short-circuits cleanly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* perf(plugin-trees): NO_RAYCAST on KindStatic meshes — cheap hover over a forest

The baked scene's <primitive> has pointer handlers, so R3F recursively raycasts
every descendant on each pointer move (incl. orbit drags). The live replace trees
had fully-raycastable dense ez-tree geometry, so hover cost ~400ms/frame over a
forest (profiler: 'JavaScript' 413ms during camera move). They carry no pascalId
(a hit resolves to the level, not the tree), so they're scenery, not pick targets
— mirror KindProxy, which already NO_RAYCASTs its real geometry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* refactor(bake): viewer 'replace' renders instanced per level (was per-node)

Replaces the per-node KindStatic viewer path with a collective instanced one,
mirroring the editor's system instead of diverging from it:

- extract InstancedNodes from InstancedKindSystem (shared by both) with a
  localSpace flag: editor folds parent world matrix (root instances); viewer uses
  level-local matrices + NO_RAYCAST, portaled into the baked level.
- bakeReplaceRenderer is now a collective renderer (BakeReplaceRenderer<N>,
  receives {nodes}); GlbReplaceInstances groups replace nodes by (level, kind) and
  portals each kind's instanced renderer into that baked level. GlbReferenceNode
  reverts to per-node renderer (strip kinds only).

Fixes: forest hover cost (100+ tree meshes → a few instanced draws, R3F walks far
fewer objects) and per-tree wind phase (instanceIndex varies again; per-node
KindStatic gave every tree phase 0 → unison sway). Trees still ride level stacking
(portaled) and stay static in a plain glTF viewer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* perf(viewer): BVH the baked scene — accelerate hover/pick raycasts

The baked /viewer runs with useBvh={false}, so GlbScene's hover/pick raycasts
against the baked building were brute-force triangle intersection (profiler:
_computeIntersections + intersectTriangle ~30%). The parametric viewer wraps its
scene in <SceneBvh>; the baked one never did. SceneBvh's effect is one-shot on
mount and can't catch the async-loaded GLB, so compute a per-mesh BVH inside
GlbScene keyed on gltf.scene instead. 'replace' instances are NO_RAYCAST, so the
raycast===Mesh.prototype.raycast guard skips them (mirrors SceneBvh).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(plugin-trees): inherit ez-tree preset by default (seed/type/tints)

Our node defaults (seed:1, treeType:deciduous, colors:#ffffff) were applied as
*overrides* on top of loadPreset, discarding each preset's tuned seed, growth
model, and tints — so our trees looked nothing like eztree.dev (pine grew
deciduous, foliage washed white, every silhouette off).

Mirror the flower petalColor pattern: seed/treeType/leafColor/branchColor are now
optional; generateTree only overrides an ez-tree option when the node set it,
otherwise loadPreset's value stands. Placement stores none of them (fresh tree =
pure preset; all same-preset trees share one instancing variant); the deciduous/
evergreen brush toggle is gone (growth model comes from the preset). The inspector
keeps them as per-tree overrides, and Randomize still varies the seed.

Verified against ez-tree: fresh Oak Medium → seed 35729 + preset tints; Pine
Medium → evergreen + seed 13977; overrides apply when set.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(plugin-trees): bundle real preset artwork (webp, in-package)

Replaces the placeholder SVG thumbnails + lucide panel icon with the real nature
art. 13 webp (~126KB total, 256px cards / 128px icon) live in src/assets and are
imported via art.ts — both consumers are Next, so transpilePackages runs them
through the image pipeline (hashed, cached /_next/static/media URLs). No CDN, no
per-app public/ mirroring; the assets travel with the package.

- art.ts: central webp imports → TREE_ART / FLOWER_ART / GRASS_ART / NATURE_ICON
- presets/flower-presets/grass-presets: thumbnail ← bundled art (was *Thumbnail())
- index.ts: Nature panel icon ← NATURE_ICON (was lucide:leaf)
- assets.d.ts: ambient *.webp decl (no next type dep)
- remove thumbnails.ts (placeholder generators, now dead)

Verified on the running dev server: webp emitted + served (HTTP 200, image/webp).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(plugin-trees): square preset cards on light-gray flattened thumbnails

Regenerate the 12 thumbnails with the transparency flattened onto gray-100
(#f3f4f6) and render the card square (aspect-square, was h-16 crop). The
nature-icon keeps its transparency for the icon rail.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(plugin-trees): correct move-drag + outline behavior for instanced plants

Two selection defects, one root cause each:

1. Outline moved mirror-wise during a move drag. The host move tool drives the
   registered Object3D imperatively with absolute level-local positions (and
   mirrors them via useLiveTransforms) — the contract ParametricNodeRenderer
   satisfies by putting position + registration on the same group. KindProxy
   registered a child nested inside the transform-carrying group, so drag
   positions landed in the node's rotated frame (placement gives every plant a
   random Y rotation → deltas rotated up to 180°). Restructure: the registered
   group now carries position/rotation (live-transform aware); the box collider
   is a positioned sibling, staying out of the outline mask.

2. Outline froze while the mesh swayed. The outline mask pass renders with a
   shared override material, so it can never follow the material positionNode
   wind. Instead, while hovered/selected the collective system skips the node
   and the proxy mounts the real geometry with static twins of the wind
   materials (toStaticMaterial — explicit property transfer; node-material
   clone() drops map/color). Outlined mesh == visible mesh, both still.

Bonus from the same restructure: a move drag now animates the actual plant in
realtime (the proxy is what the tool drives), not just the drag box.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(plugin-trees): 2D plan symbols + trunk-sized footprint

- def.floorplan for the three kinds — the registry floor-plan layer renders any
  kind that provides one, so this is all plugin nodes need to appear in 2D.
  Tree: dashed canopy ring (dashed = overhead element) in the preset swatch +
  solid trunk dot; ring is pointer-events:stroke so the large disc doesn't
  steal clicks from what's under the canopy. Flower/grass: small colour dots.
  Selected → palette stroke + move-handle; hovered → hover stroke.
- Tree floorPlaced.footprint is trunk-sized (treeTrunkRadius) instead of
  canopy-sized, so the move/placement drag box hugs where the tree actually
  plants instead of spanning the crown. The invisible hover collider keeps its
  larger radius — only the displayed box shrinks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(plugin-trees): trunk-sized drag box, 45°-aligned rotation, rotate gizmo

- The move drag box ignored floorPlaced.footprint: with collides:false the tool
  auto-measures the rendered mesh — which, since the selection swap-out, is the
  whole canopy. Declare capabilities.dragBounds (trunk-sized, node-local) so the
  box hugs the trunk and rotates with the node instead of wrapping the crown as
  a world AABB.
- Placement wrote a fully random Y rotation, so the box/gizmo never sat on a 45°
  step. Snap the random rotation to 45° increments (variety preserved, alignment
  restored) and widen rotatable.snapAngles to 8×45° on all three kinds.
- Add the standard rotate gizmo (def.handles, shelf-style arc handle): ring
  around the trunk near the ground — not the canopy, which would put the handle
  meters out on a large oak.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(plugin-trees): live rotate preview + ground-level rotate ring

- KindProxy now folds useLiveNodeOverrides into its transform (mirrors
  ParametricNodeRenderer) — the rotate/resize gizmos publish mid-drag patches
  there, so the plant turns in realtime instead of snapping on commit. The
  snap-on-commit (with the arc delta wrapping to ±180°) is also what made long
  drags land 'the wrong way'; with live feedback the direction reads correctly
  and matches the item gizmo pipeline exactly.
- Rotate ring drops from mid-trunk to 0.25m — a floor affordance like the item
  gizmo, not a waist-height hoop.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(registry): panelForKind — 'find in catalog' opens a plugin kind's panel

loadPlugin now associates every node kind with its plugin's first (namespaced)
panel id; panelRegistry.panelForKind(kind) exposes it. A host's find-node
handler can open the right panel for any plugin kind with zero per-plugin
knowledge.

plugin-trees uses it end-to-end: a module-level selection:find-node listener
(find-sync.ts, imported by the manifest so it's live from plugin load) points
the panel store at the found node's section + preset — panel section state
moved from panel-local useState into the store to make it addressable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(registry): plugin panels declare their workspaces (default edit-only)

Plugin panels rode into the studio rail: the host swaps its own sidebar tabs by
workspaceMode, but usePluginPanels appended registry panels unconditionally.

PluginPanel gains workspaces?: readonly ('edit'|'studio')[] — manifest metadata,
default ['edit'] (an authoring panel has no business in the clean render
workspace; a plugin shipping studio tooling opts in explicitly). usePluginPanels
filters by the current workspaceMode, covering both the v1 AppSidebar and v2 tab
bar paths. Nature declares nothing and disappears from studio via the default;
the v2 layout's existing active-tab fallback handles a panel vanishing mid-use.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs: link plugin-authoring guide from README + CONTRIBUTING, fix stale wiki path

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(plugin-trees): keep ez-tree off the eager SSR path

ez-tree loads its inlined textures at module scope (needs document), so any
eager import chain reaching geometry.ts crashed Next prerender
(ReferenceError: document is not defined on /_not-found). Move the pure
helpers (mulberry32, naturalHeight) to variant-utils.ts so the
flower/grass builders and floorplan no longer pull ez-tree, and drop the
generateTree re-export from the package barrel (no external consumers).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adapt the upstream plugin contract to this branch's current workspace boundaries while avoiding a premature pull-in of the newer baked-GLB viewer stack.

Constraint: Current branch lacks upstream GLB viewer prerequisites such as glb-interactive and bvh-ecctrl.

Rejected: Pull the whole upstream viewer chain in this migration slice | too broad for a reversible plugin architecture landing.

Confidence: medium

Scope-risk: moderate

Directive: Restore baked /viewer replace rendering only after its prerequisite viewer modules are merged deliberately.

Tested: bun --cwd packages/plugin-trees check-types; bun --cwd packages/editor check-types; bun run build in packages/core and packages/viewer; bun check:mojibake

Not-tested: Browser smoke test and full app build.
cofly-io added 30 commits July 4, 2026 21:44
Phase 5 needs a deterministic telemetry source before adding source lifecycle management. The editor now seeds a fixed factory demo source, keeps external WebSocket connection opt-in, and lets Data Lens plus Semantic Inspector read the same live-data store values.

Constraint: User asked not to add or delete data sources yet; source selection stays hardcoded.

Rejected: Auto-connect to localhost:3102 by default | It overwrote the fixed source and made the default product state look externally managed.

Confidence: high

Scope-risk: moderate

Directive: Do not add multi-source CRUD until the product flow for cloud/on-demand data sources is explicitly designed.

Tested: bunx biome check targeted files; bun run --cwd apps/editor check-types; bun test fixed-live-data-source/object-capabilities/semantic-equipment-editing/scene-structure; Playwright scene-structure e2e; Playwright dynamic-preview data widget e2e.

Not-tested: Full e2e suite; live-data toolbar test remains outside this slice because current action-menu visibility is unrelated to fixed source seeding.
Phase 5 needs data to drive semantic equipment without introducing source lifecycle management. This adds a binding contract that maps object capability profiles to dynamicBindings, exposes those quick bindings in Semantic Inspector, and includes binding summaries in the shared object capability profile for AI context.

Constraint: Data sources remain hardcoded; no source add/delete UI was introduced.

Rejected: A separate semantic data-binding format | Existing dynamicBindings already feed the preview runtime, so a second format would split behavior.

Rejected: High z-index equipment lens cards | They blocked Inspector interaction; the overlay now stays below panel controls.

Confidence: high

Scope-risk: moderate

Directive: Keep future data-source CRUD separate from object binding contracts.

Tested: bunx biome check targeted files; bun test fixed-live-data-source/object-capabilities/semantic-live-data-bindings/scene-structure; bun run --cwd apps/editor check-types; Playwright scene-structure e2e; Playwright dynamic-preview data widget e2e.

Not-tested: Full e2e suite; manual browser click-through of every quick binding target.
Route data-binding previews through the semantic equipment contract so selected objects can receive deterministic dynamicBindings instead of sending the user back to a manual panel-only flow.

Constraint: Phase 5 data sources are intentionally fixed/demo-only for now.

Rejected: Add a separate AI-only binding schema | it would split Inspector and AI behavior.

Confidence: high

Scope-risk: moderate

Directive: Keep future live-data commands on the semantic binding helper so Inspector, AI, and workflow automation share one contract.

Tested: bunx biome check targeted files; bun test semantic-live-data-bindings/context-builder/object-capabilities; bun run --cwd apps/editor check-types; git diff --check targeted files

Not-tested: Browser click-through for the data-binding preview confirmation was not run in this pass.
Extend Phase 5 binding beyond level and flow so selected semantic equipment can map alarm/status telemetry to an alarm pulse effect through the same dynamicBindings contract.

Constraint: Phase 5 still uses the fixed/demo data source; no source add/remove UX is introduced.

Rejected: Add a special alarm overlay outside DynamicBinding | it would bypass Data Lens and preview runtime contracts.

Confidence: high

Scope-risk: moderate

Directive: Keep new live-data effects expressible as DynamicBinding targets before adding bespoke runtime paths.

Tested: bunx biome check targeted files; bun test semantic-live-data-bindings/context-builder/intent-router/generation-plan-preview; bun run --cwd apps/editor check-types; git diff --check targeted files

Not-tested: Full browser e2e for the alarm pulse visual effect was not run in this pass.
Add browser-level coverage for the Phase 5 user flow: selected semantic equipment can be bound through AI, confirmed from the generation preview, and then inspected through Data Lens, Inspector, and the dynamic preview runtime.

Constraint: Data sources remain fixed/demo-only in this phase; the tests verify binding behavior without adding source management UX.

Rejected: Assert only library planner output | it would miss UI preview confirmation and runtime visibility regressions.

Confidence: high

Scope-risk: narrow

Directive: Keep future AI workflow tests anchored on stable generation-plan preview test ids rather than translated visible copy.

Tested: biome targeted files; apps editor typecheck; scene-structure AI tank level e2e; dynamic-preview AI alarm pulse e2e; git diff --check targeted files.

Not-tested: Full editor e2e suite was not run.
Finish the remaining Phase 5 acceptance points by proving live-data reset/reseed preserves node binding metadata and by documenting the delivered fixed-source scope versus deferred source-management UX.

Constraint: The product direction for this phase keeps data sources fixed, so source add/remove UI remains intentionally out of scope.

Rejected: Treat live data bindings as transient store state | binding contracts must live on scene node metadata.

Confidence: high

Scope-risk: narrow

Directive: Future real WebSocket source management must preserve the same dynamicBindings metadata contract tested here.

Tested: biome targeted files; apps editor typecheck; scene-structure AI tank level e2e with reset/reseed persistence; git diff --check targeted files.

Not-tested: Full editor e2e suite was not run.
Complete the fixed-source drag/drop part of Phase 5 by making live data fields draggable and accepting them on Data Lens equipment cards through the semantic binding contract.

Constraint: Data sources remain fixed/demo-only; this adds drag binding without source add/remove UI.

Rejected: Bind drag drops with ad hoc metadata | reusing semantic dynamicBindings keeps AI, Inspector, Data Lens, and runtime aligned.

Confidence: high

Scope-risk: moderate

Directive: Future freeform canvas drops should resolve to the same planSemanticLiveDataBindingForPath helper instead of creating a parallel path.

Tested: biome targeted files; semantic-live-data-bindings unit test; apps editor typecheck; scene-structure fixed field drag e2e; git diff --check targeted files.

Not-tested: Freeform raw 3D surface drop and multi-source browsing remain deferred.
Start Phase 6 by deriving an inspectable workflow graph from existing AI harness runs instead of introducing a parallel workflow store.

Constraint: Existing run-store data is the source of truth for Phase 6 history and graph inspection.

Rejected: Add a new persisted workflow model | current run/result/events already contain enough evidence for the first graph slice.

Confidence: high

Scope-risk: moderate

Directive: Future run-history UI and partial rerun features should consume buildAiWorkflowGraph or the workflow endpoint rather than rebuilding stage logic client-side.

Tested: biome targeted workflow/API files; workflow-summary unit tests; ai-harness runs route tests; apps editor typecheck; git diff --check targeted files.

Not-tested: Browser UI inspection, station-scoped rerun, and save-as-template remain later Phase 6 slices.
Make Phase 6 visible in the product by adding a lightweight Runs inspector that lists recent AI runs and opens the derived workflow stages.

Constraint: This is the first inspect-only UI slice; station reruns and template saving are intentionally not enabled yet.

Rejected: Build a full node-graph editor now | a compact stage inspector proves the workflow contract before adding editing controls.

Confidence: high

Scope-risk: moderate

Directive: Keep future workflow UI backed by the server workflow graph endpoint so stage labels and status semantics remain consistent.

Tested: biome targeted UI/API/doc files; workflow-summary unit tests; ai-harness runs route tests; apps editor typecheck; git diff --check targeted files.

Not-tested: Browser screenshot of the Runs dropdown and partial rerun interactions.
Advance Phase 6 by letting a saved factory workflow create a station-scoped equipment compiler rerun from the existing process plan.

Constraint: This slice creates a focused rerun result; replacing the old station on the canvas is intentionally deferred.

Rejected: Re-run the whole factory for station edits | station-scoped runs keep iteration smaller and preserve the workflow graph model.

Confidence: high

Scope-risk: moderate

Directive: Future canvas replacement should consume the station rerun result rather than bypassing workflow-rerun.

Tested: biome targeted rerun/workflow/runner/UI files; workflow-rerun unit tests; workflow-summary unit tests; ai-harness runs route tests; apps editor typecheck; git diff --check targeted files.

Not-tested: Browser click-through of rerun buttons and applying station rerun patches onto an existing canvas station.
Complete the station-rerun apply loop by translating focused rerun results into delete-and-create replacement batches for the matching station on the current canvas.

Constraint: Replacement is resolved client-side because only the editor has authoritative current scene nodes and parent ids.

Rejected: Let source run parent ids drive station replacement | historical parent ids can be stale for the current canvas.

Confidence: high

Scope-risk: moderate

Directive: Future station rerun UX should keep using prepareStationRerunPatches so delete/create ordering and parent repair remain centralized.

Tested: biome targeted station-rerun/apply/safety/runner/UI files; factory-station-rerun-apply tests; factory-scene-patch-apply tests; factory-scene-patch-safety tests; workflow-rerun tests; workflow-summary tests; apps editor typecheck; git diff --check targeted files.

Not-tested: Browser click-through screenshot for replacing a station from the Runs dropdown.
Station-scoped workflow reruns must behave like ordinary factory runs after the user clicks rerun: subscribe to the new run event stream, apply its result through the same patch safety path, and keep a browser regression for semantic assembly replacement.

Constraint: Phase 6 requires rerun replacements to apply on the canvas instead of only appearing in workflow history.

Rejected: Refreshing run history only | it leaves the new run detached from the result application path.

Confidence: high

Scope-risk: narrow

Directive: Keep station rerun UI actions wired to the same subscribeFactoryRun completion path as first-run factory generation.

Tested: bunx biome check packages/editor/src/components/ui/sidebar/panels/ai-chat-panel/index.tsx apps/editor/e2e/scene-structure.spec.ts

Tested: bun run --cwd apps/editor check-types

Tested: bun test packages/editor/src/lib/factory-station-rerun-apply.test.ts packages/editor/src/lib/factory-scene-patch-apply.test.ts packages/editor/src/lib/factory-scene-patch-safety.test.ts apps/editor/lib/ai-harness-runs/workflow-rerun.test.ts apps/editor/lib/ai-harness-runs/workflow-summary.test.ts

Tested: bun run --cwd apps/editor e2e -- e2e/scene-structure.spec.ts -g 'station rerun result replaces'

Not-tested: full Playwright suite
Phase 7 needs image-to-3D and joint assets to enter the same semantic scene model as industry-pack equipment. Introduce a standard metadata.assetSource contract, write it when placing generated model assets, and make object capabilities plus Scene Structure Source grouping read that contract.

Constraint: Keep the first slice small and metadata-based so existing saved scenes and item schemas keep loading unchanged.

Rejected: Inferring every source only from legacy tags/sourceTool | it keeps source identity scattered and makes future AI edits lossy.

Confidence: high

Scope-risk: moderate

Directive: Future image, joint, catalog, and semantic-conversion paths should preserve metadata.assetSource instead of replacing it with ad hoc fields.

Tested: bun test packages/editor/src/lib/object-capabilities.test.ts packages/editor/src/lib/scene-structure.test.ts

Tested: bunx biome check packages/editor/src/lib/asset-source-contract.ts packages/editor/src/lib/object-capabilities.ts packages/editor/src/lib/object-capabilities.test.ts packages/editor/src/lib/scene-structure.ts packages/editor/src/lib/scene-structure.test.ts packages/editor/src/components/ui/sidebar/panels/ai-chat-panel/index.tsx

Tested: bun run --cwd apps/editor check-types

Tested: bun run --cwd apps/editor e2e -- e2e/scene-structure.spec.ts -g 'scene structure defaults'

Not-tested: full Playwright suite; real provider-backed image-to-3D and Articraft generation
Phase 7 needs joint assets to remain editable as semantic scene objects after placement. Add record-level Articraft joint controls to the shared Inspector surface and mount that surface in the assembly custom panel so users can preview, reset, select, and edit exposed joints from the asset root.

Constraint: Assembly nodes render through the @pascal-app/nodes custom panel while the editor app resolves nodes from the ignored dist build during local e2e.

Rejected: Only exposing single-joint controls on selected child parts | selecting the asset root would still hide whole-device articulation.

Confidence: high

Scope-risk: moderate

Directive: Keep Articraft pose edits routed through applyArticraftJointValue and buildArticraftJointPatch so Inspector edits and dynamic data preview stay consistent.

Tested: bun run --cwd packages/nodes build; bun test packages/editor/src/lib/articraft-joints.test.ts packages/editor/src/lib/object-capabilities.test.ts; bun run --cwd apps/editor check-types; bunx biome check --assist-enabled=false on changed files; bun run --cwd apps/editor e2e -- e2e/scene-structure.spec.ts -g Articraft joint assets expose --timeout=120000

Not-tested: full Playwright suite; live external Articraft viewer service
Phase 7 needs generated assets to remain traceable after AI follow-up edits. Add a standard assetSource contract to AI geometry artifacts and root assembly patches, and carry non-AI source metadata forward when revision replacement rebuilds the canvas root.

Constraint: Revision replacement currently recreates generated geometry roots rather than patching existing nodes in place.

Rejected: Relying on legacy sourceTool and artifactId fields only | object capability and source grouping already read the standard assetSource contract.

Confidence: high

Scope-risk: moderate

Directive: Preserve non-AI assetSource values when converting or replacing externally generated assets; use a fresh ai-geometry assetSource for native AI geometry revisions.

Tested: bun test packages/editor/src/lib/ai-generated-geometry-nodes.test.ts packages/editor/src/lib/articraft-joints.test.ts packages/editor/src/lib/object-capabilities.test.ts; bun run --cwd apps/editor check-types; bunx biome check --formatter-enabled=false --assist-enabled=false on changed files; git diff --check

Not-tested: full Playwright suite; live image-to-3D or Articraft external provider replacement flow
Phase 7 needs external model assets to enter the same semantic equipment model without forcing GLB decomposition. Recognize common generated equipment families at placement time and attach equipment contracts so Inspector, Source grouping, AI capability reads, and future data binding can treat the item as equipment.

Constraint: Image-to-3D and Articraft model outputs are opaque GLB items, so this slice annotates them semantically instead of decomposing geometry.

Rejected: Converting every generated model into a semantic assembly | unknown or decorative assets would receive false equipment semantics and lose model fidelity.

Confidence: high

Scope-risk: moderate

Directive: Keep generated model recognition conservative; return null for unknown assets rather than inventing equipment contracts.

Tested: bun test packages/editor/src/lib/generated-model-equipment-contract.test.ts packages/editor/src/lib/object-capabilities.test.ts packages/editor/src/lib/ai-generated-geometry-nodes.test.ts packages/editor/src/lib/articraft-joints.test.ts; bun run --cwd apps/editor check-types; bunx biome check --formatter-enabled=false --assist-enabled=false on changed files; git diff --check

Not-tested: full Playwright suite; live image-to-3D or Articraft placement in browser
Phase 8 needs generation outcomes to feel predictable before adding heavier undo and preview flows. Extract factory run experience summarization so apply state, quality gates, fallback reasons, layout diagnostics, and missing pack guidance are produced by one tested contract and shown in the AI result card.

Constraint: Existing AI panel UI text contains historical encoding noise, so this change avoids broad copy churn and only wires new safety alerts into the current card.

Rejected: Keeping fallback and quality formatting inside the AI panel | it duplicated parsing logic and made future before/after preview and run-level undo harder to share.

Confidence: high

Scope-risk: moderate

Directive: Extend factory-run-experience-summary for Phase 8 preview and undo metadata instead of adding more result parsing inside the AI panel.

Tested: bun test packages/editor/src/lib/factory-run-experience-summary.test.ts packages/editor/src/lib/object-capabilities.test.ts packages/editor/src/lib/generated-model-equipment-contract.test.ts packages/editor/src/lib/ai-generated-geometry-nodes.test.ts packages/editor/src/lib/articraft-joints.test.ts; bun run --cwd apps/editor check-types; bunx biome check --formatter-enabled=false --assist-enabled=false on changed files; git diff --check

Not-tested: full Playwright suite; manual browser generation card visual review
Phase 8 needs users to understand the canvas impact before trusting generated factory runs. Add a tested change preview contract that compares the current scene node count with the post-patch estimate and lists created, updated, and deleted samples, then surface it through the factory result summary and E2E bridge.

Constraint: Full visual before/after snapshots require a heavier viewer workflow, so this slice ships a deterministic structural preview first.

Rejected: Only showing raw patch counts | patch counts do not tell users what the canvas looked like before or what named objects will change.

Confidence: high

Scope-risk: moderate

Directive: Reuse factory-run-change-preview when adding visual diff, run-level undo labels, or confirmation dialogs.

Tested: bun test packages/editor/src/lib/factory-run-change-preview.test.ts packages/editor/src/lib/factory-run-experience-summary.test.ts packages/editor/src/lib/factory-scene-patch-apply.test.ts; bun run --cwd apps/editor check-types; bunx biome check --formatter-enabled=false --assist-enabled=false on changed files; git diff --check

Not-tested: full Playwright suite; manual browser visual review of the result card
Phase 8 needs AI-generated factory changes to feel reversible. Apply validated factory patch batches through one scene-store state write so undo and redo treat the whole run, including station replacements, as a single user action.

Constraint: Existing patch application order must preserve delete-before-create replacement semantics for regenerated stations.

Rejected: Calling deleteNodes, createNodes, and updateNodes sequentially | temporal history records them as separate undo steps and makes AI runs hard to roll back.

Confidence: high

Scope-risk: moderate

Directive: Keep future factory apply paths on factory-scene-patch-undo-group unless they intentionally need multiple undo steps.

Tested: bun test packages/editor/src/lib/factory-scene-patch-undo-group.test.ts packages/editor/src/lib/factory-scene-patch-apply.test.ts packages/editor/src/lib/factory-run-change-preview.test.ts packages/editor/src/lib/factory-run-experience-summary.test.ts; bun run --cwd apps/editor check-types; bunx biome check --formatter-enabled=false --assist-enabled=false on changed files; git diff --check

Not-tested: full Playwright suite; manual browser undo/redo interaction
Phase 8 needs bad WebSocket payloads to be harmless. Sanitize incoming live data snapshots at the shared store boundary and harden dynamic preview numeric/truthy conversion so invalid numbers, objects, arrays, and empty paths cannot overwrite safe values or drive runtime animation.

Constraint: Live data is consumed by Data Lens, Inspector, and dynamic preview, so the validation belongs at the shared store boundary rather than only in one UI surface.
Rejected: Letting each renderer defend against malformed values | it leaves stale or unsafe values in the shared store and duplicates validation logic.
Confidence: high
Scope-risk: narrow
Directive: Route future live data sources through useLiveData.setSnapshot instead of writing values directly.
Tested: bun test packages/core/src/live-data/live-data-store.test.ts packages/editor/src/lib/fixed-live-data-source.test.ts packages/editor/src/lib/semantic-live-data-bindings.test.ts; bun run --cwd apps/editor check-types; bunx biome check --formatter-enabled=false --assist-enabled=false on changed files; git diff --check
Not-tested: live WebSocket browser session with malformed payloads; full Playwright suite
Phase 8 QA exposed that the editor server can start from apps/editor, where a stray test artifact can make sync repo-root discovery resolve the wrong directory. Tighten sync repo-root checks, let trusted installed-pack evidence resolve source-pack templates from installed or cloud package sources, and gate required source packs to installed create-factory intents only.

Constraint: Industry-pack generation must be blocked when a pack is missing, but an installed create-factory intent should deterministically resolve its process template without depending on free-form prompt replanning.
Rejected: Falling back to generic missing/geometry when intent routing already proved an installed pack | it breaks the one-sentence factory workflow and hides the actual pack/template contract.
Confidence: high
Scope-risk: moderate
Directive: Keep future server-side pack readers on the shared repo-root shape; do not infer the repo root from package.json alone.
Tested: bun test apps/editor/lib/ai-harness-runs/factory-planner.test.ts apps/editor/lib/ai-harness-runs/factory-runner.test.ts packages/core/src/live-data/live-data-store.test.ts packages/editor/src/lib/fixed-live-data-source.test.ts packages/editor/src/lib/semantic-live-data-bindings.test.ts; bun run --cwd apps/editor check-types; bunx biome check --formatter-enabled=false --assist-enabled=false on changed files; direct HTTP factory run for generate a refinery; direct HTTP factory run for 生成一个炼油厂; apps/editor/scripts/factory-full-run-qa.ts smoke refinery score 100; git diff --check
Not-tested: full non-smoke LLM-backed refinery generation; full Playwright suite
Phase 9 needs a fast way to prove the v2 factory workflow is releasable before running heavier browser QA. Add a factory release-readiness script that checks simulated-cloud coverage, installed intent-routed packs, server-cwd-safe pack directory discovery, and required source-pack process template planning.

Constraint: Release checks must catch industry-pack/template resolution regressions without requiring a browser or a long full-run visual pass.
Rejected: Relying only on manual refinery smoke screenshots | it catches rendering failures late and does not explain pack operations regressions.
Confidence: high
Scope-risk: narrow
Directive: Run factory:release-qa before marking future factory workflow phases or industry-pack changes as release-ready.
Tested: bun run --cwd apps/editor factory:release-qa; bun test apps/editor/scripts/factory-release-readiness.test.ts; bun test apps/editor/scripts/factory-release-readiness.test.ts apps/editor/lib/ai-harness-runs/factory-planner.test.ts apps/editor/lib/ai-harness-runs/factory-runner.test.ts; bun run --cwd apps/editor check-types; bunx biome check --formatter-enabled=false --assist-enabled=false on changed files; git diff --check --cached
Not-tested: browser full-run visual QA in this Phase 9 first slice; full Playwright suite
Phase 9 release checks need durable evidence that humans can inspect after the fast gate runs. Extend factory:release-qa with an artifact output mode that writes the readiness JSON and a product-facing Factory V2 release notes markdown document, resolving relative artifact paths from the repository root.

Constraint: The command is normally invoked with --cwd apps/editor, so artifact paths must not accidentally nest under apps/editor/apps/editor.
Rejected: Printing only JSON to stdout | it is enough for CI but poor release evidence for product and QA review.
Confidence: high
Scope-risk: narrow
Directive: Use --out-dir for release-candidate evidence and keep generated artifacts out of commits unless deliberately publishing them.
Tested: bun run --cwd apps/editor factory:release-qa; bun run --cwd apps/editor factory:release-qa -- --out-dir apps/editor/qa-artifacts/factory-release-readiness/latest; bun test apps/editor/scripts/factory-release-readiness.test.ts; bun run --cwd apps/editor check-types; bunx biome check --formatter-enabled=false --assist-enabled=false on changed files; git diff --check
Not-tested: browser full-run visual QA in this slice
Phase 9 needs one command that proves the factory workflow is a local release candidate without relying on memory of scattered checks. Add factory:release-candidate to run release readiness, core factory tests, editor typecheck, and targeted Biome checks, with optional refinery visual smoke and a JSON summary artifact.

Constraint: The default gate must stay fast enough for routine local use while preserving an explicit opt-in path for heavier browser smoke.
Rejected: Baking browser visual QA into every release-candidate run | it would make the common gate depend on a running dev server and slow down ordinary pack/workflow changes.
Confidence: high
Scope-risk: narrow
Directive: Use --with-visual-smoke only when preparing release-candidate evidence that needs screenshots; keep qa-artifacts ignored unless deliberately publishing evidence elsewhere.
Tested: bun test apps/editor/scripts/factory-release-candidate.test.ts apps/editor/scripts/factory-release-readiness.test.ts; bun run --cwd apps/editor check-types; bun run --cwd apps/editor factory:release-candidate -- --out-dir apps/editor/qa-artifacts/factory-release-candidate/latest; bunx biome check --formatter-enabled=false --assist-enabled=false on changed files; git diff --cached --check
Not-tested: factory:release-candidate --with-visual-smoke in this slice
Phase 9 needs product-facing release notes, not only machine-readable QA output. Add a stable Factory V2 release notes document and expand generated readiness notes to explain user experience, V1-to-V2 changes, release confidence, current boundaries, and release-candidate commands.

Constraint: Release notes must be understandable to product and QA reviewers while staying tied to the automated readiness evidence.
Rejected: Keeping release notes as a raw checklist of pack counts | it explains pass/fail state but not what users can trust in the release.
Confidence: high
Scope-risk: narrow
Directive: Update docs/factory-v2-release-notes.md when user-facing Factory V2 behavior or release boundaries change.
Tested: bun test apps/editor/scripts/factory-release-readiness.test.ts apps/editor/scripts/factory-release-candidate.test.ts; bun run --cwd apps/editor factory:release-qa -- --out-dir apps/editor/qa-artifacts/factory-release-readiness/latest; bun run --cwd apps/editor factory:release-candidate -- --out-dir apps/editor/qa-artifacts/factory-release-candidate/latest; bun run --cwd apps/editor check-types; bunx biome check --formatter-enabled=false --assist-enabled=false on changed files; git diff --cached --check
Not-tested: visual smoke mode in this slice
Constraint: Factory release-candidate smoke must run without third-party icon fetch noise. Rejected: Replacing every Iconify call site | too broad for a release-gate cleanup and risks UI churn. Confidence: high Scope-risk: narrow Directive: Keep editor chrome icons locally resolved in QA-visible paths; add mappings instead of reintroducing runtime icon API fetches. Tested: bun run check-types; bunx biome check --formatter-enabled=false --assist-enabled=false apps/editor/lib/local-iconify-react.tsx apps/editor/next.config.ts apps/editor/scripts/render-factory-full-run-qa.mjs; bun run factory:release-candidate -- --with-visual-smoke --base-url http://localhost:3002 --out-dir qa-artifacts/factory-release-candidate/visual-latest Not-tested: production build
Constraint: Phase 9 needs reviewable release evidence without committing generated screenshots or QA artifacts. Rejected: Keeping local visual artifacts in git | too noisy and not stable as source material. Confidence: high Scope-risk: narrow Directive: Treat docs/factory-v2-release-evidence.md as the compact release proof and regenerate local QA artifacts when deeper inspection is needed. Tested: bun run --cwd apps/editor factory:release-candidate -- --with-visual-smoke --base-url http://localhost:3002 --out-dir qa-artifacts/factory-release-candidate/final-phase9; git diff --check Not-tested: production build
Constraint: The design document needs a clear nine-phase first-release completion state. Rejected: Claiming every future plan-editing affordance is finished | richer editable plans remain a later workflow-template polish item. Confidence: high Scope-risk: narrow Directive: Treat Phase 1 as complete for V2 first-release scope, with deeper editable plan UI tracked as later polish. Tested: bun test apps/editor/lib/ai-harness-runs/intent-preview-service.test.ts apps/editor/lib/ai-harness-runs/single-equipment-compiler.test.ts apps/editor/lib/ai-harness-runs/factory-runner.test.ts apps/editor/lib/ai-harness-runs/workflow-summary.test.ts; git diff --check Not-tested: full release-candidate gate
Constraint: PR preflight must pass workspace typecheck before opening the factory v2 PR. Rejected: Opening the PR with known typecheck failures | reviewers would inherit broken test fixtures. Confidence: high Scope-risk: narrow Directive: Keep semantic capability test fixtures schema-valid when ObjectPortSummary or node schemas tighten. Tested: bun run check-types; bun run build; bun run check-types in packages/editor; bun test packages/editor/src/lib/object-capabilities.test.ts packages/editor/src/lib/ai-chat-harness/context-builder.test.ts Not-tested: browser visual smoke after this test-only fixture change
Constraint: User asked to push a v2 backup before restoring the local workspace to the 202607043Dfactory1.0 baseline.
Rejected: Leaving v2 only as dirty local changes | it would make the v1 restore risky and hard to recover from.
Confidence: high
Scope-risk: broad
Directive: Treat this branch as the v2 backup checkpoint before any v1 restoration work.
Tested: bun test apps/editor/lib/profile-packs.test.ts -t "refinery storage tanks"; bun test packages/core/src/lib/part-compose.test.ts -t "vertical storage tank"; bun test packages/plugin-factory-equipment/src/recipes/semantic-recipes.test.ts -t "storage tank"; bunx biome check targeted files; git diff --check
Not-tested: Full repository typecheck and full e2e suite.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants