Skip to content

fix: measure sublabel and tag in every renderer - #56

Merged
tt-a1i merged 5 commits into
tt-a1i:mainfrom
pruuk:fix/measure-sublabel-and-tag
Aug 5, 2026
Merged

tt-a1i merged 5 commits into
tt-a1i:mainfrom
pruuk:fix/measure-sublabel-and-tag

Conversation

@pruuk

@pruuk pruuk commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

fix: measure sublabel and tag in every renderer

Problem and value

sublabel and tag render as a single unwrapped <text> element with
text-anchor="middle". Only workflow measured its sublabel against the node
box. The other four renderers emitted both fields at a hardcoded font size
with no width check, so an over-long value renders across its neighbours while
validate --quality showcase still reports 9/9 checks, 0 errors, 0 warnings.

Renderer sublabel tag Measured before? Shrinks before?
workflow computed fixed 7 sublabel only sublabel only
architecture fixed 9 fixed 7 no no
sequence fixed 7 (no tag field) no no
dataflow fixed 7 fixed 7 no no
lifecycle fixed 7 fixed 7 no no

Two things make this worse than a cosmetic gap:

  1. Validation gives false confidence. The label field is measured; the
    fields next to it are not, so the receipt is green while text visibly
    overlaps.
  2. The tool actively steered authors into it. Every renderer's too-wide-label
    message said some form of "shorten the label, move detail to sublabel" —
    advice that moved text into the one field nothing measured.

Reproduction on main (before)

# examples/agent-run.lifecycle.json with one long sublabel:
#   states[0].sublabel = "CheckStatProgression, one draw from a 10000 step range"
node bin/archify.mjs validate lifecycle overflow.lifecycle.json --quality showcase --json
ok: true   checks: 9/9   errors: []   warnings: []

The rendered SVG has that sublabel running across the neighbouring states.

Same command on this branch (after)

ok: false  stage: render
Lifecycle layout validation failed:
- Sublabel "CheckStatProgression, one draw from a 10000 step range" needs ~195px
  at the 6px legible minimum, but state "queued" provides 110px
  — shorten the sublabel or increase state.width.

Scope

What changed

  • New renderers/shared/text-fit.mjs. This is render-workflow.mjs's existing
    file-local pattern lifted into shared code, unchanged in behaviour:
    • fittedNodeFontSize shrinks text toward a legible minimum at render time,
      so ordinary overruns get smaller instead of overlapping.
    • minimumNodeTextWidth reports the width still needed once the text has
      shrunk as far as it may, so validation rejects what shrinking cannot save.
  • architecture, sequence, dataflow, lifecycle: sublabel (and tag
    where the type has one) now use fittedNodeFontSize for the <text>
    font size, and carry a minimumNodeTextWidth check beside the existing label
    check.
  • workflow: same treatment for its tag, which had the same gap. Its label
    and sublabel behaviour is byte-identical to before.
  • All five too-wide-label messages no longer say "move detail to sublabel".
    They now ask to shorten the label or widen the box.

The fitting geometry is shared (widthFactor: 0.6, horizontalPadding: 8);
per-field preferred/minimum sizes stay with each renderer, because
architecture sets sublabels at 9px and the rest at 7px. The legible minimum is
6px throughout, matching workflow's existing sublabelMinimum.

sequence participants are a fixed layout.participantW (86px) with no
author-facing width, so shrink-to-fit applies but "widen the node" does not —
its message asks only for shorter text and names the fixed width.

What deliberately did not change

  • No schema changes. No new authored fields. No change to any renderer's
    preferred font sizes, so a diagram whose text already fits renders identically.
  • Label measurement is untouched (each renderer keeps its own 6.2/6.6/6.8
    per-unit constant).
  • Wrapping is still not implemented; this measures and shrinks only.

No unrelated changes: confirmed.

Stability impact

  • Compatibility: this is a new blocking layout rule, so a spec with a
    sublabel or tag that cannot fit even at 6px now fails where it previously
    passed. That is the point of the fix, but it is a behaviour change for
    existing specs. Diagrams whose text merely overran now render shrunk and
    keep passing.
  • Generated-artifact risk: exactly one bundled artifact changed — see below.
  • Failure behaviour: fails closed through the existing
    throwDiagnosticProblems path, with the same message shape as the workflow
    rule that has shipped for several releases (threshold + remediation verb).
  • Rollback: revert the two commits; text-fit.mjs is additive and has no
    other callers.

