Skip to content

Bring the free canvas fragments onto one shape - #96

Merged
ctgnz merged 5 commits into
masterfrom
feat/78-free-canvas-shape
Sep 24, 2026
Merged

ctgnz merged 5 commits into
masterfrom
feat/78-free-canvas-shape

Conversation

@ctgnz

@ctgnz ctgnz commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Closes #78.

A free canvas icon holds a <g>#main, a <g>#template, zero or more <g>#example groups, and whatever <defs> the main group needs to render. 545 of 577 already were that. This brings the stragglers into line so a consumer asking "which element is the content" has one answer rather than four.

Scoped down from the issue's original three options after discussion: the scaffolding stays in the fragment for now, because FREE_CANVAS icons will need separate handling anyway.

Two commits

1 — Normalise the free canvas fragments edited in Inkscape. The content changes in these four are CTG's: each Cyberspace fragment now keeps only the drawn control measure in #main, with the illustrative endpoints and net boxes moved into a hidden example group and the anchor points into the template. Inkscape rewrites a file wholesale on save, so all four needed the normaliser afterwards — 0 refused, which is the guarantee wanted: the drawn content is byte-for-byte what CTG left, and only the editor's leavings came out.

One follow-on belongs with that rather than the mechanical pass: 60200100's example group carried id="g15", which the normaliser correctly reads as a generated id and drops, leaving it anonymous. It's now id="example".

2 — Bring the free canvas fragments onto one shape. Nine fragments:

problem files
id on a bare drawing element, no group 25218800, 25281901 (polygon#main), 25200600, 25200700 (loose rect)
scaffolding group named template2 when it is an example 25200600
<pattern> loose at the root 25240301/2/3, 25172000, 25270400
<pattern> inside #main itself 25240301, 25270400

Patterns move into a <defs>. Ordering and ids otherwise left exactly as found, with one exception in a third commit: 25200600's hidden #474747 rectangle is not part of the construction guide but a backdrop for the white border of the symbol in #main, so its group is renamed template2 → example. The corpus agrees — three fragments carry that backdrop and the other two, 25200400 and 25200700, both file it under an example group.

Nothing changes what is drawn

A <g> carrying only an id paints exactly as its child did, and a <pattern> renders only where something references it — and nothing does. The whole tree holds one live url(#…) reference, 25132000's marker, untouched. The issue's note that the pattern files hold the only internal reference is no longer accurate.

SvgFingerprint can't guard this: it records structure, so inserting a group or moving an element changes it by design. Verified two independent ways instead:

  • Every drawing primitive compared before and after with its ancestors' attributes folded in, so an added group is invisible to the comparison, and each pattern subtree compared on its own. All nine identical.
  • FragmentMeasurer re-run over all 577 free canvas icons — every bound unchanged. The only bounds that moved in this PR are 60200100 and 60200200, from CTG's edits.

mvn verify green, Spotless clean.

Left alone deliberately

  • 26 fragments have no template group at all (22 are #main alone). That's a question about what those files draw, not about their shape.
  • 25270400's two patterns have no id, so they can't be referenced from defs either. Naming them belongs with the pattern usage work.
  • Group order varies — 338 are main + template + example, 13 put example before template. Reordering changes paint order, so it stays out of a "no visual change" pass.

After this, 551 of 577 hold only defs/main/template/example*, and the remaining 26 differ only by a missing template.

The matching changes to jmsfx-hallux are in the hallux working tree, uncommitted.

🤖 Generated with Claude Code

ctgnz and others added 3 commits September 25, 2026 07:18
The content changes in these four are CTG's: each Cyberspace fragment now keeps
only the drawn control measure in #main, with the illustrative endpoints and net
boxes moved into a hidden example group and the anchor points into the template.
That is the shape #78 is about, applied by hand to the three Cyberspace icons.

Inkscape rewrites a file wholesale on save - attribute per line, standalone="no",
its own namespace declarations, a namedview, an empty defs and a fresh crop of
generated ids - so all four needed the normaliser afterwards. It reported 0
refused, which is the guarantee that wanted: the drawn content is byte-for-byte
what CTG left, and only the editor's own leavings were taken out.

One follow-on belongs with this rather than with the mechanical pass: 60200100's
example group carried id="g15", which the normaliser correctly reads as a
generated id and drops, leaving the group anonymous. It is given id="example" so
it says what it is.

Bounds re-measured. 60200100 and 60200200 grew taller, which is CTG's edit - the
template's anchor arrows now reach above the drawn measure. Nothing else moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A free canvas icon holds a <g>#main, a <g>#template, zero or more <g>#example
groups, and whatever <defs> the main group needs. That is what almost all of them
already were - 545 of 577 - and this brings the stragglers into line so a consumer
asking "which element is the content" has one answer rather than four.

Three kinds of file were off the shape:

- Four put an id straight on a drawing element with no group to hold it:
  25218800 and 25281901 on a <polygon id="main">, and 25200600 and 25200700 on a
  loose <rect>. Each is now wrapped in a <g> carrying that id.
- Five define <pattern> elements loose - four at the document root, and two more
  inside #main itself, where a paint server has no business sitting. They move
  into a <defs>, which is the one place a definition belongs.
- Ordering and ids otherwise left exactly as found.

Nothing here changes what is drawn. A <g> carrying only an id paints exactly as
its child did, and a <pattern> renders only where something references it - and
nothing does: the whole tree holds one live url(#...) reference, 25132000's
marker, which is untouched. The issue's note that the pattern files hold the only
internal reference is no longer accurate.

Verified two independent ways, since SvgFingerprint cannot help here - it records
structure, so inserting a group or moving an element changes it by design:

- Every drawing primitive compared before and after with its ancestors' attributes
  folded in, so an added group is invisible to the comparison, and each pattern
  subtree compared on its own. All nine identical.
- FragmentMeasurer re-run over all 577 free canvas icons: every bound unchanged.

Left alone deliberately: 26 fragments have no template group at all, which is a
question about what those files draw rather than about their shape, and two
patterns in 25270400 have no id, so they cannot be referenced from defs either -
naming them belongs with the pattern usage work rather than here.

Closes #78

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The hidden #474747 rectangle is not part of the construction guide - it is there
so the white border of the symbol in #main has something to show up against.
The corpus agrees: three fragments carry that backdrop, and the other two,
25200400 and 25200700, both file it under an example group.

Id only, so nothing is drawn differently - the rectangle is display="none" in any
case, and ids are outside the fingerprint. With this, 551 of 577 free canvas
fragments hold only defs/main/template/example*, and every one that does not is
simply missing a template.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ctgnz and others added 2 commits September 25, 2026 07:32
The README's Details section has been a TBD placeholder for "the internal
composition of the SVG files" since the fork. Now that the shape is settled and
every fragment holds to it, it can say what it is.

A free canvas fragment holds exactly one <g>#main, zero or one <g>#template,
zero or more <g>#example, and whatever <defs> main references.

The template is optional rather than required, which is the part worth recording
because it is a decision rather than an accident. Twenty-six fragments have none,
and they divide cleanly: an area measure is defined by at least three control
points the user places, so there is no fixed geometry a template could draw -
that covers every area without one, Airhead Line included, which is an area
despite the name - and the six Space Debris fragments are whole symbols in the
way an ordinary icon is, so main holds all of it.

Confirmed against both trees: 577 free canvas fragments each, 0 failing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Injecting fragments into the generated classes needs a rule for "which element is
the content", and for the free canvas fragments that rule is the shape #78
settled: exactly one <g>#main, zero or one <g>#template, zero or more <g>#example,
and whatever <defs> main references. A fragment drifts off it easily - an editor
leaves a group anonymous, a new icon arrives with its content loose at the root -
and both of those had already happened. Catching it at verify costs nothing;
catching it during injection means finding construction scaffolding rendered as
though it were the drawing.

FragmentShapeChecker reports every fault on a fragment at once, so a file is named
once rather than once per thing wrong with it, and points at svg/README.md rather
than restating the rule in an error message. Editor metadata is passed over
deliberately: FragmentNormaliser's check is bound to the same phase and already
fails on it with a message that says what to do, so reporting the same file twice
for something that is not about its shape would only be noise.

Which icons are free canvas, and which fragment each draws, moves into
FreeCanvasIcons. FragmentMeasurer had that derivation inline, its own javadoc
citing jmsfx#52 as the caution against duplicating it; a second copy here would
have been exactly that mistake. The measurer now shares it and loses the two
helpers that went with it. Behaviour-preserving: both models re-measure
byte-identical.

Bound alongside the normaliser check, taking the directory from the module layout
for the same reason - config.yml's resourceDir is an absolute Windows path that
resolves nowhere on a runner - and the model from the classpath, which does not
have that problem.

Verified it can actually fail: renaming one fragment's #main to #content fails the
build with "25150100: a group called content; no <g id="main">". 13 unit tests
cover it, every case a shape that was genuinely in the tree before this issue.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ctgnz
ctgnz merged commit 3fef631 into master Sep 24, 2026
1 check passed
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.

Control Measure fragments: separate the drawn content from the construction scaffolding

1 participant