nf-metro-layout-fix

star 77

Drive code-level fixes to nf-metro when a real pipeline render exposes a layout bug that isn't a mmd mistake, without regressing pipelines that already ship metro maps. Use when working in nf-metro's src/ (layout engine, routing, parser) to fix kinks, station overlaps, breeze-past, asymmetric fans, bypass routing, or bbox overflow on a real pipeline diagram. Covers the savepoint tag pattern, the invariant-test-first-then-fix-then-runtime-validator loop, gallery regression vetting with build_gallery and build_render_diff, converting global fixes to conditional ones so other renders aren't pushed around, and the additive-commits-only PR chain workflow. For authoring the mmd content itself (deciding lines, stations, sections), see the `pipeline-metro-diagram` skill. For routine gallery regression testing of nf-metro, see `render-topologies`.

pinin4fjords By pinin4fjords schedule Updated 5/15/2026

name: nf-metro-layout-fix description: Drive code-level fixes to nf-metro when a real pipeline render exposes a layout bug that isn't a mmd mistake, without regressing pipelines that already ship metro maps. Use when working in nf-metro's src/ (layout engine, routing, parser) to fix kinks, station overlaps, breeze-past, asymmetric fans, bypass routing, or bbox overflow on a real pipeline diagram. Covers the savepoint tag pattern, the invariant-test-first-then-fix-then-runtime-validator loop, gallery regression vetting with build_gallery and build_render_diff, converting global fixes to conditional ones so other renders aren't pushed around, and the additive-commits-only PR chain workflow. For authoring the mmd content itself (deciding lines, stations, sections), see the pipeline-metro-diagram skill. For routine gallery regression testing of nf-metro, see render-topologies.

Fixing nf-metro Layout Bugs Surfaced by a Real Pipeline Render

Captures the workflow for driving code-level fixes to nf-metro when a pipeline's metro map exposes a layout case the engine handles wrong. The goal: ship the fix without regressing the renders for other pipelines that already use nf-metro.