Tests run

cd archify && node --test test/layout-rules.test.mjs
# before implementation: ℹ pass 55   ℹ fail 13   (the new cases, failing as intended)
# after  implementation: ℹ pass 68   ℹ fail 0

Adds 13 regression tests:

  • One validation case per renderer per field (8), mirroring the existing
    workflow: node sublabel wider than its legible minimum case.
  • One shrink-to-fit render case per renderer (4) asserting the emitted
    font-size drops below the preferred size but stays at or above 6px.
  • One contract test (1) asserting no renderer redirects an over-long label into
    sublabel.

Full suite, on a LF checkout (see caveat):

cd archify && node ../scripts/run-tests.mjs
# main:        ℹ tests 577   ℹ pass 565   ℹ fail 9  (baseline)
# this branch: ℹ tests 590   ℹ pass 577   ℹ fail 9  (same 9, after the gallery rebuild)

The 9 baseline failures are identical on main and on this branch and are
unrelated to this change (repository-evidence tests needing a real origin, the
live preview browser test, package smoke needing a built package, and the
README-language checks). Two further tests — compare failure preserves an existing trusted artifact and the shared renderer rechecks its guarded output immediately before writing — flip between runs under the concurrent runner on
both branches; both pass in isolation on this branch.

Caveat worth flagging separately: on a Windows checkout with
core.autocrlf=true, 5 extra tests fail on unmodified main because assertions
like skill.match(/^---\n([\s\S]*?)\n---/) in test/skill-metadata.test.mjs
assume LF. A .gitattributes with * text=auto eol=lf would fix that. Not
touched here.

Visual evidence

Verified against the six real diagrams that surfaced this bug (a MUD engine's
architecture/sequence/dataflow/lifecycle set), in both their pre-fix and
hand-corrected states:

fields flagged by a standalone fixed-font checker rejected by this branch rendered shrunk & fitting left overflowing
pre-fix specs 21 20 1 0
hand-corrected specs 0 0 — 0

The one field that is not rejected is an architecture tag needing ~230px at 7px
in a 200px box. It now renders at font-size="6" and fits — shrink-to-fit
rescuing it is the correct outcome, and is why the shipped fix uses workflow's
approach rather than a fixed-font width test.

Zero false positives on the six hand-corrected specs.

Visual review: passed (before/after SVG inspected for the lifecycle
reproduction above and for the shrunk architecture tag).

Generated artifacts

node scripts/build-gallery.mjs docs — 3 files, 6 lines total:

docs/gallery.html                                     | 6 +++---
docs/gallery/artifacts/release-delivery.workflow.html | 2 +-
docs/gallery/manifest.json                            | 4 ++--

The only artifact change in the entire gallery is the release-delivery
workflow example's owner: on-call tag, which is fractionally too wide for its
node and now renders at 6.6 instead of 7. Every other bundled diagram is
byte-identical, which is the clearest evidence that this does not disturb
already-fitting text.

**archify.zip rebuilt via scripts/build-zip.sh and committed. It ships the
renderers, so the new renderers/shared/text-fit.mjs and the five updated
renderers have to be in it. 879,783 → 882,703 bytes.

Verified against this repo's own zip-freshness gate before pushing — build a
fresh archive, diff -r the extracted trees against the committed one, exit 0
— plus node scripts/package-smoke.mjs on the extracted package.

Checklist

  • Minimal focused change; existing typed JSON behaviour preserved except for
    the new blocking rule that is the subject of the PR.
  • Ran targeted tests. npm test runs the full gate; see the caveats above
    for the pre-existing failures and the CRLF issue.
  • Added regression tests for the behavioural change (13).
  • Checked generated artifacts and package freshness: gallery rebuilt (1
    artifact changed) and archify.zip rebuilt, both committed.
  • No secrets, private repository content, or customer data in fixtures.

Maintainer follow-up

  • Merged current main, including the release-identity fix from fix: restore release identity checks #62.
  • Added four render-level regressions proving recoverable tags actually shrink in Architecture, Dataflow, Lifecycle, and Workflow.
  • Rebuilt archify.zip from the LF checkout so the repository's ZIP freshness gate is deterministic.

Additional validation:

  • layout rules: 72/72 on Node 26 and Node 18
  • full suite: 594/594
  • WebM artifact smoke: passed
  • ZIP freshness: passed
  • extracted package smoke: passed
  • git diff --check: passed

