Skip to content

Let users change a placed component's silhouette - #24

Open
augustomesquita wants to merge 2 commits into
webdevcody:mainfrom
augustomesquita:feat/silhouette-toggle
Open

Let users change a placed component's silhouette#24
augustomesquita wants to merge 2 commits into
webdevcody:mainfrom
augustomesquita:feat/silhouette-toggle

Conversation

@augustomesquita

@augustomesquita augustomesquita commented Jul 31, 2026

Copy link
Copy Markdown

The problem

The pedestal a component stands on is decided when it's created and can never be changed. An icon dropped from the palette is round forever. The only way to get a square one is to delete it and rebuild the node as a rectangle, losing the artwork, the label, the floor assignment and every edge already connected to it.

Every other property of a placed component (fill, label, text size, floor, z-order) can be edited after the fact. The footprint was the one that couldn't.

The proposal

Shape gains an optional silhouette override. When it's absent the footprint is derived from the kind exactly as today, so existing boards and share links are unaffected and there's no migration.

Users reach it from a Shape field in the style panel and two entries in the right-click menu. It covers every kind that raises a pedestal (icon, circle, rect, image) and skips text and code.

For review

The footprint rule was duplicated across six call sites: edge anchoring, culling, selection outline, click targeting, and both render paths. A single isRoundFootprint predicate now backs all of them, which is why the diff touches more files than the feature might suggest.

I split it into two commits so the refactor can be judged on its own. The first is behaviour-preserving, the second is the feature.

A few decisions worth knowing before reading the diff:

  • Reshaping doesn't resize. A non-square box turned round draws a disc across its shorter side and keeps its box, so switching back restores it exactly.
  • I added a field instead of flipping kind, because image has no round counterpart and an icon flipped to rect would lose its artwork on a ?g= round trip.
  • GeneratedGraph and the OpenAI schema are deliberately untouched. That schema is strict with every property required, so adding a field would change generation behaviour. Happy to follow up if you want it exposed.

Testing

New suite in test/silhouette.test.ts. npm test passes 40 tests across 9 files and npm run build is clean.

Two assertions in test/renderPerf.test.ts fail on my machine, but they fail identically on an unmodified main, so they look environment-bound rather than caused by this change.

Also verified in the browser: inserting an icon, switching it to square, and confirming the pedestal renders as a slab, the outline becomes a box, both edges re-anchor, and the change survives autosave.

Whether a shape stands on a disc or a slab was decided by repeating
`kind === "circle" || kind === "icon"` at six independent call sites:
edge anchoring, viewport culling, the selection outline, click targeting,
and both the per-view and batched render paths. pointInShape held a
seventh copy that had already drifted, testing only "circle".

Introduce isRoundFootprint as the single definition and route every site
through it. The predicate reads an optional silhouette override and
otherwise falls back to the previous kind test, so behaviour is unchanged
until something writes that field.

pedestalBatch is the site that matters most here: it dispatched on kind
directly, so any future disagreement between kind and footprint would
have drawn a slab in the batched path and a disc in the per-view one.
The footprint of a component was fixed at creation: an icon dropped from
the palette was always round, and the only way to get a square one was to
delete it and rebuild it as a rectangle, losing the artwork, label, floor
and every connected edge.

Add an optional silhouette override on Shape, written by a new
setShapesSilhouette action, and surface it as a Shape control in the style
panel and two entries in the right-click menu. It applies to every kind
that raises a pedestal — icon, circle, rect and image — and skips text and
code, which have no footprint to swap.

Dimensions are deliberately untouched, so a non-square box turned round
draws a disc across its shorter side and still restores exactly when
switched back. Undo, autosave, clipboard and share links need no work:
the action bumps the revision the history and autosave already watch, and
the field rides along in the shapes the clipboard and encoder copy
wholesale.

Boards and share links predating the field carry no override and fall
back to the kind default, so they render exactly as before.
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.

1 participant