Skip to content

Approach B: relative placement + align/distribute/autosize/tidy verbs#28

Open
molefrog wants to merge 1 commit into
claude/scratchpad-visual-design-xkgqd6from
claude/scratchpad-visual-design-xkgqd6-b-relative-tools
Open

Approach B: relative placement + align/distribute/autosize/tidy verbs#28
molefrog wants to merge 1 commit into
claude/scratchpad-visual-design-xkgqd6from
claude/scratchpad-visual-design-xkgqd6-b-relative-tools

Conversation

@molefrog

@molefrog molefrog commented Jul 6, 2026

Copy link
Copy Markdown
Owner

The bet

The agent's topology instincts are fine — it needs a designer's hand tools instead of a calculator. Keeps the imperative model (so it works for annotating the user's own sketches too), removes the arithmetic.

Stacked on #26 (text-metrics foundation). Sibling approaches: A (ELK auto-layout), C (see + lint).

What it does

moi scratch add rect --at 0,0 --text "Browser (internet)" --id browser
moi scratch add rect --right-of browser --gap 140 --text "reverse proxy — terminates TLS" --id proxy
moi scratch add rect --right-of proxy --gap 140 --text "service on localhost:3000" --id service
moi scratch align browser proxy service --edge center-y
moi scratch distribute browser proxy service --axis x --gap 140
moi scratch add text --above browser --gap 64 --align start --font-size big --text "Title"
moi scratch tidy

No coordinate ever computed by the agent; rects auto-size to their labels (no --size needed), so overflow can't happen on create.

How

  • Relative placement on add rect|note|text: --below/--above/--left-of/--right-of <id>, --gap (default 48), --align start|center|end (cross-axis). Resolved server-side in the executor against the live store (place field on add ops; x/y now optional).
  • Auto-sizing: add rect without --size fits the label via fitRectToLabel (260px wrap target); unlabeled default 160×96.
  • server/scratchpad-arrange.tsshapeBounds without a DOM (geo/image props, note 200×200+growY, text estimated with font metrics), plus the four verbs:
    • align <ids…> --edge left|right|top|bottom|center-x|center-y [--to <id>]
    • distribute <ids…> --axis x|y [--gap N] (exact-gap repack, or endpoint-fixed equalize; spatial order, like design tools)
    • autosize <ids…> — re-fit labeled rects
    • tidy [--grid N] — grid snap + deterministic near-alignment clustering (10px tolerance, grid-rounded cluster means, center pass respects the edge pass)
  • Arrows are never arranged directly — bound arrows follow their shapes, so all verbs skip them and reject them as anchors.
  • Docs + agent skill teach the workflow: anchor once → place relatively → omit --size → finish with align/distribute/tidy.

Tests

19 new tests (placement math on all four sides, auto-size fit, align/distribute/autosize/tidy behavior, binding survival, error cases), every block asserting browser-loadability. Full suite 338 green; lint/format clean.

Trade-offs

Multi-step: the scenario takes ~12 commands vs. one diagram call on approach A — but it's the only approach that helps mid-edit and around user-drawn shapes. Composable with A and C.

🤖 Generated with Claude Code

https://claude.ai/code/session_01A4d4g5CV89L1AanPrEYp7p


Generated by Claude Code

…e verbs

The agent laid out diagrams by guessing absolute coordinates — overlaps,
overflowing labels, crooked spacing. This keeps the imperative model but
removes the arithmetic:

- `add text|rect|note` gain relative placement (--below/--above/--left-of/
  --right-of + --gap + --align) as an alternative to --at. The op carries a
  `place` intent; the executor resolves it against the anchor's real bounds
  (server/scratchpad-arrange.ts: shapeBounds, with text estimated via the
  canvas-font metrics). Arrows can't anchor.
- `add rect --size` is now optional: omitted with --text, the rect is sized
  by fitRectToLabel so the label can never overflow; without text it falls
  back to a 160×96 node box.
- New arrangement verbs, all headless-store ops: `align` (edge/center to an
  anchor, one axis only), `distribute` (exact --gap repack, or equalize with
  endpoints fixed), `autosize` (re-fit labeled rects in place), and `tidy`
  (grid snap + pull near-aligned left edges / centers / top edges onto one
  shared grid line). Arrows are always skipped — bound arrows follow.
- Tests in server/test/scratchpad-arrange.test.ts (placement math, label
  fit, align/distribute/autosize/tidy, browser-loadable snapshots), docs
  and the workspace skill teach the workflow: anchor once, place
  relatively, never compute coordinates, finish with align/distribute/tidy.

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