Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
9cf0a07
Add the sketch workspace design spec.
jaak0b Jul 24, 2026
4f62ce2
Add the sketch workspace implementation plan.
jaak0b Jul 24, 2026
55f899a
Add the PlaneGCS solver dependency with a node smoke test and LGPL at…
jaak0b Jul 24, 2026
81ed711
Add the sketch datatype with validation and serialization.
jaak0b Jul 24, 2026
7a65589
Add the PlaneGCS solver adapter with dof, conflict and drag support.
jaak0b Jul 24, 2026
2a54891
Add sketch profile extraction sharing the trace outline tolerance.
jaak0b Jul 24, 2026
f43aa10
Add the sketch worker and client owning the PlaneGCS wasm.
jaak0b Jul 24, 2026
301b8cb
Add the tool outline source discriminator and plan version 11.
jaak0b Jul 24, 2026
cc2b13a
Add the sketch editor store.
jaak0b Jul 24, 2026
9aede69
Use the shared assertNever helper in the sketch dimension switch.
jaak0b Jul 24, 2026
79b329e
Add the sketch canvas and workspace behind an upload-or-draw toggle.
jaak0b Jul 24, 2026
ee695a1
Add dimensioning, constraints, solver diagnostics and the photo under…
jaak0b Jul 24, 2026
3d7ff51
Wire the sketch finish flow into tool placement and sketch re-editing.
jaak0b Jul 24, 2026
56bab7b
Fix sketch chain joining, stale solves, and sketch tab review findings.
jaak0b Jul 24, 2026
c1191f4
Group the sketch toolbar into icon segments with context-sensitive co…
jaak0b Jul 25, 2026
dfc26dc
Add sketch entity deletion, point snapping, anchor cue and conflict r…
jaak0b Jul 25, 2026
d3fdd9c
Compact the sketch toolbar to two rows, widen line hit targets, and a…
jaak0b Jul 25, 2026
95fde5d
Select sketch points with a click and drag only after a movement thre…
jaak0b Jul 25, 2026
e6958af
Forbid interim fixes in the conventions.
jaak0b Jul 25, 2026
f9f8e6d
Add the multi-source tool bin design spec.
jaak0b Jul 25, 2026
612f1c2
Add the multi-source tool bin implementation plan.
jaak0b Jul 25, 2026
bdfea43
Make ToolSource a three-variant union owning its re-edit data.
jaak0b Jul 25, 2026
97ef686
Silence planegcs solver diagnostic logging via its debug_mode flag.
jaak0b Jul 25, 2026
974aae8
Capture sketch point drags on the SVG root so off-circle pointerdowns…
jaak0b Jul 25, 2026
c5b8b97
Reshape plan v11 around trace sessions and migrate v10 bins.
jaak0b Jul 25, 2026
3c214c9
Pin the photo sweep to trace session references.
jaak0b Jul 25, 2026
ce01cc3
Add referencedSessionIds helper for trace session reference counting.
jaak0b Jul 25, 2026
c962bed
Turn the toolTrace store photo state into atomic active-session state.
jaak0b Jul 25, 2026
b04d7b1
Clone stored trace sessions into the store so re-confirms cannot muta…
jaak0b Jul 25, 2026
b24dbc6
Reference-count trace sessions on save and drop orphaned photos.
jaak0b Jul 25, 2026
e33e8a5
Replace the input stage with a source list and rewire workspace entry.
jaak0b Jul 25, 2026
f685f82
Add undo and redo to the sketch editor.
jaak0b Jul 25, 2026
8209d50
Snapshot chain state in sketch undo and make drag merges one history …
jaak0b Jul 25, 2026
13efe99
Add CAD constraint glyphs to the sketch canvas and fix pointercancel …
jaak0b Jul 25, 2026
ea0cf9d
Add the sketch region extraction design spec.
jaak0b Jul 25, 2026
958dc37
Add sketch region extraction (arrangement DCEL) engine module, sharin…
jaak0b Jul 25, 2026
b37856e
Handle collinear overlaps, arc coverage and hole cycles in region ext…
jaak0b Jul 25, 2026
82dd1de
Wire sketch region extraction into the canvas, store and finish flow.
jaak0b Jul 25, 2026
cf4ee68
Recompute regions once per drag instead of per pointermove solve.
jaak0b Jul 25, 2026
8ce9d14
Record the CAD expert review backlog for the sketch workspace.
jaak0b Jul 25, 2026
f1d4db6
Add measured-value dimensions, auto H/V inference, and rectangle/slot…
jaak0b Jul 25, 2026
d2574d2
Highlight hovered conflicts, add the H/V hint glyph, and show the liv…
jaak0b Jul 25, 2026
4a416f4
Wire the rectangle, slot, and typed-length tools into the sketch work…
jaak0b Jul 25, 2026
df50aec
Add a radius or diameter choice to circle dimensioning and note revie…
jaak0b Jul 25, 2026
a068e59
Fix the six sketch workspace review findings: dropped photo sessions,…
jaak0b Jul 25, 2026
c27fd0c
Add source deletion with cascade confirm and sketch tool ghost previews
jaak0b Jul 25, 2026
c29303b
Add sketch canvas space/middle pan, wider zoom-out with a Fit button,…
jaak0b Jul 25, 2026
b337591
Round dimension display and reject garbage input in sketch dimension …
jaak0b Jul 25, 2026
96b621f
Fix the tangent arc preview center sign and cover it with tests.
jaak0b Jul 25, 2026
0a55884
Rebuild the sketch Dimension tool to a Fusion-style click, place, typ…
jaak0b Jul 25, 2026
28cb76b
Fix dimension label dblclick and second-line angle dimension routing.
jaak0b Jul 25, 2026
7d9dcdd
Add point-line distance dimensions and a driven-dimension foundation.
jaak0b Jul 25, 2026
7e72397
Wire driven dimensions into the solve loop with rendering and an over…
jaak0b Jul 25, 2026
4cafecd
Add H/V/aligned point-distance flavors and angle-quadrant picking to …
jaak0b Jul 25, 2026
d2f2f49
Pin the picked angle sector through the solver and route point clicks…
jaak0b Jul 25, 2026
b0b3dde
Fix angle dimension default sector and arc witness extensions at a sh…
jaak0b Jul 25, 2026
1b4d99c
Add multi-part traced tools with per-part hole edits and warning-only…
jaak0b Jul 25, 2026
2076241
Add multi-select regions and geometry-preserving re-finish to the ske…
jaak0b Jul 25, 2026
d32e792
Fix the pocket-carve phantom wall and re-finish placement/hole-remap …
jaak0b Jul 25, 2026
9d906ca
Make region shading click-transparent outside the select tool so dime…
jaak0b Jul 25, 2026
4ef558b
Make sketch canvas text unselectable during label drags.
jaak0b Jul 25, 2026
45e4408
Stop clamping sketch canvas pan to a fake fixed design window.
jaak0b Jul 26, 2026
c2ffbfc
Rebuild the sketch reference photo underlay as a Fusion-style Canvas …
jaak0b Jul 26, 2026
3aad28b
Fix calibrate click routing and manipulator edge cases in the referen…
jaak0b Jul 26, 2026
2fbacea
Round the reference photo menu fields to sensible precision.
jaak0b Jul 26, 2026
2558e28
Move the reference photo with the left mouse button only.
jaak0b Jul 26, 2026
89a9751
Cascade point deletion to its dependent lines/arcs/circles so remaini…
jaak0b Jul 26, 2026
fcec4f5
Support multiple reference photo canvases in the sketch workspace.
jaak0b Jul 26, 2026
3f74639
Show units on every sketch numeric input.
jaak0b Jul 26, 2026
c638245
Keep the reference photo menu a fixed width with a readable calibrate…
jaak0b Jul 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ Numbered for unambiguous reference; do not cite rule numbers in shipped source o
want the same. A minimal local guard that duplicates existing logic is a defect, not a small change.
Any non-trivial engine or cross-cutting change gets a short written design first (its canonical
home, what it extends, what it must not duplicate) for owner approval before implementation.
Interim ("quick fix now, proper fix later") solutions are forbidden in all cases: deferred fixes
are forgotten and the interim state becomes permanent, so the correct structure is built
immediately, even when it costs a schema change or a larger diff.

