fix(layout): widen straight-style fan row pitch for stacked branch labels - #1269
Open
pinin4fjords wants to merge 1638 commits into
Open
fix(layout): widen straight-style fan row pitch for stacked branch labels#1269pinin4fjords wants to merge 1638 commits into
pinin4fjords wants to merge 1638 commits into
Conversation
chore: update owner references after repo transfer to seqeralabs
…utton - Add `b64urlEncodeGz`/`b64urlDecodeGz` using CompressionStream/DecompressionStream - Share link button now encodes as `#mmd-gz=<b64>` (gzip + base64url), typically 60-70% shorter than the raw `#mmd=` format for realistic maps - Old `#mmd=` fragment URLs continue to load correctly (backward compat read path) - Bug-report reproduce links keep the uncompressed `#mmd=` format (URL embedded in GitHub issue body, length is not a constraint there) - Add "Copy source" button that writes the raw .mmd text to the clipboard Closes #982 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract _bytesToB64url/_b64urlToBytes to eliminate copy-paste between sync and async encode/decode pairs. Add _hashParam for fragment extraction (shared by loadFromHash and loadFromHashGz). Add _pageUrl for URL assembly. Remove redundant hasGz guard in initEditor. Fix stack-overflow risk in b64urlEncodeGz by replacing spread with forEach (mirrors existing b64urlEncode). Remove comments that restate function names. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Keeping a geometric-bypass helper re-feeds the bypassed section's height into the next pass's downstream-section placement, which lags by one pass, so a kept fix needs one more settling pass to reach its fixed point. That pass was gated on `validate`, so `compute_layout(validate=False)` (the render path) shipped a stale, one-pass-short layout while `validate=True` ran the extra pass and converged -- breaking validate-flag idempotency for a fold + bypass-V + downstream-section map. Run the settling pass whenever a bypass was kept, regardless of `validate`; the flag now only layers checks onto the pass it shares with the render path. Geometry no longer depends on the flag, and the folded downstream section seats at its converged Y in both modes. Adds examples/topologies/fold_bypass_creep.mmd (gallery + README + guard-golden baseline) as the regression lock and a focused validate-idempotency / fixed-point test. Fixes #1171 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…or mmd-gz - Run Prettier to reformat app.js (line-length wraps) - Update share-link round-trip test: wait for hash to settle after async gzip completes before asserting URL, and check for #mmd-gz= not #mmd= Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…press feat(playground): compress share links via gzip; add copy-source button
) A cross-row feed into a RIGHT entry whose source is stacked directly above the target (same column) descended its shared corridor via a mid-descent lateral jog: the wrap led in to a source-side corner, dropped to an inter-row staging channel, then stepped sideways onto the corridor X partway down before continuing the descent. When the corridor admits a clean straight drop from the source row to the entry, _route_right_entry_wrap now leads straight to it at the top corner and drops in (reusing _route_right_entry_drop_in), so the descent reads as one vertical run. Gated on same-column and corridor clearance so the adjacent-column wrap (whose band traverse runs the natural inter-row channel between the boxes) and multi-column bypasses are untouched; only fold_bypass_creep changes. Adds check_right_entry_corridor_descent_no_jog + its final-phase guard so the kink cannot silently return, plus a parametrised invariant test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e-idempotent fix: run the post-bypass settling pass in both validate modes (#1171)
The Copy source button and its click wiring were added to the standalone
harness and to app.js, but not to the deployed Astro playground page. On
that page el("btn-copy-source") is null, so the unguarded addEventListener
throws during init and aborts app.js before boot() runs - the playground
hangs forever on "Starting Python runtime…".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rce-button fix(playground): add missing Copy source button to Astro page
The playground "Report a bug" link builds its GitHub issue URL from the REPO constant in app.js, which still referenced the pre-transfer owner, so reports opened against the old repository. Update it (and the matching e2e assertion) to seqeralabs/nf-metro. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(playground): point bug-report link at seqeralabs/nf-metro
On a folded vertical-flow (TB/BT) section whose file terminus sits one row below the forking station, the bypass V peeling around that terminus seats on the trailing row, so its run-out flat is exactly the trailing-station-to-exit-corridor gap. That gap was MIN_PORT_STATION_GAP (16px), below MIN_STATION_FLAT_LENGTH (20px), so the V read as a curve apex rather than sitting on a visible flat run. position_ports now widens the perpendicular exit gap to a full station flat when a bypass V seats on the trailing row. The runtime guard _guard_bypass_v_flat_visible, previously LR/RL-only, gains a vertical-flow run-out check so the same property is enforced at render time on TB/BT sections; the horizontal grid-column gaps still feed the strike-clearance loop alone. Adds fold_bypass_creep_tight.mmd as a gallery + topology regression fixture; test_bypass_v_has_horizontal_segment auto-covers it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rename _tb_bypass_v_short_runouts to _vertical_bypass_v_short_runouts to
match its lanes_run_along_x (TB and BT) scope, and align the horizontal
sibling _bypass_v_flat_gaps_from onto the same lanes_run_along_y axis
vocabulary instead of a raw ("LR", "RL") direction tuple.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reword the fold_bypass_creep_tight gallery description to drop the mid-scalar colon that broke gallery.yaml parsing (and cascaded into the links / e2e / render-diff content builds), and re-align the topology README table to Prettier's column widths after the new row. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…reep fix(layout): clear bypass-V run-out on a tight 1-row fold (#1177)
…ng sections (#1167) A lowered fold_threshold relocates sections onto a return row, where a left/right entry/exit authored for the unfolded grid can face away from the column its connecting sections occupy; the connecting leg then wraps back across the section's own box and the routing self-check aborts. Add a parse-time grid pass that, for fold-relocated horizontal sections, turns a flow-axis entry toward its producer sections and an exit toward its consumers whenever those all sit strictly to one side. It runs before the intra-section re-anchor so that pass no longer mistakes the relocated geometry for an internal fold. A Tier-B validator (_guard_fold_relocated_flow_ports_face_connections) pins the invariant. On genomeassembly under fold this faces the convergence sink inward; the residual same-side hairpin fold-back is tracked as #1182 with a strict-xfail lock. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Disconnected components are placed in separate local column grids then stacked into disjoint vertical bands, but each component's grid rows start at its own local minimum, so two components could share a grid_row even though they occupy different vertical bands. The inter-row cascade (Stage 6.13/6.14) reasons by grid_row assuming it rises monotonically with Y; a folded component spanning several rows let an un-renormalised peer get shuffled into the band, colliding with the section above. Renormalise each component's grid rows onto a contiguous global band matching the physical stack order. Adds a fixture, a parametrised grid-row monotonicity invariant test, and a gallery entry for the CI render-diff. Fixes #1164 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hoist the next-band computation out of the shift loop into a single max-comprehension and trim the explanatory comment to its load-bearing invariant. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…st after fix for #1167 Extract _expected_flow_side(cols, col) in resolve.py: returns the horizontal side a flow-axis port should face when its connecting sections all sit strictly to one side, else None. The parse-time re-siding, the runtime guard, and the invariant test now consume it instead of each hand-rolling the all-strictly-one- side rule. The test reads the typed Port.side rather than splitting the port id. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(layout): keep disconnected components clear under folding (#1164)
…main Records the _guard_fold_relocated_flow_ports_face_connections call in the trace for fold_bypass_creep_tight and disconnected_components_fold (added on main); the guard returns early on both, so their layout is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rt-side fix: face fold-relocated convergence-sink ports toward their connecting sections (#1167)
…bottom [skip ci] A folded TB section's LEFT/RIGHT exit runs straight into a relocated target's entry. Stage 6.5 aligned the TB section's bbox bottom to the target's live bbox bottom, but that read happened before the bbox-shrink phase collapsed the target's padding band, so the TB section was left stranded below the target once it tightened. The straight inter-section run then cleared the two sections by different distances (62px vs 50px on fold_left_exit_right_entry / epitopeprediction at fold 7). Align to the target's settled content bottom (_predict_section_content_bottom) instead, so both sections tighten to the same edge. Adds a runtime guard (_guard_fold_lr_exit_sections_share_bbox_bottom) and corpus + fixture invariant tests, sharing scope via iter_fold_lr_exit_straight_runs. Fixes #1162 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The first pass lifted every titled map's topmost drawn section to a single clearance, which over-padded maps that already cleared the title: a map whose box top sat just above the clearance was pushed a full grid pitch down (e.g. differentialabundance, 05f_banner_labels moved ~58px). Split the rule into a deadband: TITLE_BAND_OVERLAP_FLOOR is the hard no-overlap floor (header badge level with the title's lowest glyph); only a map breaching it is lifted, and it lands at TITLE_BAND_CLEARANCE a small band clear. A map already clearing the title keeps its exact position. _canvas_top_shortfall (lift-to-target) and _canvas_top_preserved (hard-floor feasibility for the grid snap) replace the single deficit helper; the guard and tests assert no-overlap rather than the lift target. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(layout): clear a title band above top grid-row section headers
…and fix Merging main brought the #1273 title-band-clearance guards into the trace, so the new fixture's guard-call sequence grew (416->418). Regenerate the golden baseline to record them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(layout): top-align side-entered vertical section to a grown feeder row-mate
Same JSON auto-merge gap as before: the newly-merged rowmate fixtures' guard traces didn't pick up this branch's added guard call.
…track A station where 3+ metro lines on distinct tracks converge, with no single predecessor carrying the full bundle, hit the convergence branch in _place_single_node and snapped to the first-declared line's base track -- an extreme of the feeder spread -- forcing every other feeder into a longer detour. In diamond_style: symmetric the merge now anchors on the median feeder track (median_low, an actual feeder track, so it stays on-grid for even feeder counts). Straight mode is unchanged. Fixes #1277 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(layout): anchor symmetric multi-track merge on the median feeder track
The title-band clearance in canvas.py's _canvas_top_shortfall fired whenever a titled map's header would overlap it, regardless of whether render actually draws anything there. render.svg only draws a title or standalone logo when the map isn't --bare and the logo (if any) doesn't fold into the legend; a titled map combining a legend-embedded logo with a header that would otherwise overlap the band paid for clearance nothing occupies. prepare_graph now resolves graph.reserve_title_band from --bare, %%metro legend:, and whether a logo is configured (conservatively, via the new render.legend.logo_certainly_shows helper) before layout runs, and canvas.py gates the lift on it. HTML output always reserves, since its embedded SVG forces the legend off and ignores --bare regardless. Fixes #1278
A pre-migration check for moving off the legacy GitHub Pages builder, which has been flaking repeatedly (see #1281's stalled/errored preview build). Packages the live gh-pages branch through actions/upload-pages-artifact - the step an Actions-based deployment would use - without calling actions/deploy-pages, so it never touches the live site or its Pages Source setting. workflow_dispatch only. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
v3 (the tag used in the initial dry-run workflow) invokes actions/upload-artifact@v4 internally without a SHA, which this repo's pinned-actions policy rejects even though the unpinned reference is inside a third-party composite action rather than our own workflow file. v5.0.0 pins that internal call to a commit SHA, so it passes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Publishes the gh-pages branch via actions/configure-pages + upload-pages-artifact + deploy-pages whenever that branch changes. Replaces the dry-run-only workflow from #1282/#1283, now that the packaging step is proven out - this one actually deploys. Inert until Settings > Pages > Source is switched to "GitHub Actions": until then, deploy-pages rejects the deployment and the legacy branch-build publisher keeps serving the site unaffected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Publishes the gh-pages branch via actions/configure-pages + upload-pages-artifact + deploy-pages whenever that branch changes. Supersedes the dry-run-only workflow from #1282/#1283, now that the packaging step is proven out - this one actually deploys. Inert until Settings > Pages > Source is switched to "GitHub Actions": until then, deploy-pages rejects the deployment and the legacy branch-build publisher keeps serving the site unaffected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The gh-pages branch is a content-only orphan branch with no .github/workflows directory, so a `push` trigger scoped to it can never match a workflow definition and never fires. Listen for completion of the three workflows that publish into gh-pages instead, guarding on a successful upstream conclusion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci: fire Pages deploy via workflow_run, not gh-pages push
Actions-source deployment does not fit this repo: the docs, render-preview and cleanup workflows accumulate content into the gh-pages branch, but actions/deploy-pages keys each deployment on the triggering commit SHA. Under workflow_run that is always the default-branch head, so repeated same-SHA deploys do not resurface newly added content and previews silently fail to publish. Pages reverts to the branch builder (build_type: legacy), which redeploys the branch on every push. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci: remove the Actions-based Pages deploy workflow
The publish step pushes the preview to the gh-pages branch, but GitHub Pages builds and serves it asynchronously afterwards. Announcing the link immediately produced a comment pointing at a URL that 404s until the build finishes. Poll the preview URL after pushing and only post the ready comment once it serves; otherwise post a still-publishing notice. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci: gate render-preview comment on the URL being live
The publish job announced the preview as soon as it pushed to gh-pages, but GitHub Pages serves the branch asynchronously. For a fresh preview the old wait-for-200 check sufficed, but for an update the URL already returned 200 from the previous render, so the comment could point at stale content while the new render was still building. Embed the render run id in the diff page (build_render_diff.py --marker, passed as github.run_id) and poll until the served page carries that exact marker before posting the ready comment; otherwise post a still-publishing notice. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci: gate render-preview comment on a per-run content marker
Encodes the recurring legacy-builder stall and its re-trigger fix, the pr-renders -> pr-render-publish -> build chain, the render content marker, and the CDN caching gotchas that make preview verification misleading.
skill: add pages-ci-doctor for Pages publishing issues
pinin4fjords
force-pushed
the
main
branch
2 times, most recently
from
July 28, 2026 11:30
a90d751 to
d134b08
Compare
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.
Summary
diamond_style: straight, a 3+-way fork/join fan stacks every branchlabel on the same side (layer-column alternation picks a side once per
column), so a branch's label lands in the gap toward the next branch row.
A thick multi-line bundle's marker grows past the default label offset,
and the label reads as cramped among the bundle lines.
compute_min_y_spacingnow widens the global row pitch for a straight-style3+-way fan whose branches share a column, are fed by a thick bundle, and
carry a single-line label. Symmetric-style fans are unaffected.
_guard_wide_fan_branch_label_clears_next_row, avalidate=Trueruntime check for the same invariant, plus a shared
_column_grouped_branch_rowshelper (inoff_track.py) used by the guard,the pre-layout pitch calculator, and the new test.
test_wide_fan_branch_label_clears_next_row_marker, parametrized overthree gallery fixtures and both diamond styles.
Fixes #1266
Test plan
_guard_wide_fan_branch_label_clears_next_row) added and exercised against the full fixture corpusrnaseq_sections.mmd's bbox height grew 140→146px;funcprofiler_upstream.mmd's guard trace shortened by 6 calls — both are direct, correct consequences of the wider pitch)funcprofiler_upstream: improvement — its 7-way HUMAnN/RGI/mifaser/DIAMOND/eggNOG fan opens up, each branch label now clears the bundle instead of sitting inside itrnaseq_sections,rnaseq_sections_manual,sarek_metro: neutral — pixel-level shifts only (a few px of canvas growth), visually flat; these already had enough incidental row pitch from surrounding contentThis is a corpus-wide vertical change: any straight-style 3+-way fan with a
thick bundle gets a wider row pitch, so all 5 deltas were reviewed above.
Generated with Claude Code