Catalog: answer a group with its whole reach curve, and keep the wall off the centreline - #6
Merged
Merged
Conversation
The curve the drawing, the sweep and the tool list were all judged against came off `reading` — the face clicked last — so a group of six was answered against whichever of the six that happened to be, and the verdict changed when nothing about the question had. One tool for all of them has to get past all of their material, so the curve is now the pointwise maximum of theirs. A reach curve is a staircase of heights by offset out from the cut, read up from the bottom of the feature — which is where the tool tip sits when it is in that feature — so every curve is already in the tool's own frame and the max is exact rather than an approximation. `groupCurve` in shared/group-geometry.ts is the fold: every input knot is a knot of the answer, heights come from `heightAt` so the drawing and the sweep cannot disagree about where a rise comes, and a knot whose height the run after it repeats is dropped. `askedCurve` beside it is the scope — `asked()`'s answer, not one feature of it. A group answered one tool each still reads the feature in front of it, since every feature there gets its own tool.
A reach curve is measured out from the cut, so every radius the overlay draws is `cuttingRadius + offset` and the wall's inner face *is* the cutting radius. `(DC ?? 0) / 2` behind a `DC !== undefined` guard put that face at r = 0 for any tool stating no cutting diameter: the guard admits exactly the values `?? 0` then swallows. `geometry` is typed `Record<string, number>`, but a catalog built from a vendor that published none carries `null` through the JSON, and null is not undefined — so the hatch was drawn from the centreline outward, straight through the tool it was meant to stand clear of. A positive diameter is now required. A tool that states none gets no wall at all rather than one drawn from a radius nobody stated, and the gaps go with it since there is no flank to measure from either.
sallen2
approved these changes
Sep 11, 2026
pclauss123
added a commit
that referenced
this pull request
Sep 11, 2026
Takes the 1.0.0 migration, the group reach curve and #6's material-wall fix onto the clearance work. Three conflicts, all in the drawing seam: - catalog-drawing.tsx: keeps this branch's extraction of the measurement into shared/assembly-gaps, and takes main's reservation semantics with it — MATERIAL_ROOM rather than the retired materialRoom prop. main's DEV ClearanceProbe is kept; its `outline !== null` guard goes, because gapsFor already returns null where the package draws no outline. - tool-details.tsx: main's ratio rule for the sheet box, which its own e2e test pins — the sheet is as wide as the room or three quarters of its height, whichever is less. This branch's 22 rem floor under an 18 rem cap was the flat cap that rule replaced. - catalog-drawing.test.tsx: keeps main's new zoom sensor and this branch's comment on the test below it. #6 fixed the cutting radius in catalog-drawing.tsx, which this branch had already moved to shared/assembly-gaps, so the merge resolved it in a file nothing reads any more and the defect would have survived it. cuttingRadiusOf now returns `number | null` under main's guard, where the three clearance boxes read it as well as the drawing — a fix applied to the drawing alone would leave a box reading a gap taken from r = 0 while the picture beside it had stopped drawing one.
pclauss123
added a commit
that referenced
this pull request
Sep 11, 2026
The guard #6 put on the drawing, as a sensor on the module that now owns it. `!== undefined` was true of both shapes a silent vendor leaves — a `DC` of `null` and a `DC` of `0` — so the wall was measured from `r = 0` and drawn through the tool. Reverting cuttingRadiusOf to `(DC ?? 0) / 2` fails both cases; nothing else in the suite notices.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two defects in the clearance drawing on the catalog's part page, found
together while looking at what a group is actually drawn against.
A group was answered against one of its features
The reach curve the drawing, the holder sweep and the tool list's
clearspredicate were all judged against came offreading— the faceclicked last. A group of six was therefore answered against whichever of
the six that happened to be, and the verdict under the drawing changed
when nothing about the question had.
One tool for all of them has to get past all of their material, so the
curve is now the pointwise maximum of theirs. A reach curve is a
staircase of material heights by offset out from the cut, read up from
the bottom of the feature — which is where the tool tip sits when it is
in that feature — so every curve is already in the tool's own frame and
the maximum is exact rather than an approximation.
groupCurve(shared/group-geometry.ts) is the fold. Every inputknot is a knot of the answer, since between two of them no input moves
and so neither does the max; heights come from
heightAtrather thana re-derived staircase, so the drawing and the sweep cannot disagree
about where a rise comes; and a knot whose height the run after it
repeats is dropped.
askedCurvebeside it is the scope:asked()'s answer, not onefeature of it, so the wall under the drawing and the column beside it
are about the same thing. A group answered one tool each still reads
the feature in front of it — every feature there gets its own tool, so
there is nothing to fold.
The wall was drawn from the centreline
Every radius the overlay draws is
cuttingRadius + offset, so thewall's inner face is the cutting radius.
(DC ?? 0) / 2behind aDC !== undefinedguard put that face atr = 0for any tool statingno cutting diameter — the guard admits exactly the values
?? 0thenswallows.
geometryis typedRecord<string, number>, but a catalogbuilt from a vendor that published no cutting diameter carries
nullthrough the JSON, and
null !== undefined. The hatch was drawn from thecentreline outward, straight through the tool it was meant to stand
clear of, covering its whole
+rflank.A positive diameter is now required. A tool that states none gets no
wall at all rather than one drawn from a radius nobody stated, and the
gaps go with it since there is no flank to measure from either.
Tests
group-geometry.test.ts— the fold sampled against the true pointwisemax across a span of offsets, order-independence, knot trimming, the
single-curve identity, a feature stating no curve, and the scope rule
including the
eachexemption.catalog-drawing.test.tsx— the wall stands off the cut by the tool'sown radius; no diameter draws no wall;
nulland0draw no wall.Verified red against the previous guard.
pnpm checkgreen. Catalog e2e: 122 passed, 2 skipped.Not covered
The route wiring has no end-to-end test. Every click near the cube
fixture's centre resolves to the same feature, so a two-feature group
with differing curves cannot be built there honestly; the scope rule is
pinned as pure logic instead.