Files
moteva/findings.md
T
2026-07-15 12:09:10 +08:00

151 lines
28 KiB
Markdown

# Findings: Server-Backed Brand Kit Canvas Context
## 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.
- Browser rasterization is the appropriate conversion boundary because the generated CAD SVG relies on reusable definitions, `<use>`, patterns, text, and browser SVG rendering semantics.
- `CanvasWorkspace.generate` is the shared Agent Chat and image-generator submission boundary. It currently uses the same `AgentContent[]` for optimistic display and the socket request, so model URL normalization must produce a separate request copy while the visible message retains the original SVG URL/name.
- SVG detection cannot rely only on public URL suffixes because uploaded asset URLs may not preserve the source extension; the `AgentContent` image name and fetched response MIME/type must also participate.
- `PromptComposer.toAgentContents()` already preserves `imageName`; `ImageGeneratorFloatingComposer` currently drops it and must retain the reference name so extension detection and user-facing labels remain reliable.
- Home creation uses serialized prompt directives rather than `AgentContent[]`, so it requires the same reference normalization before serialization if SVG attachments can immediately trigger generation there.
- The requested canvas scope has two model-bound submission sources, Agent Chat and the floating image-generator composer; both converge on `CanvasWorkspace.generate`, so one adapter at that boundary covers them without touching canvas persistence.
- The existing timeline capsule already routes canvas asset URLs through SVG-safe thumbnail/preview URL helpers and provides its copy behavior; once the directive parser recognizes the full filename, no separate vector-specific UI is needed.
## Requirements
- The previous implementation is frontend-only and insufficient for durable user ownership.
- The canvas header needs a compact Brand Kit selector beside the project name.
- The selector contains None plus the authenticated user's Brand Kits and shows the active selection.
- Agent Chat and every new session within that canvas must use the selected Brand Kit as a creative baseline.
- The Home prompt composer also needs a Brand Kit picker. Its empty state provides a direct `创建 Brand Kit` action.
## Initial Decisions
- Persist the project-to-kit relation on the server and inject context server-side so clients cannot silently omit it.
- Preserve the existing restrained Moteva canvas visual language and use the supplied interaction hierarchy without copying product identifiers.
## Repository Discovery
- The server is an existing go-zero REST service with one `img_infinite_canvas.api`, generated handlers/types, thin logic files, and a shared `ServiceContext`.
- PostgreSQL persistence is SQLC-based (`server/sqlc.yaml`, `schema.sql`, `query.sql`) while memory storage supports local development and tests.
- The local `mcp-zero` binary and `/Users/liangxu/go/bin/goctl` are both available; API generation can follow the required spec-first workflow.
- The frontend Brand Kit implementation is still uncommitted and browser-local, so it must be migrated carefully without discarding its editor/model work.
- Playwright prerequisites are available, and the user has an authenticated browser session for final integration testing.
- `design.Project` is the common object passed through chat planning, conversation, and image-prompt generation, so adding resolved Brand Kit context there gives one reliable injection point for all Agent Chat flows.
- `design.Repository` currently owns project CRUD; both memory and PostgreSQL implementations enforce user scope through `design.UserIDFromContext`.
- Projects currently persist title/canvas/thread state but no `brandKitId`; SQLC generates PostgreSQL accessors from `schema.sql` and `query.sql`.
- Agent messages already carry `thread_id`, and the request can start a new thread; the selected kit should therefore be loaded from the project before every chat operation rather than copied into individual messages.
- `DesignService.AgentChat` reloads the project before planning every turn, then passes project-scoped copies to planning, research, conversation, and background generation. Hydrating the Brand Kit immediately after that load covers existing and newly created threads.
- Creative-agent prompt builders already centralize project identity through `projectBriefContext`, while deterministic/fallback image generation reads `design.Project`; Brand Kit context should be added to both centralized context builders and agent memory.
- Project creation has synchronous and asynchronous paths. Both should accept an optional requested kit and otherwise bind the authenticated user's default kit server-side.
- An explicit Home selection should override the default kit; the server must still verify that the selected kit belongs to the authenticated user.
- Home's composer has a compact action row with existing Radix Popovers for model selection, making a palette-icon Brand Kit popover a native extension rather than a new interaction pattern.
- Home currently appends a browser-local default kit prompt. This must be removed once project creation sends `brandKitId`, preventing duplicated or client-controlled brand instructions.
- Frontend `Project` and canvas snapshot merge paths need to preserve `brandKitId`; the direct project-document endpoint remains the authoritative source.
- Canvas workspace already centralizes title rendering and project updates, so the selector can live beside `WorkspaceTitle` and update the same in-memory project after a binding API call.
- The existing Brand Kit document is already versioned and contains the requested grouped colors, uploaded fonts with optional sizes/descriptions, logos, cover, references, voice, and visual direction. The server can validate this exact version-1 shape and compile it into authoritative prompt context.
- The project repository abstraction has a single `Save` operation and user-scoped `Get`; adding `BrandKitID` to `design.Project` will naturally flow through memory/cache/realtime wrappers as long as PostgreSQL mapping and API mappers are updated.
- `ServiceContext` constructs storage-specific modules beside the project repository. A dedicated Brand Kit service/store should follow the existing auth/sharing module pattern and use the same configured memory/PostgreSQL driver.
- All `/api/*` routes except the explicit public allowlist already require a valid bearer session, so new `/api/brand-kits` routes automatically enforce login before logic runs.
- SQLC is installed locally. The existing PostgreSQL repository runs the idempotent schema at startup, making schema/query changes plus SQLC regeneration the consistent persistence path.
- Direct image mode currently sends the raw user prompt to image generation, bypassing the creative planner. Brand context therefore must also be added to the direct/fallback image prompt builders, not only long-term memory and `projectBriefContext`.
- Asynchronous create/follow-up jobs reload projects from persistence, so Brand Kit context must be re-resolved inside those job execution paths; hydrating only the initial HTTP turn would be insufficient.
- Uploaded Brand Kit logos, cover images, and reference images are resolved server-side as ephemeral project inputs. Image generation appends them after user-supplied references so existing inline reference numbering remains stable.
## Device Management Request (2026-07-11)
- The requested visual is a restrained account settings page headed `设备管理`, with a safety-limit explanation, a bulk removal action, and a table of active devices.
- The repository already contains `list_account_devices_logic.go`, `remove_account_devices_logic.go`, and an `AccountManagementDialog`, so the first implementation step is to verify and complete the existing vertical slice instead of creating a parallel settings system.
- The target interaction must cover loading, API failure, no secondary devices, current-device labeling, disabled destructive actions, confirmation, and responsive behavior.
- The account dialog already renders a device section and calls `authGateway.devices()` / `removeDevices()`, but it silently fabricates a current-device row whenever the API is empty or fails. That masks production errors and makes destructive-action state unreliable.
- The existing removal handler has no pending/error guard around the async request, so repeated confirmation clicks or a network failure can produce duplicate calls or an unhandled rejection.
- Existing localized copy says bulk removal invalidates the current session, while the visible button is disabled when only the current device remains. Backend behavior must be treated as the source of truth and the copy aligned to it.
- The server device service is currently a stub: `ListDevices` always synthesizes one unknown desktop row and both `RemoveOtherDevices` and `Logout` return zero. There is no persistent session/device registry.
- Access and refresh tokens currently identify only the user and token type. Production-grade selective revocation therefore requires a random session ID claim plus a server-side session record; user-only stateless tokens cannot distinguish the current device from other logins.
- The desired bulk action is already named `RemoveOtherDevices` and the UI disables it when only the current session exists, so the consistent contract is: preserve the active session, revoke every other session, and update confirmation copy accordingly.
- The authentication store supports users and verification codes only. A complete implementation must add session persistence to both memory and PostgreSQL stores, bind token validation to active session state, and capture request/device metadata without putting HTTP concerns into business logic.
- Selected architecture: signed access/refresh tokens receive an opaque random `sid`; active sessions are persisted with user, device, creation, expiry, last-seen, and revocation timestamps; every bearer authentication checks the session registry.
- Backward compatibility: an otherwise-valid legacy access token without `sid` is mapped to a stable digest-derived session only after confirming that its subject is an existing user. This avoids a forced logout during rollout and prevents signed state tokens from becoming user sessions.
- Middleware will attach sanitized request metadata for public login routes and authenticated requests, then attach the resolved session ID to request context. Business logic remains HTTP-agnostic.
- Last-seen writes will be throttled and active device rows sorted current-first, then most recently seen. Device metadata is informational only.
- PostgreSQL uses the repository's existing idempotent embedded schema; memory and PostgreSQL stores will implement the same atomic revoke-others contract.
- `github.com/mileusna/useragent` is now the selected parser. It exposes normalized browser, OS, device, desktop/mobile/tablet, and bot fields while keeping authorization independent from user-agent data.
- The Next.js API proxy forwards incoming headers after removing only hop-by-hop headers, so browser UA and Client Hint metadata reach the Go middleware without a new client contract.
- The current device table is rendered as three independent columns rather than semantic rows. This makes loading/error/empty states awkward, can misalign cells, and forces horizontal scrolling on mobile.
- The dialog already has restrained neutral styling consistent with the supplied reference. The right approach is to preserve that language while replacing only the device slice with a row grid, explicit async state machine, accessible status text, and mobile stacking.
- Current helper text fabricates an extra empty separator in the device string. The replacement will expose normalized primary metadata and a quieter client/last-seen line instead of leaking placeholder delimiters.
- Follow-up requirement: the desktop and mobile session counts must be configurable. The backend config will be the source of truth, `GET /api/account/devices` will return the effective limits, and localized UI copy will interpolate those values.
- Bug confirmation: `TestDesktopDeviceLimitRevokesOldestSession` fails because `upsertAndSign` creates sessions without consulting `DeviceLimits`; the values currently affect only API/UI presentation.
- Enforcement policy: limits apply independently to normalized desktop and mobile sessions. A new login succeeds and atomically revokes the oldest same-type session, so the latest login wins and active sessions never exceed the configured count.
- Existing sessions created before enforcement are reconciled when the authenticated device list is read, preserving the current session first and then the most recently created sessions.
- Follow-up display requirement: `lastSeenAt` must be a timezone-neutral instant over the API. The backend will emit RFC3339 UTC and the browser will format it with `Intl.DateTimeFormat` using its local timezone and the selected UI locale.
- No configured `mcp-zero` callable tool is available in this session, so API validation/generation will use the documented `goctl` fallback after the `.api` spec is updated.
## Visual Annotation Request (2026-07-11)
- The supplied reference adds `标注改图` as an image action with brush, circle, rectangle, and cross tools, a mark list, and generation controls.
- The repository already has general canvas point annotations for Agent Chat, but those annotations are not image-local drawing geometry and cannot represent marked regions.
- `CanvasWorkspace` owns image action mode state and already routes selected-image operations through a shared asynchronous generation path; the new mode should extend that path rather than add a parallel API.
- The requested `告诉 AI 怎么改` content needs to be an editable instruction per visual mark, not a fixed tool-name label as shown in the reference HTML.
- Existing unrelated user edits are limited to `frontend/src/app/layout.tsx`, `CanvasAgentComposer/index.css`, and `ImageDropOverlay/index.css`; the annotation work must preserve them.
- The existing node action contract already carries an open action string, prompt, selection, mask, and options map, but the backend rejects unknown normalized action names and only sends the original source image to the image model.
- A reliable annotated edit should send both the clean source and a browser-rendered annotated guide image; coordinate-only text is less robust for freehand marks and irregular regions.
- The browser can rasterize the selected node through the existing `imageNodeRasterBlob`, draw normalized annotation geometry and numbered badges, upload the guide with `designGateway.uploadImage`, and submit its URL through a dedicated spec field.
- The server should treat the guide as temporary visual context: include it as a second image in the action message, tell the model never to reproduce the marks, and keep the clean source as the output base.
- The selected canvas image model and credit metadata already exist in `CanvasWorkspace`; the annotation panel can expose the same model selection rather than invent a separate model state.
- Existing side panels use viewport-aware positioning and restrained neutral surfaces. The annotation UI should follow this dense operational style while keeping all drawing geometry directly on the selected image.
- The implemented contract adds `annotationImage` to the existing node-action request. The backend requires it for `visual-annotation`, keeps the clean source first, appends the guide second, and stores both URLs in generator-task metadata for traceability and asset cleanup.
- Brush, circle, rectangle, and cross marks use normalized coordinates, so the on-canvas overlay, uploaded guide raster, structured action payload, and resized canvas view stay aligned.
- Generation remains blocked until every mark has a non-empty editable instruction, preventing unlabeled regions from reaching the image model.
- Follow-up invariant: visual annotation never changes the source node. Submission creates a sibling copy to the source's right; generation state, failures, and the final image are isolated to that copy.
## CAD Import Request (2026-07-15)
- The infinite canvas should accept DWG and DXF drawings and display them through a derived SVG or canvas-compatible representation.
- The existing import, upload, persistence, and rendering architecture must be traced before selecting a converter or API boundary.
- The frontend is React 19/Next 16/Vite and currently has no CAD-specific dependency; `lucide-react` is the existing icon source.
- The backend is a single go-zero REST service. Any new REST conversion boundary must be added to `server/img_infinite_canvas.api` before generation.
- The working tree already contains a user-owned change to `frontend/next-env.d.ts`; CAD work must not modify or revert it.
- Canvas image upload is centralized through `designGateway.uploadImage`, with drag/drop filtering in `ImageDropOverlay` and an upload command in `WorkspaceToolbar`.
- The canvas already supports SVG image URLs and exports vector/image nodes to SVG or raster, making SVG the natural render target for CAD conversion.
- Existing client image upload preprocessing intentionally preserves SVG, while server asset uploads are generic `fileName` plus `contentType` requests.
- Image nodes render regular image URLs, including SVG, and persist their URL plus world-space dimensions in the existing canvas snapshot; no new canvas node type is required.
- `uploadImagesToCanvas` already performs optimistic node creation, parallel upload, selection, save, and failure cleanup. CAD import should convert first, then feed derived SVG `File` objects into this path.
- The current file input and drag/drop gates are image-only, so both explicit selection and DataTransfer parsing need CAD-aware file recognition and copy.
- No DWG/DXF converter CLI is installed in the local environment (`dwg2svg`, `dwg2dxf`, `dxf2svg`, LibreDWG tools, and Inkscape are absent).
- `dxf-parser` is available from npm and can cover browser-side ASCII DXF parsing, but it does not parse binary DWG; DWG needs a real LibreDWG/ODA/cloud conversion boundary rather than treating the binary file as SVG.
- The server configuration pattern supports adding a configurable CAD converter, but a default that depends on a missing host executable would leave local DWG import nonfunctional.
- Browser-capable packages now exist for DWG: `dwgdxf` provides an MIT-licensed WASM DWG-to-DXF converter, while several LibreDWG viewer/parser packages are GPL/AGPL and unsuitable as a casual dependency in a potentially proprietary app.
- A clean client pipeline is feasible: convert DWG bytes to DXF in a worker/WASM path, parse DXF, serialize supported entities into a sanitized standalone SVG, then reuse the existing image upload and canvas rendering flow.
- The canvas file input lives inside the stage and currently accepts `image/*`; the drag helpers are local functions in `CanvasWorkspace/index.tsx`, so CAD recognition can be scoped to this workspace without changing Home's reference-image drop behavior.
- `@node-projects/acad-ts` 2.3.0 is the strongest single dependency: MIT licensed, TypeScript-native, reads ASCII/binary DXF and DWG AC1014 through AC1032, and includes an SVG writer. Its 6.4 MB unpacked size argues for dynamic import so ordinary canvas startup is unaffected.
- `dwgdxf` is smaller and MIT but would still require a second DXF parser and a custom SVG renderer. The single `acad-ts` document model should preserve more entity/layer/text behavior with less application code.
- `@node-projects/acad-ts` ships as dependency-free ESM with first-class type declarations; its compressed tarball is about 969 KB, making a lazy-loaded import acceptable for CAD-only use.
- The frontend has no existing unit-test runner. Verification must rely on focused converter fixtures/manual scripts plus the existing TypeScript, Next, and Vite production builds unless introducing a runner becomes necessary.
- The selected package contains pure ESM/browser-neutral code with no Node built-in imports detected. It exports `DwgReader`, `DxfReader`, `SvgWriter`, and SVG converter internals from the main entry.
- The package's reader API accepts raw `ArrayBuffer` for DWG and `Uint8Array` for DXF, which aligns directly with browser `File.arrayBuffer()`.
- Source inspection found suspicious/incomplete closing-tag logic in the packaged `SvgXmlWriter`; the advertised SVG writer must pass a real fixture test before it is trusted. If it fails, retain its CAD readers but implement a small application-owned SVG serializer over the parsed document.
- The runtime fixture confirmed the writer emits invalid `</>` closing tags and maps default CAD color to white. The readers remain usable, but raw writer output cannot be uploaded directly.
- The writer's entity geometry is still valuable if its deterministic closing placeholders can be repaired and the root transform/color normalized before DOM validation; this is materially smaller than reimplementing arcs, hatches, inserts, and text.
- `SvgXmlWriter` can write to an `ArrayBuffer` sink and expose its complete string via `getOutput()`, avoiding fixed output buffer sizing.
- Final architecture: run the pure TypeScript DWG/DXF readers and geometry writer in a module worker; repair the writer's deterministic close placeholders; validate/sanitize with browser XML APIs; move the Y-axis flip into a bounded group; map CAD index-7 white to dark ink; size the derived SVG for canvas display; upload only that SVG.
- CAD files will be accepted only by the canvas asset input/drop path. Home/reference/clipboard image behaviors remain image-only.
- The installed package exposes `SvgXmlWriter` directly, so conversion can capture the generated string without guessing an output-buffer size. Text, groups, hatches, and inserts all use the same anonymous closing placeholder, enabling deterministic stack-based repair before XML parsing.
- Normalization will allowlist generated SVG elements/attributes, remove CSS/external-resource attributes, keep CAD text upright inside a drawing-wide Y flip, remap context-dependent CAD white to dark ink, and enforce readable non-scaling strokes.
- The implementation passes strict TypeScript compilation and locale JSON parsing. The worker uses the library collection's declared `count` API and preserves the existing image-only clipboard/reference paths.
- The full frontend build passes. Vite emits CAD conversion as a separate 1.02 MB worker chunk (201 KB gzip), so normal canvas JavaScript does not absorb the parser cost.
- Local preview dependencies are already listening on frontend port 5173 and backend port 8888. Playwright CLI prerequisites are available.
- Next's production output also includes worker-related chunks plus a `.ts` media asset; real-browser testing must confirm Turbopack serves executable worker code instead of treating the TypeScript source as a passive URL asset.
- The process currently occupying port 5173 belongs to an unrelated Electron/Vite project, not this repository. This app needs an isolated preview port.
- The selected CAD library repository provides real ASCII/binary DXF and DWG fixtures across AC1014-AC1032, including `samples/svg/export_sample.dwg`, enabling format-level browser verification without fabricated test data.
- Inspection confirmed the Turbopack media asset is raw TypeScript with a bare package import, so it would fail as a browser worker. Next must use a worker-aware build mode or the conversion must move off-worker.
- Next 16 officially supports `--webpack` for both dev and build. After switching scripts, the full Next+Vite build passes and the raw `.ts` worker media asset is gone; webpack emits the CAD dependency as a separate ~828 KB JavaScript chunk instead.
- Real-browser import of the selected library's own 116 KB DXF fixture fails before SVG generation because the parsed document exposes no model-space block. This invalidates the assumption that advertised DXF support is production-ready.
- The user's real failing file is a 915,957-byte AutoCAD R11/R12 (AC1009) DXF. R12 support is therefore a hard compatibility requirement, not an optional legacy case.
- Node verification shows the larger AC1015 upstream DXF does contain 161 model-space entities, while the geolocation fixture legitimately contains zero. The first browser failure still needs separation from the empty-fixture result before replacing all parsing.
- The exact R12 file parses successfully into 15 model-space entities referencing eight blocks and 11,205 nested drawing entities. The failure occurs later in `SvgXmlWriter`: its point/text/fill paths read raw ByLayer color index 256, whose RGB lookup is undefined, instead of using the resolved layer color.
- Resolving inherited entity colors before SVG serialization removes that library failure without changing the source file. The exact drawing now converts in about 0.6 seconds to a 2.24 MB SVG containing 10,518 points, 565 text elements, 114 polylines, and eight lines.
- Browser verification against the exact WeChat DXF completed the derived-SVG upload and project save with HTTP 200 responses, displayed nonblank paper-pattern geometry, emitted no console errors, and preserved the CAD node after reload.
- Next's production minifier renames `acad-ts` constructors, but the library uses `constructor.name` as its DXF metadata key; without repair, the parsed entities keep zero-valued geometry. The worker now restores stable names from the package's export keys before parsing, preserving normal production minification.
- The normally minified Next production build now imports the exact R12 DXF and the upstream `export_sample.dwg` fixture successfully; both produce persisted SVG image nodes without console errors.
- The second user-provided file, `/Users/liangxu/Desktop/花语江南6-204方案.dwg`, is an AutoCAD 2007/2008/2009 drawing with 4,376 model-space entities, 362 blocks, and 15,729 block entities. Parsing takes roughly 9-13 seconds, but the third-party SVG writer takes about 35 seconds and emits roughly 50 MB.
- Profiling at low arc tessellation shows recursive `INSERT` expansion contributes about 37.6 MB, while hatches contribute about 4 MB and all other model-space entity types about 1.3 MB. Increasing the timeout or reducing arc points would not address the dominant cost.
- `SvgXmlWriter._writeInsert` expands the referenced block at every occurrence and merges translation only, dropping scale and rotation. A generic fix must own reference assembly: serialize each block once, emit `<use>` instances with complete matrices, support MINSERT row/column instances, and guard nested reference cycles.
- The existing `VectorizedImageToolbar` already matches the requested compact `矢量` plus download UI. Its caller only identifies `layerRole === "vectorized-image"`, so normal uploaded SVGs and CAD-derived SVGs incorrectly receive the raster `image-node-toolbar`.
- SVG export already preserves SVG-backed source content through `downloadSvgSource`; vector detection can route the toolbar without changing the download implementation.
- Browser inspection of the second DWG reproduced the tiny-content symptom after the performance fix. Its extreme bounds come from one `MTEXT` entity around 550,000 drawing units away from the actual plans; the source marks that entity `isInvisible`, but the third-party SVG writer ignores CAD visibility. Excluding invisible/off/frozen-layer entities from both SVG output and bounds is the generic display fix.