Skip to content

Approach C: headless view rendering + moi scratch lint feedback loop#29

Open
molefrog wants to merge 1 commit into
claude/scratchpad-visual-design-xkgqd6from
claude/scratchpad-visual-design-xkgqd6-c-see-lint
Open

Approach C: headless view rendering + moi scratch lint feedback loop#29
molefrog wants to merge 1 commit into
claude/scratchpad-visual-design-xkgqd6from
claude/scratchpad-visual-design-xkgqd6-c-see-lint

Conversation

@molefrog

@molefrog molefrog commented Jul 6, 2026

Copy link
Copy Markdown
Owner

The bet

Feedback dominates tooling: an agent that can always see the canvas and machine-check "looks off" converges on human-looking output no matter how it draws.

Stacked on #26 (text-metrics foundation). Sibling approaches: A (ELK auto-layout), B (relative tools).

What it does

Two feedback channels, plus the mutation needed to act on them:

  1. moi scratch view always works now. When no browser tab shows the canvas (previously a hard "No live canvas" failure), the server renders it: our own SVG emitter for the shape set, rasterized by @resvg/resvg-js with the real Shantell Sans (woff2 → TTF via wawoff2, cached under envPaths('moi').cache). Covers rects (all four fills incl. hatch), ellipses, notes, text, arrows (binding resolution → border-clipped heads, elbow + arc routing, halo'd labels), freehand strokes, images (webp→png transcode); grey dashed placeholder for anything else. --headless skips the relay for determinism; headless results are marked as approximations.
  2. moi scratch lint — geometry checks off the disk snapshot, each finding with a copy-pasteable fix:
    • text-overflow (error) — measured with the real font; fix is an exact resize from fitRectToLabel (never suggests narrowing; applying it provably clears the finding)
    • overlap (error) — skips ≥90% containment (intentional grouping); fix is a minimal concrete push
    • near-misalign (warn) — edges/centers ≤10px apart; fix is the exact aligning move
    • uneven-gaps (warn) — row/column runs with >12px gap spread; fix uses the median gap
    • Noise control: freehand strokes excluded, misalign capped at the 10 closest, never doubled with overlap. --json for machine use; always exit 0.
  3. moi scratch resize <id> --size w,h — small new op (geo/image) so overflow findings are actionable.

Skill/doc update teaches the loop: draw → lint (fix errors, judge warns) → view → adjust.

Verified end-to-end

Recreated the motivating screenshot's mess (3 overflowing labels, title crossing a box, 2px misalignment): lint reported all five with exact fixes; applying them plus one follow-up gap fix reached zero findings, and the headless render visually confirms the before/after. Relay fallback, --headless, and CLI output verified against a live server.

Tests

13 new tests (render: PNG output, webp transcode, unsupported-type safety; lint: each finding type, containment exclusion, clean-canvas zero, resize clears overflow). Full suite 332 green; lint/format clean.

Trade-offs

Iteration costs turns vs. approach A's one-shot correctness — but this is the only approach that also audits what's already on the canvas (including user drawings) and it composes with both siblings.

🤖 Generated with Claude Code

https://claude.ai/code/session_01A4d4g5CV89L1AanPrEYp7p


Generated by Claude Code

The agent no longer draws blind. Two feedback channels close the loop:

- server/scratchpad-render.ts: our own SVG emitter for the primitive shape
  set (geo rect/ellipse, note, text, arrow with bindings/elbow/arc, draw,
  image), rasterized by @resvg/resvg-js with the real canvas font (Shantell
  Sans woff2 decompressed to TTF via wawoff2, cached in moi's cache dir).
  `moi scratch view` now always works: a live tab still gives exact pixels;
  with no tab the server renders an approximation, marked `headless` in the
  result and noted on stderr. `--headless` skips the relay outright.
- server/scratchpad-lint.ts: read-only geometry checks off the disk snapshot
  — text-overflow (measured with the actual font), overlap (containment =
  grouping, skipped), near-misalign (≤10px), uneven-gaps in rows/columns —
  each with a ready-to-run fix command. Advisory: `moi scratch lint` always
  exits 0; `--json` for structured output.
- `moi scratch resize <id> --size w,h` (geo rects and images only) makes
  overflow findings actionable; lint's fix is the exact fitRectToLabel size.

Supporting changes: richer snapshot access in server/scratchpad.ts
(readScratchpadRecords, scratchShapeBounds, exported extractShapeText with
paragraph-aware newlines), the palette hex table moved to
lib/scratch-palette.ts (shared by CLI snapping and the renderer), the relay's
no-live-canvas failure exported as a constant so the view fallback catches
precisely that case, and the skill/docs teach the loop: draw → lint → view →
adjust.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A4d4g5CV89L1AanPrEYp7p
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants