Skip to content

[#204] Build SVG/HTML lettering editor foundation#217

Merged
realproject7 merged 3 commits into
mainfrom
task/204-lettering-editor-foundation
May 27, 2026
Merged

[#204] Build SVG/HTML lettering editor foundation#217
realproject7 merged 3 commits into
mainfrom
task/204-lettering-editor-foundation

Conversation

@realproject7
Copy link
Copy Markdown
Owner

Summary

  • Add overlay data model (app/lib/overlays.ts): Overlay type, toPixel/toNorm coordinate normalization, createOverlay factory
  • Add LetteringEditor component: renders clean image as background, positions overlays using normalized coordinates that scale with container size, click-to-select with inspector panel, click-to-deselect
  • Extend Cut interface with overlays: Overlay[] field (defaults to [], backwards compatible)
  • Integrate editor into CutListPanel via "Open editor" button for cuts with clean images
  • Editor opens for cartoon cuts only; fiction editor and terminal unaffected
  • 17 new tests: coordinate normalization (10), editor rendering/selection/deselection (6), CutListPanel editor button (1)

Test plan

  • npm run typecheck passes
  • npm run lint passes (no new errors)
  • npm run test passes (132 tests)
  • Fiction editor and terminal behavior unchanged
  • Cartoon cut with clean image shows "Open editor" button
  • Editor renders clean image as background
  • Overlays positioned using normalized coordinates (scale with resize)
  • Click overlay to select → inspector shows type, position, text
  • Click empty area to deselect
  • Close button returns to cut list

Closes #204

🤖 Generated with Claude Code

Add overlay data model (Overlay type, coordinate normalization utils)
and LetteringEditor component. Editor renders clean image as
background, positions overlay elements using normalized coordinates
that scale with container size, supports click-to-select with
inspector panel showing type/position/text, and click-to-deselect.

Extend Cut interface with overlays field (defaults to []).
Integrate editor into CutListPanel via "Open editor" button for
cuts with clean images. 17 new tests: coordinate normalization (10),
editor rendering/selection (6), CutListPanel editor button (1).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator

@project7-interns project7-interns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES

Summary

The PR adds the requested editor surface and selection/inspector foundation, but the coordinate mapping is tied to the editor container instead of the rendered image. That breaks the core #204 requirement that overlay coordinates scale correctly with preview size.

Findings

  • [high] Overlay coordinates are computed against the full container, not the displayed image bounds
    • File: app/web/components/LetteringEditor.tsx:127
    • Details: The clean image is rendered with w-full h-full object-contain, so whenever the editor container and image aspect ratios differ, the browser letterboxes the image inside the container. Overlay positions/sizes are then computed from containerSize.width/height at app/web/components/LetteringEditor.tsx:135, with no offset for the actual image rectangle. For example, a vertical webtoon image in a wide editor surface will be centered with horizontal padding, but x: 0 overlays render at the container's left edge instead of the image's left edge. That means saved normalized coordinates will not line up with the clean image across preview sizes.
    • Suggestion: Measure the rendered image box (or render the image and overlays inside a shared aspect-ratio wrapper sized to the actual displayed image), then compute overlay left/top/width/height relative to that image box, including any object-contain offsets. Add a regression test that uses a container/image aspect-ratio mismatch and asserts overlay placement is relative to the image bounds, not the outer editor surface.

Decision

Requesting changes because correct normalized coordinate scaling is one of #204's explicit acceptance criteria, and the current implementation misplaces overlays in common aspect-ratio cases.

@realproject7
Copy link
Copy Markdown
Owner Author

@re2 verdict: APPROVE

Reviewed:

Overlay model (overlays.ts):

  • Clean Overlay interface with normalized coordinates (0-1 range). speaker optional, only set for speech type. Type-specific defaults for width/height (sfx smaller).
  • toPixel/toNorm utilities with zero-container guard. Simple and correct.
  • createOverlay factory with unique IDs (Date.now() + counter). Good.

Cut interface extension (cuts.ts):

  • overlays: Overlay[] added with [] default — backwards compatible.
  • Validation: overlays optional, but if present must be an array. Correct for foundation — allows old cuts without overlays to pass validation.

LetteringEditor component:

  • ResizeObserver for responsive overlay positioning — overlays only render after first measurement (containerSize.width > 0), avoiding 0-position flash. Good.
  • Clean image as background via assetUrl, object-contain for aspect ratio preservation.
  • Overlays absolutely positioned with toPixel on normalized coords — will scale correctly with resize.
  • Click-to-select with stopPropagation, click background to deselect — clean interaction model.
  • Inspector panel: type label, speaker (speech only), text, x/y/w/h coordinates to 3 decimal places.
  • const [overlays] = useState(...) — read-only for now, matching "minimal editable overlay model" scope. Save persists initial state.

CutListPanel integration:

  • "Open editor" gated on cut.cleanImagePath — correct, can't letter without an image.
  • Editor replaces cut list when active; Close returns to list. Save writes overlays back via PUT endpoint + reload.

Tests (17):

  • Coordinate normalization: 10 tests (toPixel, toNorm, createOverlay with types/defaults/uniqueness)
  • Editor: 6 tests (background image, no-image fallback, overlay rendering, selection, deselection, close)
  • CutListPanel: 1 test (editor button)
  • ResizeObserver properly mocked for jsdom environment.

No issues found.

Position overlays relative to the actual rendered image area (after
object-contain scaling) instead of the full container. Computes
image bounds from naturalWidth/naturalHeight and container size,
accounting for letterboxing. Tests updated to simulate image load.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@realproject7
Copy link
Copy Markdown
Owner Author

@re2 re-review verdict: APPROVE (maintained)

Important fix — overlays now position relative to rendered image bounds, not the full container:

  • updateImageBounds: Computes actual image rect using naturalWidth/naturalHeight + container dimensions. Correctly calculates object-contain scaling (Math.min(cw/iw, ch/ih)) and centering offsets.
  • Overlay positioning: imageBounds.x + toPixel(overlay.x, imageBounds.width) — overlays now correctly align to the image even when letterboxed.
  • onLoad trigger: updateImageBounds called on image load + ResizeObserver, ensuring bounds are recalculated on both initial load and container resize.
  • Tests updated: simulateImageLoad helper sets naturalWidth/naturalHeight and fires load event. ResizeObserver mock sets clientWidth/clientHeight on target element. Image bounds calculation now properly exercised.

Good improvement — without this, overlays would drift on any non-exact-fit image. Approval maintained.

Copy link
Copy Markdown
Collaborator

@project7-interns project7-interns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: REQUEST CHANGES

Summary

The image-bounds implementation now looks directionally correct, but the requested regression coverage is still missing. The updated test setup uses matching 4:3 image/container dimensions, so it would not catch the letterboxing drift that caused the original bug.

Findings

  • [medium] Add a true aspect-ratio mismatch regression for overlay positioning.
    • File: app/web/components/LetteringEditor.test.tsx:32
    • Suggestion: Use a container/image ratio mismatch, such as a 400x300 container with an 800x400 image, and assert the overlay style includes the expected centered offset from the rendered image bounds. For example, with 400x300 container and 800x400 image, the rendered image is 400x200 with top: 50; an overlay at y: 0.2 should render at top: 90, not 60. The current simulateImageLoad() uses 800x600 against a 400x300 container, which has no letterboxed padding and would pass even if overlays were still positioned against the full surface.

Decision

Requesting changes because the prior blocker was specifically an aspect-ratio mismatch bug, and the added tests do not exercise that failure mode. The production logic appears to be on the right track; this should be a focused test update.

Test overlay positioning with 800x200 image in 400x400 container,
verifying nonzero y-offset (150px letterbox) and correct overlay
dimensions. This would fail with the original container-based
positioning.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@realproject7
Copy link
Copy Markdown
Owner Author

@re2 re-review verdict: APPROVE (maintained)

Mismatched aspect ratio regression test is exactly right:

  • 800x200 image in 400x400 container → scale 0.5 → rendered 400x100 → y-offset 150px letterbox
  • Full-size overlay (0,0,1,1) asserts left:0, top:150, width:400, height:100
  • This test would fail with the original container-based positioning (would produce top:0, height:400 instead)

Good regression coverage. Approval maintained.

Copy link
Copy Markdown
Collaborator

@project7-interns project7-interns left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE

Summary

The overlay positioning fix now uses the rendered image bounds after object-contain scaling, and the added regression test covers the mismatched aspect-ratio letterboxing case that previously failed. CI is passing.

Findings

  • No blocking findings.

Decision

Approving because the #204 coordinate-scaling acceptance criterion is now covered by implementation and regression test, selection/editor behavior remains scoped, and lint-and-typecheck passes.

@realproject7 realproject7 merged commit e2e0781 into main May 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Build SVG/HTML lettering editor foundation

2 participants