docs: record CAD semantic context and vector crop planning notes

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-16 16:42:34 +08:00
parent ac6f1d84af
commit e7a0cc0b86
3 changed files with 264 additions and 1 deletions
+88
View File
@@ -1,5 +1,93 @@
# Findings: Server-Backed Brand Kit Canvas Context # Findings: Server-Backed Brand Kit Canvas Context
## 2026-07-16 Canvas Performance and LeaferJS Evaluation
- The current canvas is a React/DOM renderer: every visual node is a `CanvasNodeCard`, and the viewport is applied through a transformed DOM content layer.
- Wheel zoom and canvas pan update React `viewport` state at pointer/wheel frequency. Node dragging rebuilds the full `nodes` array and calls `setNodes` on every pointer event, so high-frequency interaction can reconcile the workspace and every un-memoized node even when only one transform changed.
- Large CAD drawings are displayed as one external SVG `<img>`, not thousands of React elements. Their 9 MB SVG decode/paint cost can amplify frame drops, but React state churn is a separate engine-wide bottleneck that also affects normal images and shapes.
- LeaferJS exposes a retained scene tree, Canvas rendering, hit testing, dragging, zooming, and an editor plugin. Those capabilities align with the current visual/interaction layer, while project persistence, Agent Chat, CAD conversion/semantics, crop, toolbars, and backend contracts should remain application-owned.
- The LeaferJS README's million-element benchmark is vendor-provided and has not been reproduced for this application's image-heavy/CAD workload. The migration decision must use this repo's pan/zoom/selection frame-time baseline.
- A full rewrite would unnecessarily couple chat, persistence, and action panels to the engine. The likely boundary is a canvas renderer adapter driven by the existing `CanvasNode[]` domain model, with React retained for toolbars, panels, overlays, and text editing.
- Viewport changes invalidate more than the `.canvas-world` transform: every `CanvasNodeView` receives `selectionScale` and `viewportScale`, fresh inline event callbacks, a fresh transform object, and a newly built context-menu handler object. A shallow `React.memo` alone would therefore not stop node rerenders.
- `workspaceCropTarget` scans all nodes and recomputes screen bounds on every viewport update. Selection overlays and annotations also consume React viewport state, adding more work to every wheel/pan event.
- Node drag calls `moveNodesByDelta` for the whole document, filters moved nodes, computes alignment guides against the full node list, replaces the `nodes` array, and updates React state on every pointer event. This is a stronger predicted bottleneck than SVG decoding once the image is loaded.
- The latest `leafer-ui` package observed is 2.2.2, MIT licensed, with about 4.3 MB unpacked package contents. The base engine can render images and vector paths, but editor/plugin bundle size and exact SVG/image behavior require a prototype before dependency adoption.
- The separate `@leafer-in/editor` 2.2.2 package is also MIT and about 1.23 MB unpacked. It provides selection/transform events, but React DOM overlays and the existing tool model still need an adapter rather than direct component replacement.
- `useCanvasPersistence` depends on both `viewport` and `nodes`; each interaction update reruns its effect, clears the existing 1.2-second timer, and creates a new timer. It does not save over the network each frame, but it adds avoidable effect/timer churn to the render path.
- Alignment-guide computation is linear in all document nodes for a single active node (with nine edge/center comparisons per candidate), then React still renders the resulting guides and the full node list. The measured browser baseline should vary node count to separate algorithmic scaling from a single large SVG's paint cost.
- Playwright production baseline on a 120Hz browser with lightweight frame nodes:
- 100 nodes: zoom p95 16.1ms, drag p95 9.1ms, one drag frame over 20ms.
- 500 nodes: zoom p95 40.9ms and drag p95 41.4ms; more than 90% of measured frames exceeded 20ms.
- 1000 nodes: zoom p95 83.4ms and drag p95 82.8ms; almost every frame exceeded 34ms and many 50ms+ long tasks were observed.
- The decisive differential probe kept the same 1000 DOM nodes but bypassed React. Direct `.canvas-world` transform updates measured p95 9.2ms with no frame over 20ms; direct single-node position updates also measured p95 9.2ms. This confirms React reconciliation/state/effect work, not raw DOM compositing capacity, as the primary bottleneck.
- Ranked causes after measurement: (1) confirmed React full-tree high-frequency updates; (2) drag-only full-array/alignment/persistence amplification; (3) unmeasured large SVG decode/paint cost; (4) disproved raw DOM node count as the primary cause.
- The first exact-DWG paint probe served the raw `convertCadBufferToSvg` output directly and Chromium rejected it. This is expected for the internal boundary: the third-party writer emits anonymous `</>` closing tags, while the normal browser import path repairs and sanitizes them in `normalizeCadSvg` before upload. The valid CAD paint baseline must capture the final UI-uploaded SVG, not the worker's raw intermediate string.
- The real browser upload path converted `/Users/liangxu/Desktop/花语江南6-204方案.dwg` into a valid 8,868,302-byte SVG with a 1200 x 839 preview viewport.
- With that single CAD node, React zoom measured roughly 192ms p95 and even direct `.canvas-world` transform measured roughly 158ms p95, with nearly every frame over 34ms. This isolates a second bottleneck: Chromium repeatedly rasterizes the complex SVG during transform/zoom.
- Replacing only the mocked display source with a one-time 1200 x 839 transparent WebP preview (119,676 bytes) reduced both React and direct zoom to 9.2ms p95 with no frame over 20ms. The persisted node/source remained conceptually SVG; only the display surface changed in the probe.
- The engine therefore needs two independent mechanisms: retained/incremental scene updates for general node count, and cached raster display textures for complex SVG/CAD sources. LeaferJS is relevant because its Canvas image nodes naturally draw cached bitmap textures while the application can keep the original SVG URL and CAD semantics in `CanvasNode`.
- LeaferJS's React guide explicitly warns against directly binding Leafer elements to reactive data because proxy/reactive wrapping significantly reduces performance. The adapter must own mutable engine objects outside React and emit domain updates only at transaction boundaries.
- `App.zoomLayer` exposes imperative `x`, `y`, and `scale`, matching the existing `CanvasViewport` without requiring React rerenders. `App` also separates ground/tree/sky engine layers, which maps cleanly to grid, document content, and editor handles.
- Image paint supports SVG URLs and maintains an asynchronous high-performance image-layer cache by default (`sync: false`). The global default image cache ceiling is documented as 2560 x 1600, which covers the current 1200 x 839 CAD preview texture.
- The recommended dependency boundary is `leafer-ui` plus narrowly selected plugins such as `@leafer-in/editor`, not the full `leafer-editor` bundle, which also includes viewport, scrollbars, arrows, HTML, export, text editing, and other features already owned by the application.
- Editor selection/move/scale events expose world-coordinate deltas and selected UI objects. These can update the Leafer scene immediately and commit the resulting `CanvasNode[]` once on interaction completion, keeping existing persistence and Agent Chat contracts unchanged.
- A throwaway LeaferJS 2.2.2 browser benchmark with 1000 draggable rectangles measured 9.2ms p95 for both viewport updates and one-node movement, with no frame over 20ms. This validates the retained renderer against the same node count where the current React path measured about 83ms p95.
- LeaferJS does not automatically solve complex CAD SVG paint: the exact 8.87 MB SVG used directly as an image fill measured 275.4ms p95 during viewport updates. The same Leafer scene using the one-time transparent WebP preview measured 9.1ms p95.
- Recommended architecture:
- Keep `CanvasNode[]`, original SVG URLs, CAD metadata/semantics, crop/export/model-reference logic, persistence, and Agent Chat application-owned.
- Add a renderer adapter whose Leafer scene objects are mutable and never wrapped in React state. Use `App.tree` for document nodes, `zoomLayer` for viewport interaction, and engine hit testing/editor events for selection and transforms.
- Keep React for workspace chrome, Agent Chat, action panels, menus, and temporary DOM text editing. React receives selection/screen-bounds snapshots and committed transforms, not per-frame movement.
- Persist a separate optional render-preview URL for complex vector nodes. Generate transparent WebP levels from the SVG once, use the current level throughout interaction, and upgrade the level asynchronously after zoom settles. The source `content` remains SVG and is still used by crop/download/semantic/model boundaries.
- Proposed acceptance gates before replacing the default renderer: 1000 mixed nodes and the exact CAD fixture both at <=16.7ms p95 for pan/zoom/drag after resources are ready; zero 50ms+ interaction long tasks; source SVG and semantic context byte/contracts unchanged; crop and Agent Chat reference tests unchanged; pixel comparison for CAD previews at each texture level.
- Production implementation discovery: canvas-node persistence uses explicit field lists across the go-zero `.api` type, Go domain/API mappers, PostgreSQL schema/queries/SQLC models, and frontend snapshot normalization. A durable preview URL therefore requires an explicit `renderContent` field through every layer.
- The canvas upload flow currently batches one file/public URL per optimistic node. Vector preview support must upload the source SVG and transparent WebP together, then commit both URLs to the node only when the source upload succeeds; failed preview upload should fail the vector node rather than silently reverting to the known-stalling raw SVG renderer.
## 2026-07-16 CAD SVG Crop Fidelity
- The crop overlay's `1934 × 1671` label is the selected region size in canvas coordinates, not the generated sibling node size. `createVectorCropSiblingNode` still caps the sibling's longest displayed edge at 640.
- `cropImageNodeFile` currently loads the complete SVG as an `HTMLImageElement`, uses intrinsic-image source coordinates in the nine-argument `drawImage`, and scales that crop to a 2048px long edge.
- This path does not change the SVG `viewBox` before decoding, so the browser can rasterize the full drawing first and then magnify the selected pixels. That explains thick CAD strokes and degraded text/detail in the crop.
- The fix seam is to map the normalized selection into the source SVG `viewBox`, set the cropped SVG's width/height to the final WebP dimensions, decode that rewritten SVG, and draw it once with no source-rectangle resampling.
- Raster image crop behavior remains on the current source-rectangle path.
- A real Chromium pixel probe imported the production `createCroppedSvgRasterSource` function and used an SVG line with `vector-effect="non-scaling-stroke"`. The old full-SVG source-rectangle upscale expanded the line from 4px to 82px; the cropped-viewBox render stayed 4px before and after WebP encoding.
- The generated sibling display cap is independent of the crop overlay's canvas-coordinate label. The crop asset remains a 2048px-long-edge WebP for model readability while the canvas sibling stays bounded to a 640-unit long edge.
- CAD crop semantics are carried separately from the raster bytes: `canvasNodeToReferenceImage` copies `semanticContext`, the composer emits it as hidden `cad-context`, and the backend includes that context in model prompts while excluding it from visible chat text.
## 2026-07-15 Canvas Wheel Passive-Listener Bug
- The reported stack points exactly to `CanvasWorkspace/index.tsx:1242`, where the React `onWheel` handler calls `event.preventDefault()`.
- React delegates `wheel` as a passive event in this runtime, so the browser rejects `preventDefault()` even though the handler is correct to suppress page scrolling while zooming the infinite canvas.
- There is no competing native wheel listener in the workspace. The correct boundary is one native listener on `.canvas-stage` registered with `{ passive: false }`, with explicit cleanup and the existing toolbar/popover bypass guard preserved.
- DWG parsing does not directly cause the warning; the long import makes canvas wheel interaction more likely while the React passive handler is mounted.
- A first native-listener implementation still missed the canvas because its effect ran during the loading view. Binding on `project.id` attaches after `.canvas-stage` mounts; browser verification then changed zoom from 100% to 91%, returned `defaultPrevented=true`, and emitted zero passive-listener warnings.
- The persistent workspace Crop action now follows Text directly. It remains visible/disabled without a selected image, enables for ready raster/vector/CAD images, and opens the same normalized crop overlay used by the floating image toolbars.
## 2026-07-16 Enlarged Single-Image Crop
- The screenshot reproduces a different contract from selected-node cropping: one image fills or exceeds the viewport, no node is selected, and the workspace Crop button is disabled.
- Enabling Crop for every unselected multi-image canvas would be ambiguous. The deterministic fallback should apply only when exactly one visible, unlocked, ready image exists; explicit selection remains authoritative otherwise.
- Initializing an oversized image to the full `{0,0,1,1}` crop leaves resize handles and the crop panel offscreen. The initial normalized rectangle must be the intersection of the image's screen bounds with a safe visible stage area.
- Crop controls should anchor to the active selection's screen bounds, not the full oversized image bounds.
- Browser verification used an unselected 2000 x 1600 image extending beyond a 680 x 672 stage. Workspace Crop enabled automatically, selected the image, and initialized a 632 x 576 crop at the safe visible inset; the panel remained within the 1280 x 720 browser viewport.
- With two visible images and no explicit selection, Workspace Crop remains disabled as intended.
## 2026-07-15 CAD Semantics and Crop Follow-up
- The user explicitly declined legacy-project semantic recovery and will re-upload all DWG/DXF assets. Semantic guarantees therefore apply to newly converted CAD nodes; no heuristic reconstruction from old SVG snapshots is included.
- The requested feature has two coupled outputs: a visually small crop for efficient inspection and a bounded semantic payload that preserves CAD meaning beyond raster pixels.
- The source CAD-derived SVG must remain unchanged. Crop must create a sibling node and asset, enabling repeated crops from the same large drawing and independent Agent Chat follow-ups.
- Useful model semantics should prioritize layer names, block names, visible text, entity-type counts, units/extents, and crop bounds while enforcing size/count limits so large files cannot inflate every prompt.
- Existing crop infrastructure already renders any image source, including SVG, into transparent WebP, but `applyCropImage` currently replaces and repositions the selected node. CAD can reuse the overlay/rasterizer while branching to a new sibling-node save path.
- Vector nodes currently expose only the compact vector/download toolbar. Adding a crop icon there is sufficient to enter the existing crop overlay without exposing unrelated raster AI actions.
- `AgentContent` already supports `textSource`; a `cad-context` text part can carry hidden model context without a new chat payload type. Frontend optimistic display and backend user-message serialization must explicitly exclude that source while agent prompt assembly retains it.
- `CanvasNode` has no semantic field and PostgreSQL persists node columns explicitly. Durable semantics therefore require a spec-first optional node field plus domain/mapping/schema/query updates.
- The CAD converter still has the parsed `CadDocument` in the worker and can embed a structured `<metadata>` payload into the derived SVG. This preserves richer layer/block/text/entity data in the vector asset while the node stores only a bounded prompt-ready summary.
- Selected contract: the SVG metadata keeps version, source/format/units, drawing bounds, layer/block/entity counts, bounded visible text, and a bounded top-level entity index with visual-coordinate bounds. The node persists a compact human-readable `semanticContext` derived from it.
- Crop semantics can intersect the normalized crop rectangle with the SVG metadata's visual-coordinate entity bounds, producing a crop-specific layer/block/entity/text summary even though the sibling visual is WebP.
- CAD imports will use a distinct `cad-vector` layer role; crop outputs use `cad-crop`. Generic SVGs may reuse the crop action but do not claim CAD semantics.
- Existing `TextSource` behavior is ideal for model delivery: `cad-context` remains in prompt/reference-memory builders, while optimistic UI and persisted user-message rendering need a narrow visibility filter.
- PostgreSQL `canvas_nodes` uses explicit columns and SQLC-generated queries. Adding `semantic_context TEXT NOT NULL DEFAULT ''` requires schema/query regeneration plus repository/domain/API mapping.
- Exact R12 semantic verification exposed legacy GBK bytes decoded as Latin-1 by the CAD library (`¿î·âÑùÖ½°å`). The converter now repairs this high-confidence mojibake before both SVG writing and semantic extraction so labels remain meaningful.
- Exact R12 verification now completes in about 0.37 seconds with 15 model entities, 11,198 reusable block entities, corrected Chinese garment labels, and a 1,050-character bounded model context.
- Exact `花语江南6-204方案.dwg` verification completes in about 11 seconds with 4,376 model entities, 3,397 reusable block entities, 4,422 crop-indexed entries, meaningful Chinese layer/block/text names, and a 1,912-character bounded model context.
- Semantic metadata increases the large DWG-derived SVG from about 8.49 MB to about 9.05 MB while retaining the reusable-block performance model; the increase is bounded and materially smaller than the former 50 MB recursive output.
## 2026-07-15 Vector Reference Follow-up ## 2026-07-15 Vector Reference Follow-up
- Raw CAD/SVG reference text is caused by the prompt-directive parser stopping at the first closing parenthesis in filenames such as `...2026-05-05(1) (DXF).svg`; the existing capsule and hover-preview UI is already shared with WebP references. - Raw CAD/SVG reference text is caused by the prompt-directive parser stopping at the first closing parenthesis in filenames such as `...2026-05-05(1) (DXF).svg`; the existing capsule and hover-preview UI is already shared with WebP references.
- GPT Image rejects SVG input bytes even when the canvas and browser can render them. The SVG source must remain unchanged on canvas and be rasterized to alpha-preserving WebP only when building model-bound image contents. - GPT Image rejects SVG input bytes even when the canvas and browser can render them. The SVG source must remain unchanged on canvas and be rasterized to alpha-preserving WebP only when building model-bound image contents.
+89
View File
@@ -1,5 +1,35 @@
# Progress Log # Progress Log
## Session: 2026-07-15 (CAD Semantics and Crop)
### Phase 23
- Loaded the repository workflow plus planning, frontend, and Go implementation guidance.
- Added the CAD semantic-context and non-destructive crop request as new phases without changing unrelated work.
- Fixed the product invariants: preserve the source, create a sibling crop, and keep model semantics out of visible chat copy.
- Completed the end-to-end design across worker metadata, durable node context, hidden Agent Chat content, vector crop UI, sibling placement, and PostgreSQL persistence.
- Added the optional `semanticContext` node field to the API specification before generated/server implementation.
### Phase 24
- Added versioned CAD metadata containing bounded source/format/unit, drawing bounds, layer/block/entity counts, visible text, and crop-intersection entries.
- Persisted compact node semantic context through the API/domain/PostgreSQL/SQLC/canvas snapshot paths.
- Added hidden `cad-context` Agent Chat contents: model prompt and memory retain them, while optimistic and persisted user copy exclude them.
- Added vector-toolbar crop, transparent WebP output, crop-specific semantic filtering, right-side sibling placement, source preservation, and automatic selection/reference of the new crop.
- Focused Go application/logic/PostgreSQL tests pass; frontend typecheck and all 16 frontend tests pass.
- Repaired legacy GBK CAD labels and locked the behavior with a focused converter regression.
- Re-ran both supplied files: exact DXF semantics complete in 0.37s and exact DWG semantics in 11s, with bounded contexts of 1,050 and 1,912 characters respectively.
- Increased vector crop raster output to a 2048px long edge so small selections from large drawings remain useful to the image model; sibling canvas display stays bounded to 220-640px.
### Phase 25
- All 20 frontend tests pass, including CAD conversion, GBK labels, semantic crop filtering, immutable sibling creation, readable crop sizing, hidden model context, WebP submission, and vector recognition.
- `go mod tidy`, full `go test ./...`, `go build ./...`, API validation, locale parsing, frontend production build, and `git diff --check` pass.
- Browser setup again reported no available backend, so live crop/hover/request inspection could not be repeated; the active Vite preview still responds at `http://127.0.0.1:5174`.
- Restored the repository's pre-existing `frontend/next-env.d.ts` development route import after production builds.
### Phase 26
- Documented CAD semantic metadata, hidden Agent Chat context, durable node persistence, and non-destructive crop behavior in the root README and API guide.
- Completed final scope review without reverting unrelated worktree changes.
- Started an isolated updated backend on `http://127.0.0.1:8889` and the verified production frontend on `http://127.0.0.1:5175`; the proxied health endpoint returns the updated service successfully.
## Session: 2026-07-15 (Vector Reference Follow-up) ## Session: 2026-07-15 (Vector Reference Follow-up)
### Phases 21-22 ### Phases 21-22
@@ -105,6 +135,65 @@
## Session: 2026-07-15 ## Session: 2026-07-15
- Started Phase 27 after reproducing the warning directly from the source stack: `.canvas-stage` uses React `onWheel`, and its handler calls `preventDefault()` at the reported line under a passive delegated listener.
- Added the listener-registration regression first; its red run fails because the non-passive canvas wheel adapter does not yet exist.
- Added the element-level wheel adapter, switched the canvas to the latest viewport ref, removed React `onWheel`, and preserved the existing interactive-surface bypass behavior.
- The focused non-passive registration/cleanup test, TypeScript build, and diff check now pass.
- Added Phase 28 after confirming Crop existed only in the selected-image/vector floating toolbar, not in `workspace-toolbar` as requested.
- Browser instrumentation found that the first native-listener effect ran before the project canvas mounted. Bound the effect to `project.id` so the non-passive listener attaches when `.canvas-stage` appears.
- Added the Crop icon immediately after Text, with a visible disabled state, active crop state, and selection-tool handoff before opening the existing overlay.
- Full frontend verification passes with 21 tests and both Next/Vite production builds. The supplied DWG still converts in about 10.5 seconds to a 9.06 MB semantic SVG with 748 reusable block instances.
- Browser verification confirms `Text -> Crop -> Image` ordering, disabled/enabled Crop states, active state, one mounted crop overlay, functional 100% -> 91% wheel zoom, and no passive-listener warnings.
- Restarted the verified frontend preview on `http://127.0.0.1:5175` against the existing updated backend on port 8889.
- Started Phase 29 from the enlarged single-image screenshot: the workspace Crop target currently resolves only from `selectedImageNode`, so the button remains disabled even though there is one obvious image target.
- Added visible-stage crop geometry regressions first; the red run failed on the intentionally missing viewport geometry exports.
- Added shared crop viewport geometry, single-visible-image target resolution, deferred auto-selection crop entry, and selection-anchored crop controls.
- All 23 frontend tests and the Next/Vite production build pass.
- Browser verification confirms the exact enlarged-single-image case and the multi-image ambiguity guard; the updated preview is running at `http://127.0.0.1:5175`.
- Confirmed the final compatibility boundary: old project CAD nodes will not be migrated or heuristically enriched; the user will re-upload source DWG/DXF files so every retained CAD node uses the current semantic conversion contract.
- Re-ran the final verification after removing legacy recovery scope: all 20 frontend tests, full Go tests, API validation, locale parsing, Go build, Next build, Vite build, and diff checks pass.
- Restored Next's development route type import after production build and restarted the updated preview at `http://127.0.0.1:5175`; its backend proxy to `http://127.0.0.1:8889` returns HTTP 200.
## Session: 2026-07-16
### Phase 31: Canvas Performance and LeaferJS Evaluation
- **Status:** complete
- Started a diagnosis-only performance pass after the user reported canvas lag and pointed to `leaferjs/leafer-ui` as the reference engine.
- The evaluation will establish browser frame-time and long-task baselines before deciding whether LeaferJS should replace the full renderer or only the high-cost visual layer.
- Mapped the current renderer as React/DOM with viewport state updates and full-array node updates on high-frequency interaction.
- Reviewed LeaferJS's retained scene tree, Canvas renderer, hit testing, interaction, and editor capabilities; vendor benchmark claims remain unverified for the application's CAD/image workload.
- Identified viewport-dependent props and fresh per-node callbacks/objects that defeat a simple memoization fix, plus full-document alignment and node-array work on each drag event.
- Confirmed the current LeaferJS package baseline (2.2.2, MIT) and kept dependency installation out of the diagnosis phase.
- Found per-update persistence effect/timer churn and prepared a node-count-scaled Playwright baseline for zoom and drag interactions.
- Reproduced near-linear interaction degradation from 100 to 500 to 1000 nodes, reaching roughly 83ms p95 frame intervals at 1000 nodes.
- Ran the differential control on the same 1000-node DOM: direct world and node transforms held about 9.2ms p95, confirming React-path overhead as the primary bottleneck.
- The initial exact-DWG paint fixture used the raw worker intermediate and correctly failed SVG decoding; switched the probe to capture the normalized asset from the real browser upload path.
- Captured the final 8.87 MB SVG from the real UI upload and measured a single CAD node at 158-192ms p95 during zoom.
- Proved the cached-texture control: a one-time 1200 x 839 transparent WebP display preview reduced the same interaction to 9.2ms p95 while leaving the source contract separable.
- Confirmed that both the general retained renderer and complex-vector texture caching are required; neither optimization alone covers the reported workload.
- Mapped LeaferJS `App` ground/tree/sky layers, imperative `zoomLayer`, image caching, and editor events onto the existing canvas domain and React overlay responsibilities.
- Benchmarked LeaferJS itself rather than relying on vendor claims: 1000 rectangles held 9.2ms p95, compared with about 83ms in the current React path.
- Demonstrated that raw complex SVG remains slow in Leafer (275.4ms p95), while an application-owned transparent WebP texture restores 9.1ms p95.
- Completed the evaluation with an incremental renderer-adapter and vector texture-LOD recommendation; no production dependency or renderer code was changed during diagnosis.
### Phase 32: Leafer Renderer and Vector Texture Cache
- **Status:** in_progress
- Started the production implementation after user approval, using the existing `CanvasNode[]` and SVG/CAD semantic contracts as the source of truth.
- Chosen boundary: persist a separate render-preview URL, render supported visual nodes through LeaferJS, and keep React UI/overlays plus source-based crop/export/model behavior.
- Traced the explicit canvas-node field through the API/domain/PostgreSQL/snapshot layers and the paired source/preview upload changes needed for atomic vector imports.
### Phase 30: Native-Resolution Vector Crop Fidelity
- **Status:** in_progress
- Reproduced the user-visible symptom from the supplied comparison: the cropped transparent WebP has substantially thicker CAD strokes and degraded text/detail than the source SVG.
- Ranked the likely causes: low-resolution SVG rasterization followed by pixel upscaling, incorrect viewBox mapping, display-size mismatch, then WebP compression.
- Started a focused regression loop around SVG viewBox cropping and final-resolution rendering while preserving the existing raster crop path.
- Added the red regression for normalized crop mapping against a non-zero SVG viewBox, then implemented the mapping and final-size source preparation.
- Moved the browser SVG rewrite into the exported `createCroppedSvgRasterSource` production seam; `CanvasWorkspace` now draws the rewritten crop once at its final output dimensions while raster images retain the existing source-rectangle crop.
- Chromium verification measured a representative non-scaling CAD stroke at 82px through the old low-resolution upscale and 4px through the new native crop, unchanged after transparent WebP encoding.
- Reconfirmed that crop-specific CAD context travels with the sibling reference as hidden `cad-context` and remains available to the image-generation backend.
- Full verification passes: 24 frontend tests, `pnpm exec tsc -b`, `go test ./...`, `go build ./...`, Next/Vite production builds, and `git diff --check`.
- Restored Next's development route type import after the production build and restarted the updated preview at `http://127.0.0.1:5175`; the page and backend proxy return HTTP 200 with no browser console errors.
### Phase 15: CAD Import Discovery and Design ### Phase 15: CAD Import Discovery and Design
- **Status:** in_progress - **Status:** in_progress
- Loaded the repository go-zero instructions plus the applicable Go, frontend, and file-planning skills. - Loaded the repository go-zero instructions plus the applicable Go, frontend, and file-planning skills.
+87 -1
View File
@@ -4,7 +4,7 @@
Persist Brand Kits by authenticated user, bind one optional Brand Kit to each project/canvas, expose a canvas-header selector, and make every Agent Chat turn and newly created thread use the selected kit as authoritative creative context. Persist Brand Kits by authenticated user, bind one optional Brand Kit to each project/canvas, expose a canvas-header selector, and make every Agent Chat turn and newly created thread use the selected kit as authoritative creative context.
## Current Phase ## Current Phase
Phases 21-22 Phase 32
## Phases ## Phases
@@ -139,6 +139,72 @@ Phases 21-22
- [x] Add focused regression coverage and verify prepared model content receives a WebP asset URL - [x] Add focused regression coverage and verify prepared model content receives a WebP asset URL
- **Status:** complete - **Status:** complete
### Phase 23: CAD Semantics and Crop Discovery
- [x] Map CAD entity/layer/block/text data through conversion, canvas persistence, references, and Agent Chat generation
- [x] Map the existing crop overlay, sibling-node placement, vector toolbar, and upload/save paths
- [x] Define bounded semantic metadata and crop-output contracts for large drawings
- **Status:** complete
### Phase 24: CAD Semantics and Crop Implementation
- [x] Preserve bounded CAD semantics during DWG/DXF conversion and canvas persistence
- [x] Attach CAD semantic context to model-bound references without exposing it in user-visible chat text
- [x] Add vector crop action that preserves the source and creates a cropped sibling asset
- [x] Make cropped CAD regions attachable and reusable in Agent Chat
- **Status:** complete
### Phase 25: CAD Semantics and Crop Verification
- [x] Add conversion, persistence, semantic-payload, crop, and source-preservation regression coverage
- [x] Verify both supplied CAD files, focused Go/frontend tests, typecheck, and production builds
- [x] Attempt real-browser crop/reference inspection and record the unavailable browser backend
- **Status:** complete
### Phase 26: CAD Semantics and Crop Delivery
- [x] Review scope and generated files without altering unrelated changes
- [x] Document the semantic contract, crop behavior, and preview URL
- **Status:** complete
### Phase 27: Non-Passive Canvas Wheel Regression
- [x] Add a deterministic regression test for non-passive element-level wheel listener registration and cleanup
- [x] Replace the React synthetic canvas wheel handler with a native `{ passive: false }` listener
- [x] Verify zoom behavior, DWG upload flow, focused/full tests, and production build
- **Status:** complete
### Phase 28: Workspace Toolbar Crop Action
- [x] Add a persistent Crop icon immediately after the Text tool
- [x] Enable it only for a selected, ready image/vector node and reuse the existing crop overlay
- [x] Verify icon order, disabled/active states, crop entry, tests, and production build
- **Status:** complete
### Phase 29: Enlarged Single-Image Crop
- [x] Add regression coverage for translating the visible viewport intersection into normalized crop coordinates
- [x] Let workspace Crop automatically target the only visible image when none is selected
- [x] Anchor the crop selection and controls inside the visible viewport for oversized images
- [x] Verify enlarged-image crop entry, multi-image ambiguity, tests, and production build
- **Status:** complete
### Phase 30: Native-Resolution Vector Crop Fidelity
- [x] Reproduce the CAD SVG crop stroke/text degradation with a deterministic regression seam
- [x] Crop SVGs by rewriting their viewBox and render once at the final WebP resolution
- [x] Preserve SVG structure, transparency, CAD semantics, source immutability, and raster crop behavior
- [x] Verify crop output dimensions/display sizing, focused tests, production builds, and browser behavior
- **Status:** complete
### Phase 31: Canvas Performance and LeaferJS Evaluation
- [x] Build a repeatable browser performance baseline for pan, zoom, selection, and large CAD nodes
- [x] Isolate React/DOM reconciliation, SVG rasterization, overlays, persistence, and event-path costs
- [x] Map LeaferJS capabilities and integration constraints against the existing canvas domain model
- [x] Recommend the smallest migration boundary with measurable acceptance criteria
- **Status:** complete
### Phase 32: Leafer Renderer and Vector Texture Cache
- [ ] Extend the API/domain/storage contract with an optional vector render-preview URL
- [ ] Generate and upload transparent WebP previews while preserving original SVG/CAD source and semantics
- [ ] Add a LeaferJS visual renderer adapter for image and manual-frame nodes behind the existing canvas domain
- [ ] Move wheel/pan and node drag hot paths to imperative engine/DOM updates with commit-on-end state synchronization
- [ ] Preserve crop, export, selection, context menus, overlays, Agent Chat references, and persistence behavior
- [ ] Add focused regression/performance coverage and run full frontend/backend/build/browser verification
- **Status:** in_progress
## Invariants ## Invariants
- Brand Kits are owned and queried only through the authenticated user ID from request context. - Brand Kits are owned and queried only through the authenticated user ID from request context.
- A project may reference zero or one Brand Kit owned by the same user. - A project may reference zero or one Brand Kit owned by the same user.
@@ -160,6 +226,8 @@ Phases 21-22
- SVG-backed image nodes use vector-only actions and download their source SVG rather than exposing raster editing actions. - SVG-backed image nodes use vector-only actions and download their source SVG rather than exposing raster editing actions.
- User-message image directives render through one attachment capsule/preview path regardless of raster or SVG source format. - User-message image directives render through one attachment capsule/preview path regardless of raster or SVG source format.
- SVG remains the persisted canvas source; transparent WebP derivatives exist only for model-compatible image inputs. - SVG remains the persisted canvas source; transparent WebP derivatives exist only for model-compatible image inputs.
- CAD source nodes are immutable during crop; every crop is a sibling asset positioned beside the source.
- CAD semantic context is bounded, persisted with the relevant source/crop, and sent as model context without becoming visible user-message copy.
## Errors Encountered ## Errors Encountered
| Error | Attempt | Resolution | | Error | Attempt | Resolution |
@@ -197,3 +265,21 @@ Phases 21-22
| First post-fix parser assertion still expected no `index` property | 1 | The production parser correctly emits `index: undefined`; align the regression fixture with the stable result shape. | | First post-fix parser assertion still expected no `index` property | 1 | The production parser correctly emits `index: undefined`; align the regression fixture with the stable result shape. |
| In-app browser discovery returned no available browser backend | 1 | Followed the browser skill troubleshooting flow and confirmed an empty backend list; retain automated tests/builds and report the interactive verification gap. | | In-app browser discovery returned no available browser backend | 1 | Followed the browser skill troubleshooting flow and confirmed an empty backend list; retain automated tests/builds and report the interactive verification gap. |
| Combined final planning patch targeted progress lines as task-plan context | 1 | Split the update by file and apply each change against its exact current section. | | Combined final planning patch targeted progress lines as task-plan context | 1 | Split the update by file and apply each change against its exact current section. |
| Local `mcp-zero` stdio initialization returned no JSON-RPC response | 1 | API validation succeeded; use the documented `goctl api go` fallback with the existing `go_zero` style. |
| TypeScript did not infer that a crop existed from the derived crop-bounds check | 1 | Guard the scope line with both `crop` and `cropBounds`; Go targeted tests already pass. |
| One combined patch contained a malformed hunk boundary | 1 | Reissued the same two-file edit with valid patch boundaries; no source change was lost. |
| Node's strip-types test runner could not resolve the new extensionless runtime import | 1 | Keep the shared semantic types as a stripped type-only import and define the stable metadata element ID locally in the worker module. |
| Synthetic text-only CAD fixture did not expose a library bounding box | 1 | Add a simple visible line to establish drawing bounds while keeping the text-encoding assertion focused. |
| A second planning/error patch initially repeated the malformed hunk separator | 1 | Removed the empty hunk marker and applied the intended fixture/error update cleanly. |
| Isolated Next dev preview was rejected by an existing repository-wide dev lock | 1 | Keep the updated backend on 8889 and run the verified production Next build on 5175 with its API proxy pointed at 8889. |
| zsh rejected the optional `frontend/.env*` inspection glob because no env file exists | 1 | Search the concrete Next/Vite config and source paths without an unmatched optional glob. |
| Combined toolbar patch expected the wrong `onChooseShapeKind` callback name | 1 | Re-read the exact `WorkspaceToolbar` prop block and apply the component and call-site changes against current code. |
| First browser wheel probe stayed at 100% because the stage listener was never attached | 1 | The initial effect ran while the project-loading view had no `.canvas-stage`; re-run listener binding when `project.id` mounts the canvas. |
| Playwright `run-code` rejected a bare `await page...` snippet despite the abbreviated skill example | 1 | Read command help and pass an `async (page) => { ... }` function as required by the installed CLI. |
| The first mocked route handler used `URL`, which is unavailable in the Playwright callback sandbox | 1 | Derive the pathname from `route.request().url()` with bounded string operations; the corrected route exposed the real canvas. |
| Initial combined Phase 30 test/findings patch assumed a generic `# Findings` heading | 1 | Re-read the file header and apply the regression test and findings as separate patches against the exact heading. |
| Exact-DWG performance fixture served the raw converter intermediate and Chromium rejected anonymous `</>` tags | 1 | Use the real browser upload path so `normalizeCadSvg` repairs and sanitizes the final SVG before measuring paint/interaction cost. |
| Playwright file-input locator matched both canvas upload and Agent Chat upload controls | 1 | Target the first canvas file input explicitly for the real DWG upload probe. |
| Playwright request context bypassed the page's mocked asset route and could not resolve `asset.local` | 1 | Fetch the captured normalized SVG inside the page context so the route handler supplies the bytes. |
| Combined performance findings patch matched entries in a different order than the current file | 1 | Re-read the exact section and apply the package/result additions against their real positions. |
| Direct CDN import used LeaferJS's package ESM build, whose bare `@leafer/core` specifier is not browser-resolvable | 1 | Load the official prebundled browser distribution for the throwaway benchmark. |