diff --git a/findings.md b/findings.md index 6724140..7fdae5d 100644 --- a/findings.md +++ b/findings.md @@ -1,5 +1,93 @@ # 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 ``, 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 `` 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 - 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. diff --git a/progress.md b/progress.md index 15bb80c..dfd8ba7 100644 --- a/progress.md +++ b/progress.md @@ -1,5 +1,35 @@ # 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) ### Phases 21-22 @@ -105,6 +135,65 @@ ## 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 - **Status:** in_progress - Loaded the repository go-zero instructions plus the applicable Go, frontend, and file-planning skills. diff --git a/task_plan.md b/task_plan.md index 95eb075..80681b6 100644 --- a/task_plan.md +++ b/task_plan.md @@ -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. ## Current Phase -Phases 21-22 +Phase 32 ## Phases @@ -139,6 +139,72 @@ Phases 21-22 - [x] Add focused regression coverage and verify prepared model content receives a WebP asset URL - **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 - 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. @@ -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. - 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. +- 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 | 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. | | 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. | +| 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. |