11. **Subagent discipline.** Give every subagent a correct, specific title; never run more than 1 Fable
agent at a time (hard budget limit). Sonnet is fine for parallel design/research work.
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ The stack is Vue 3, TypeScript, Vite, Vuetify, and Pinia. All geometry generatio
Gridfinity bin geometry constants are ported from the MIT-licensed
[kennetek/gridfinity-rebuilt-openscad](https://github.com/kennetek/gridfinity-rebuilt-openscad).

The 2D sketch workspace is powered by the FreeCAD PlaneGCS constraint solver, compiled to
WebAssembly by the LGPL-2.1-licensed
[Salusoft89/planegcs](https://github.com/Salusoft89/planegcs). The `planegcs.wasm` binary
ships as a separate, replaceable asset, as the LGPL requires.

## Contributing

Issues and pull requests are welcome at [github.com/jaak0b/StoreForge](https://github.com/jaak0b/StoreForge).
Expand Down
3,662 changes: 3,662 additions & 0 deletions docs/superpowers/plans/2026-07-24-sketch-workspace.md

Large diffs are not rendered by default.

1,941 changes: 1,941 additions & 0 deletions docs/superpowers/plans/2026-07-25-multi-source-tool-bin.md

Large diffs are not rendered by default.

96 changes: 96 additions & 0 deletions docs/superpowers/specs/2026-07-24-sketch-workspace-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Sketch workspace for tool outlines

Date: 2026-07-24. Status: approved by owner in conversation; this document is the written record.

## Problem

Tool bins currently get their pocket outlines from a photo trace or from an uploaded STL. Some
objects cannot be photographed usefully (tall objects such as bottles cast shadows and distort),
and many users cannot produce an STL. They need a way to construct a precise 2D outline directly
in the app.

## Decision summary

A parametric 2D sketch workspace, in the spirit of the Fusion 360 sketcher, built on the FreeCAD
PlaneGCS constraint solver compiled to WASM (npm package `@salusoft89/planegcs`, LGPL-2.1). A
finished sketch produces the same millimeter outline a photo trace produces and enters the
existing tool pipeline unchanged (placement, outline offset, draft angle, pocket carve, cavity
edits, plate, export). No new bin type. The tool datatype gains an origin discriminator
(photo trace vs sketch), mirroring the established `Bin.origin` pattern, and sketched tools store
their editable `Sketch` so they can be reopened and changed later.

### Solver choice rationale

Researched options: JSketcher (copyright-assignment license, unusable), SolveSpace/libslvs
(GPLv3, dead JS port), hobby solvers (unmaintained), hand-rolled least-squares solver (viable
but we would own convergence bugs and diagnostics). PlaneGCS is the only maintained,
industrial-grade solver with a usable license. LGPL-2.1 obligations are met by shipping the
`.wasm` as a separate replaceable asset (the app already loads all WASM as separate worker
assets) and adding an attribution notice.

## Architecture

### New engine module `web/src/engine/sketch/` (framework-agnostic, convention 3)

- **`model.ts`**: the `Sketch` datatype. Entities: point, line, arc, circle, all in mm, each
with a construction flag. Constraints: coincident, horizontal, vertical, parallel,
perpendicular, tangent, symmetric, and dimensions (length, distance, radius/diameter, angle).
Entities and constraints are discriminated unions handled with exhaustive switches and
`assertNever` (convention 13). The `Sketch` carries its own small schema version so the sketch
format can evolve without a full plan version bump each time.
- **`solve.ts`**: adapter mapping a `Sketch` onto PlaneGCS primitives and constraints, running
the solver, and writing solved coordinates back. Reports solver status to the UI: fully
constrained, under-constrained (with degrees of freedom), or conflicting (with the offending
constraints). Dragging a point is expressed as the solver's standard driven-point workflow.
- **`profile.ts`**: extracts the closed outer loop from the solved entities (chained line/arc
endpoints; a non-construction circle stands alone), flattens arcs at the same tolerance the
trace pipeline already uses, and returns the same mm outline type traced tools carry.
Failures are user-worded messages (convention 2): open chain, self-intersecting outline,
construction-only sketch, multiple disjoint loops.

### Solver placement

PlaneGCS runs in its own small worker (`sketch.worker.ts` beside the existing manifold and
vision workers) so sketch editing never blocks on a running carve and the WASM stays out of the
main bundle. A `sketchClient.ts` mirrors the existing worker client pattern.

### Data model integration

- The traced-tool datatype gains an origin discriminator: photo trace vs sketch. A sketched
tool embeds its `Sketch`; its outline feeds every downstream stage untouched.
- Plan file: new plan version with validators, following the existing migration pattern.
- Accepted cost: plans that embed sketches are larger than outline-only plans. Worth it,
because without the stored sketch a saved tool would no longer be editable.

## Editor UI

A sketch workspace inside the tool trace flow. The input step becomes a toggle: upload a photo
or draw the shape. Drawing opens the sketch canvas.

- **Canvas**: SVG, mm coordinate system with grid, pan and zoom following the existing trace
canvas conventions. The canvas is preview only; all geometry lives in the engine.
- **Tools**: select and drag, line chain, arc (three-point and tangent continuation), circle
(center plus diameter, the one-click bottle case), construction toggle, mirror line for the
symmetric constraint.
- **Dimensions**: click one or two entities, type a value (length, distance, radius, diameter,
angle). Dimension labels render on the canvas and are click-to-edit. The solver runs after
every edit. Under-constrained geometry is drawn in a distinct color from fully constrained
geometry; conflicts surface the solver's diagnostics as labeled rows (convention 8).
- **Photo underlay**: optional image upload with an opacity slider, calibrated by drawing one
reference line over the photo and typing its real length. Display only; never enters
geometry.
- **Finish**: "Use this shape" validates through `profile.ts` and drops the user into the
normal tool placement and depth step. Pocket depth is the existing per-placement value.

## V1 scope

Lines and arcs, dimensions and core constraints, full-circle primitive, reference photo
underlay, symmetry and construction lines. Splines and ellipses are out of scope.

## Testing

Engine-level Vitest coverage: model validation and (de)serialization round-trips, solve.ts
against known sketches (dimensioned rectangle, tangent arc chain, symmetric profile,
over-constrained conflict), profile extraction including every user-worded failure, and the
plan version migration. Worker smoke test loading the PlaneGCS WASM in node, following the
existing vision smoke test pattern.
77 changes: 77 additions & 0 deletions docs/superpowers/specs/2026-07-25-multi-source-tool-bin-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Multi-source tool bins: trace sessions, per-tool source data, source list UI

Date: 2026-07-25. Status: approved by owner in conversation (data model and scope); UI structure
per expert consultation. This document is the written record.

## Problem

The sketch workspace made a tool's origin per-tool (`ToolSource`), but photo trace data stayed
bin-level from the single-photo era: `paper`, `traceSourceId`, and the stored photo blob are saved
with the bin regardless of whether any tool still uses them, while the photo-specific re-edit data
(`clicks`, `brushStrokes`) sits on every `TracedTool` including sketched ones. Consequence the
owner hit: a sketch-only bin silently saves and restores a photo it never used. The owner also
wants the capability this structure blocks: one bin combining tools traced from several
photographed sheets with any number of sketched and primitive tools.

## Decision summary

Reshape the data model around **trace sessions** and make each `ToolSource` variant fully own its
re-edit data. Interim fixes are forbidden (convention 10); this is the final structure. Plan file
version 11 is unshipped, so it is reshaped in place; no intermediate migration exists.

### Data model

- A tool bin carries `traceSessions: TraceSession[]` where
`TraceSession = { id: string; traceSourceId: string; paper: { corners; kind } }`. Zero or more.
- `ToolSource` becomes a three-variant union, each variant self-contained:
- `{ kind: 'photo'; sessionId: string; clicks: SamPoint[]; brushStrokes?: BrushStroke[] }`
- `{ kind: 'sketch'; sketch: Sketch }`
- `{ kind: 'primitive' }` (basic shapes, previously mislabeled as photo)
- `clicks` and `brushStrokes` leave `TracedTool`; they exist only inside the photo variant.
- Persistence rule: a session (and its photo blob in the photo store) is saved iff at least one
tool references its `sessionId`; orphaned sessions and their stored photos are dropped on save.
A sketch-only bin therefore carries zero photo data by construction.
- Plan file: v11 reshaped. v10 plans migrate: bin-level `paper`/`traceSourceId` become one
session; every tool with clicks becomes a photo tool referencing it; primitive-shaped tools
(empty clicks) become `primitive`; validation messages follow the planFile.ts convention.

### Store and worker

- The `toolTrace` store's single-photo state (`photoUrl`, `corners`, `calibration`,
`embedReady`, ...) becomes the **active session** state. Session activation is one atomic store
action that clears all of it first, then loads the session's photo, applies its saved corners,
rectifies and embeds. `embedReady` is keyed by session id so a re-trace can never run against a
stale sheet's calibration (expert-flagged failure mode; wrong millimeters otherwise).

### UI (expert-consulted structure)

- The input stage becomes a **source list**: one card per existing trace session (sheet) and per
sketched tool, plus two actions, "Add a photo sheet" and "Draw a shape". Clicking a sheet card
activates that session and opens the trace workspace; a sketch card opens the sketch workspace.
- First-run shortcut: with no sessions and no sketches, the stage shows only the two large
actions (today's screen without the toggle); the list appears once a source exists. The common
single-sheet or single-sketch flow stays as short as today.
- Breadcrumb: first chip renamed to "Sources"; second stays "Trace and lay out". The trace and
sketch workspaces are modal work on one source entered from a card, with a "Back to sources"
action, not a third chip.
- Tool rail: re-trace resolves the tool's `sessionId`, atomically activates that session, then
opens trace mode. Edit-sketch opens the sketch workspace directly. The `traceInput` toggle and
`sketchCancelStage` mechanism in TraceTab.vue are removed; workspaces return to whichever stage
opened them.
- Mobile: source cards stack full width in one column at 375 px; workspaces stay full-bleed; no
persistent rail.
- UI text plain technical prose; no em-dash characters; exhaustive `ToolSource` switches with
`assertNever` everywhere, including the new `primitive` member.

## Out of scope

Cross-bin session sharing, sheet thumbnails beyond what the photo store already yields cheaply,
and any change to the pocket/export pipeline (tools still resolve to outlines exactly as today).

## Testing

Engine: session reference-counting on save (orphan dropped, referenced kept), v10 to v11
migration (tools gain photo sources referencing the migrated session; primitives detected), plan
round-trip with multi-session bins, ToolSource validation for all three variants. Store: atomic
session activation clears prior calibration and re-keys embedReady; re-trace against the correct
session. UI behavior beyond build/typecheck is covered by the owner's browser check.
49 changes: 49 additions & 0 deletions docs/superpowers/specs/2026-07-25-sketch-cad-review-backlog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Sketch workspace: senior CAD designer review, backlog

Date: 2026-07-25. Source: consulted CAD-application expert review of the shipped sketch
workspace. Items marked BUILT are being implemented the same night under the owner's standing
directive; everything else awaits owner triage.

## Defects in shipped features (BUILT tonight)

- Dimension entry field: blur cancels the draft even when the text parses; blur should commit
a parseable value, Escape stays cancel. New dimensions should default to the measured current
value instead of a placeholder (Enter without editing = lock at current size), which also
stops the solver yanking geometry to a placeholder value mid-entry.
- Mirror tool hint promises "then the two points to keep symmetric" but the tool only places
the line; hint corrected to the real workflow.
- Escape during a multi-click tool (three-point arc, mirror) should clear the pending clicks,
not only end chains.
- Conflict rows name raw constraint ids; hovering a conflict row should highlight the offending
constraint's glyph/entities on canvas.

## Quick wins (BUILT tonight, expert's build-next top 3)

- Rectangle tool: two clicks, auto horizontal/vertical constraints on the four lines.
- Slot tool: two clicks plus a width, the canonical elongated tool pocket shape.
- Dimension ergonomics: measured-value defaults, commit-on-blur, and type-length-while-drawing
for line and circle (numeric input while the tool is active applies to the segment being
drawn).
- Auto horizontal/vertical inference: lines drawn within about 2 degrees of an axis get the
constraint automatically, with a pre-commit visual hint glyph so the user sees it coming
(and can suppress with a modifier key).
- Live length/angle readout beside the rubber-band cursor while drawing.

## Awaiting owner triage

- Novice-first presentation: replace "Degrees of freedom: N" phrasing with plain language
("Shape fully defined" / "N measurements still free"), demote the constraint vocabulary
visually so the trace-then-dimension path leads.
- Underlay positioning: drag and rotate the reference photo, not only scale; draw the
calibration line's measured span before commit; calibration click flow hardening.
- Entity dragging: drag whole lines/circles, not only points; marquee selection with Shift
semantics.
- Sketch fillet (corner pick, radius). Note: the pocket pipeline already offsets outlines;
clarify overlap with the outline-offset stage before building (convention 10).
- Template shapes: parametric starter sketches (hex key L, wrench silhouette, cylinder+flat).
- Trim tool: low priority, region picking already yields trim's outcome.
- Offset curves in-sketch: skip, pipeline offsets already.
- DXF import: real hobbyist demand (Inkscape exports); an importer module, separate decision.
- Rectangle and slot corners do not snap onto existing sketch points (unlike line/circle/arc);
a compound profile built from these tools relies on region extraction to join adjacent
shapes rather than shared corner points.
67 changes: 67 additions & 0 deletions docs/superpowers/specs/2026-07-25-sketch-regions-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Sketch region extraction: CAD-style enclosed-area picking

Date: 2026-07-25. Status: owner approved the feature and the shaded-region UI in conversation;
algorithm choices per expert consultation. Written record.

## Problem

Profile extraction only walks entities chained end to end through shared points, so overlapping
geometry yields nothing: a line crossing a circle cannot produce the flat-sided-bottle outline.
Real CAD sketchers compute curve intersections and let the user pick any enclosed region.

## Decision summary

New engine module `web/src/engine/sketch/regions.ts` implementing an arrangement of the sketch's
non-construction curves and enumeration of its faces; the UI shades every bounded face with a
light translucent fill (CAD blue), the user picks one (auto-pick when exactly one), and the
picked face becomes the tool outline with holes from its inner cycles. `profile.ts` becomes a
thin caller; its flattening helpers move to the shared location rather than being duplicated
(convention 10).

## Algorithms (established, named; convention 12)

1. **Intersections:** analytic closed forms per pair: line/line, line/circle-or-arc (quadratic),
circle/circle (radical line). O(n^2) pairwise is sufficient at sketch scale (tens of curves);
Bentley-Ottmann deliberately not needed, stated in code.
2. **Vertex welding:** epsilon vertex clustering via union-find (the tolerance model used by
CGAL Arrangement_2 style snap approaches). Epsilon is one shared constant with a reasoned
derivation documented in code (1e-6 mm: far below any manufacturable feature, far above
accumulated double-precision error at sketch scale). Not a tuned fudge.
3. **Splitting and flattening:** curves split at exact analytic intersection parameters, then
each sub-curve flattened at the shared OUTLINE_TOLERANCE_MM; the arrangement is built on
polyline edges that remember their source entity id for UI hit-testing. Exact-arc traversal
is rejected: the outline is flattened at 0.2 mm regardless, so exact split points are kept
and nothing further is gained.
4. **Face extraction:** doubly connected edge list with face traversal by most-counterclockwise
outgoing edge, including the full inner-cycle grouping step (cycles with negative signed
area grouped into containing faces via the leftmost-vertex ray-crossing containment test),
per de Berg, Cheong, van Kreveld, Overmars, Computational Geometry, chapter 2. Inner cycles
of the picked face become outline holes directly.
5. **Tangency defense:** a near-zero discriminant is treated as exactly one tangent point
(threshold expressed in the shared epsilon); post-split edges shorter than the epsilon are
dropped, so tangent contact cannot produce zero-length edges or broken turn decisions.
Tangent cases are tested explicitly.

## UI

- The canvas shades every bounded face translucently whenever the solver state is not
conflicting; hovering a face raises its opacity; clicking selects it (selected face visibly
distinct). With exactly one bounded face it is preselected.
- "Use this shape" consumes the selected face (outer cycle plus holes); with no face selected
and several available, the finish action asks the user to pick a region first (user-worded
message, no exception).
- Region shading lives in its own SVG layer under the geometry so entity/point/glyph hit
targets keep priority. Construction entities never contribute edges.

## Out of scope

Splines and ellipses (V1 scope holds); multi-region union pockets (one region per tool for
now, matching one outline per tool in the plan schema).

## Testing

Engine tests per the expert's list: line through circle (two faces, both extractable), two
overlapping circles (three faces), circle tangent to line and circle tangent to circle
(tangency defense), island in region (hole via inner cycle), construction-only geometry
(no faces, user-worded error), plus reuse of every existing profile.ts failure wording where
it still applies. Store/UI: face pick state, auto-pick single face, finish-with-no-pick message.
Loading
Loading