Fixes #61

pruuk added 3 commits August 4, 2026 13:17
`sublabel` and `tag` render as a single unwrapped <text> element with
text-anchor="middle". Only `workflow` measured its sublabel against the
node box; `architecture`, `sequence`, `dataflow` and `lifecycle` emitted
all of them at a hardcoded font size with no width check, so an over-long
value painted across its neighbours while `validate --quality showcase`
still reported 9/9 with zero errors. `workflow`'s own `tag` had the same
gap.

Lift workflow's existing two-part pattern into renderers/shared/text-fit.mjs
and apply it everywhere:

  - fittedNodeFontSize shrinks the text toward a legible minimum at render
    time, so ordinary overruns get smaller instead of overlapping.
  - minimumNodeTextWidth reports the width still needed once the text has
    shrunk as far as it may, so validation rejects what shrinking cannot
    save.

The fitting geometry is shared; per-field preferred/minimum sizes stay with
each renderer, because architecture sets sublabels at 9px and the rest at
7px. Workflow's label and sublabel behaviour is unchanged.

Sequence participants are a fixed layout width with no author-facing knob,
so its message asks only for shorter text.

Every renderer's too-wide-label message advised "move detail to sublabel".
That field is measured now, so the advice would move the problem rather
than fix it; each message now asks to shorten the label or widen the box.

Adds 13 regression tests: a validation case per renderer per field, a
shrink-to-fit render case, and a contract test that no renderer redirects
an over-long label into sublabel.
The release-delivery workflow example carries a tag ('owner: on-call')
that is fractionally too wide for its node, so the new fit renders it at
6.6px instead of 7px. Only that one artifact changes.
The archive ships the renderers, so the new shared text-fit module and the
four updated renderers have to be in it. Built with scripts/build-zip.sh;
verified against CI's own staleness gate (build fresh, diff -r against the
committed archive) and package-smoke.
@pruuk

pruuk commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

A note on the red checks, since two of them have different causes.

zip-freshness — fixed. I'd originally left archify.zip stale and said so
in the description, because scripts/build-zip.sh needs rsync and zip and I
had neither. I've now built it properly in a Linux container and pushed it as
build: rebuild archify.zip for the sublabel and tag fit. Verified against this
repo's own gate before pushing:

$ (cd /tmp && /repo/scripts/build-zip.sh fresh.zip)
$ unzip -q /tmp/fresh.zip -d /tmp/fresh && unzip -q archify.zip -d /tmp/checked
$ diff -r /tmp/fresh/archify /tmp/checked/archify
$ echo $?
0
$ node scripts/package-smoke.mjs /tmp/pkg/archify
package smoke passed on linux

Please treat that commit with the usual suspicion you'd give any regenerated
binary — the archive is 879,783 → 882,703 bytes, and the only content change is
the new renderers/shared/text-fit.mjs plus the five updated renderers.

test (Node 18/20/22/24) — not this PR. It fails on main too, before a
single test runs:

$ git checkout main && cd archify && npm ci && npm test

> archify@2.13.0 check:release-identity
> node ../scripts/check-release-identity.mjs
release identity: README_EN.md must remain byte-identical to README.md.

19213e1 ("docs: remove attribution section") dropped the Attribution section
from README.md and README_ZH.md but not README_EN.md, and npm test
chains check:release-identity ahead of the suite, so it halts there. Filed
separately as #ISSUE_4_NUMBER — it's a four-line deletion and unrelated to this
change, but it will keep every PR red until it's fixed.

For what it's worth, with that gate stepped over, the suite on this branch shows
the same pre-existing failures as main and no new ones — 577 pass / 9 fail on
main, 590 tests with 577 pass / 9 fail here, the 13 extra all being the new
regression cases.

@tt-a1i
tt-a1i merged commit ed9e91f into tt-a1i:main Aug 5, 2026
9 checks passed

tt-a1i commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Thank you, @pruuk, for this thoughtful and thorough contribution. The shared text-fitting implementation, actionable diagnostics, regression coverage, and detailed validation evidence make Archify more reliable across all five renderers.

We've merged the PR into main, and we're very grateful for the care you put into both the fix and its verification. We'd be delighted to have you continue building Archify with us—future ideas, issues, and pull requests are always welcome.

Thanks again for helping make Archify better!

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.

Sublabel and tag are never measured, so diagrams pass validation while text overlaps

2 participants