If the bad render is actually a mmd mistake (line drawn through a station that doesn't consume it, missing off_track directive, undersized rowspan, etc.), don't reach for this skill — go fix the mmd in the pipeline repo. The pipeline-metro-diagram skill covers the triage for "is it mmd or nf-metro?". Only when the mmd is correct and the engine still produces a bad layout do you arrive here.

When to use this skill

Trigger when:

  • A pipeline's rendered mmd looks wrong (kinks at section boundaries, station or icon overlaps, breeze-past, broken trunk alignment, bypass routing through a non-consumer, asymmetric fans, bbox overflow) and you've already verified the mmd is correct.
  • You're about to modify code in src/nf_metro/layout/engine.py, src/nf_metro/routing/, or src/nf_metro/parser/ to fix a layout case surfaced by a real pipeline diagram.
  • You're managing a stack of PRs against nf-metro main from a single pipeline-integration session and want the chain rules in one place.

Step 1: Make a save point first

Before touching nf-metro, tag the current state of the pipeline's render as "good enough":

# In the pipeline repo
git tag pipeline-render-baseline <commit>

# In nf-metro
git tag <pipeline>-render-savepoint <commit>
git push origin <pipeline>-render-savepoint

Treat the savepoint as immutable. Every later step measures regressions against it. If the iteration goes off the rails, you can return to this tag and start over with a different approach.

Step 2: Per-fix iteration loop

For each layout issue:

  1. Reproduce minimally. Render the pipeline mmd with --debug and the exact params it ships with. Capture the SVG. Identify the offending coordinates by parsing the SVG (<rect> station markers, <path> edge data) — visual inspection alone is unreliable for sub-pixel issues.

  2. Form a hypothesis. What in src/nf_metro/layout/engine.py (or routing/core.py, parser/mermaid.py) is producing the wrong output? The engine is a long pipeline of phases; the bug is almost always in a specific phase rather than across many.

  3. Add an invariant test first, then fix. Each layout invariant should fail on the bug case and pass after the fix. Examples from prior sessions:

    • test_row_trunk_marker_cy_consistent (trunk Y consistent across same-row sections)
    • test_no_station_or_icon_overlap (no two station/icon bboxes intersect)
    • test_lines_dont_cross_non_consumer_markers (no line passes through a station that doesn't consume it)
    • test_all_stations_snap_to_grid (with explicit exceptions for half-grid 2-branch fans)
    • test_bypass_v_has_horizontal_segment (virtual bypass stations have a visible flat segment ≥ 20 px)
    • test_section_bbox_contains_all_content (no station/icon overflows its section's bbox)
    • test_no_kink_at_section_boundary (inter-section trunk Ys match)
    • test_loop_column_stations_share_x (column-mate stations share X)

    Parametrize each invariant over multiple fixture mmds AND multiple render parameter sets (savepoint params, rnaseq defaults, no-center-ports). A bug that only manifests at non-default params has bitten us before.

  4. Verify against the gallery. After every fix, run python scripts/build_gallery.py --debug and build_render_diff.py against origin/main. If any existing example shifts visually, classify the shift:

    • Improvement / neutral: fine.
    • Detrimental (new crossings, mad routing, bbox overflow, label overlap, station collisions, broken trunk alignment, breeze-past): the fix needs to become conditional. Don't ship the fix in its current form.
  5. Convert "fix it everywhere" to "fix it conditionally" when needed. A common failure mode: a fix that works for the new pipeline introduces spurious work for renders that didn't need it. Examples:

    • Bbox padding change that grows row gaps even where no bypass routing occurs → restrict to column-overlapping section pairs only.
    • Bypass V flat-segment minimum that pushes 4 unrelated gallery examples vertically → only enforce when there's horizontal headroom.

    The pattern: identify the precise topological precondition that makes the fix needed, and gate the new behaviour on that precondition.

  6. Add a runtime validator (not just a test). End users running nf-metro render should see a clear error if the layout violates an invariant — not produce a silently broken render. Add a _guard_<name> function called from compute_layout's validate block that raises PhaseInvariantError with the offending coordinates.

Step 3: The "improvement ratchet"

Every regression we identify adds an invariant before its fix lands. The test suite grows monotonically. Later PRs can't accidentally re-introduce problems that earlier PRs solved. This is the only way the engine — built as a long sequence of mutating phases — stays sustainable across many pipelines.

When dispatching agents to do nf-metro work, include in their brief: "you must add an invariant test that fails on the bug AND a matching runtime validator. Both are mandatory; the fix is not complete without them."

Step 4: Shipping the chain back to main

Layout fixes accumulate into a stack of PRs against nf-metro main: the bottom-of-chain PR is based on main, each subsequent PR is based on its predecessor's branch, and bases auto-retarget as each PR merges.

The actual per-PR shipping workflow — worktree setup, reverting known-rejected commits, /simplify as a separate commit, gallery diff vs main, classifying every changed example, sweeping narrative comments, rewriting the description to be standalone, triggering CI, and the exact after-merge cleanup order (re-target children before deleting the merged branch) — lives in the pr-chain-vet skill. Reach for that one when you're walking the chain into main one PR at a time.

A few rules live here as well because they shape how the fixes are written in the first place, not just how they're shipped:

  • No force-pushes. Every change to a PR is an additive commit. To undo something already in the branch, append a git revert <hash> — don't rewrite history.
  • One concern per commit. The fix, the /simplify pass, and any follow-on detrimental cleanup land as separate commits so each can be read on its own.
  • Reconciling stack regressions: fold fixes into the earliest PR where the issue first appears, not as a bolt-on top-of-stack PR. A regression introduced by an early PR may only become visible later in the chain; when that happens, append the fix commit to the offending PR's branch and re-vet that PR plus everything downstream.
Install via CLI
npx skills add https://github.com/pinin4fjords/nf-metro --skill nf-metro-layout-fix
Repository Details
star Stars 77
call_split Forks 5
navigation Branch main
article Path SKILL.md
More from Creator
pinin4fjords
pinin4fjords Explore all skills →