feat(ci): pre-commit hook to render metro maps - #1243
Open
adamrtalbot wants to merge 1583 commits into
Open
Conversation
fix(docs): convert absolute internal links to relative; exclude releases from validator
…ator The starlight-links-validator explicitly rejects relative links, so the prior fix (PR #1152) was wrong. Revert all docs files to absolute /nf-metro/ links and instead configure the validator to skip any link that starts with /nf-metro/ but not with the current build base. Versioned/dev builds use a different base (e.g. /nf-metro/dev/), so /nf-metro/xxx/ links resolve to pages outside the current build. The production build (base=/nf-metro/) validates them normally, so skipping them in dev/versioned builds is safe. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(docs): exclude cross-base links from validator; revert relative links
…ctions Under a tightened fold_threshold a convergence sink folds onto a lower row of its branches' column, fed through a TOP entry. Each upper branch's BOTTOM-exit feeder dropped straight down the exit column, ploughing through the intervening branch boxes away from any port. Add a dispatch rule, ahead of the plain TB bottom-exit drop, for a feeder whose straight drop would cross an intervening section: it diverts out into the clear inter-column gap left of the column, descends past every stacked box, and turns into the TOP entry. Adjacent feeders with no box in the way keep the straight drop. Refine check_perp_exit_over_leadin_clears_only_spanned_sections so its overshoot test exempts a same-row section no deeper than the source (the corridor must clear the source's own depth regardless), which the divert needs. Fixes #1148 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… fixture Add examples/topologies/convergence_sink_fold.mmd (a fold that stacks parallel branches in one column and drops the shared sink onto a lower row via a TOP entry) to the gallery and corpus, plus a route-around invariant test over the multi-line repro at folds 1-3 and the committed single-line fixture. Regenerate the guard golden (additive: the new fixture's trace), the routing gate-coverage baseline + matrix (the divert's crossing predicate now exercises an arm the baseline listed as a gap), and prune the now-covered triage entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Docs author internal links with the production `/nf-metro/` base, but versioned deploys build with a different base (`/nf-metro/dev/`, `/nf-metro/latest/`, `/nf-metro/<version>/`), so those links pointed at pages that do not exist on those deploys - broken at runtime and flagged by starlight-links-validator on the deploy build. Add a remark plugin that rewrites the `/nf-metro/` prefix to the active build base. It runs before the validator's rehype pass, so links resolve correctly at runtime on every base and the validator checks them against real pages instead of being told to skip them. With links resolving, drop the cross-base exclusion: the validator now validates all internal cross-references (including the releases pages, which are real content entries). The only remaining exclusions are the gallery/ and pipelines/ custom Astro routes (opaque to the validator) and the live_demo.mp4 static asset. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Build the Astro site on PRs that touch docs or the site so starlight-links-validator runs and fails the PR on a broken internal link, instead of the breakage only surfacing when the post-merge deploy builds and fails. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(docs): rebase internal links to the build base so link validation works on every deploy
…#1144) Under a tightened fold_threshold, distinct lines reaching a horizontal-flow section through a shared TOP/BOTTOM entry (one single-line feeder per line, all stacked on one column trunk) collapsed onto the port's single trunk X: the converging approaches overlaid one vertical channel and any shared run through the section drew as a zero-offset collinear bundle. Detect this distinct-line perp entry (needs_perp_approach_fan) and: - skip the entry-port offset collapse so the lines keep distinct slots and any shared run separates; - fan each line's approach X by its cross-boundary bundle index (_perp_approach_fan_x), shared by the inter-section feeder drop and the intra-section drop so they meet on one per-line channel. Excludes redundant same-bundle feeders (each whole bundle keeps its feeder lane) and vertical-flow consumers (their shared run separates in X already). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- detect horizontal-flow sections via lanes_run_along_y rather than a raw
("LR","RL") direction tuple
- factor the shared bundled-feeder index lookup out of _perp_approach_fan_x
and _perp_entry_crossing_x into _bundled_feeders
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…trix scripts/routing_gate_coverage.py wrote the matrix doc with a bare H1 and no frontmatter, so each regenerate stripped the title/description the Starlight content collection requires, failing the docs link-validation build. Emit the frontmatter block from the generator and regenerate the committed doc. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… do not cross The distinct-line fan slotted approach channels and the intra-section bundle by raw bundle index, which under a section reflection placed the outermost feeder (the one wrapping around the intervening boxes from furthest away) on the inner channel. Its riser then crossed its bundle-mate to reach that channel, and the shared run drew it on the wrong side. Order both by approach instead: the feeder descending from furthest away (the around-stack wrap) takes the outermost channel and the top bundle slot, the trunk-near feeder stays on the column trunk. The descent, the turn, and the shared run now agree, so the lines stay on their own side throughout. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- read the lone bundled feeder via next(iter(...)) rather than max() over a 0-or-1 element list - note the source-Y ordering coupling between _slot_perp_fan_bundle and compute_bundle_info that the approach fan relies on - drop a stray trailing comma in the feeder sort Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace text-only tile grids with inline <Metro purpose="showcase"> renders grouped by category (gallery) or per pipeline. Each entry heading links to its dedicated page for full-screen viewing. [skip ci]
…l-collapse fix: fan distinct lines onto parallel channels at a folded perp entry (#1144)
Pass explicit headings to <StarlightPage> and add matching id attributes to h2 elements so Starlight's right-hand panel links to each category / pipeline section. [skip ci]
feat(docs): render maps inline on gallery and pipelines index pages
…or_scheme parity fix (#981) Introduce RenderConfig to group the render-side kwargs on render_string, making the embedding API easier to extend. Add a shared _render_graph private helper that both render_string and the CLI now delegate to, eliminating the duplicated font_portability/render_svg/render_html branch in cli.py. Also close the self_color_scheme parity gap: render_string was silently hardcoding True; it is now an explicit kwarg (default True) that maps to --no-self-color-scheme on the CLI. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ny to satisfy mypy [skip ci] Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…n on shadowed kwargs Address review nits on the RenderConfig change: - embed_basename uses a plain string default instead of field(default=...); drop the now-unused dataclasses.field import. - Promote _render_graph to a public render_graph: render_string's docstring already points advanced callers at prepare_graph + the renderer, and this is exactly the render half they need without re-parsing. cli.py imports the public name. - render_string now warns when config= is passed alongside non-default flat render kwargs, so the "pass one or the other" contract is enforced rather than only documented. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Anchor the pytest.warns regex on `ignoring [...'responsive'` rather than a bare substring so it asserts the kwarg appears in the ignored list. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
refactor(api): RenderConfig dataclass + self_color_scheme parity fix
…1143) Under a tightened fold_threshold an inter-section fan from a section's LEFT exit into a relocated section's RIGHT entry built a bundle-order flip plus a non-concentric corner. Two coupled defects: - compute_station_offsets desynchronised the bundle: a reconvergence section fed by a single multi-line feeder whose lines originate at separate single-line producers has no well-defined delivered order (the producers each sit on a local slot 0, so two lines collide on one offset). Settling the section on that ambiguous, hash-seed-dependent order desynchronised its exit port from the relocated section's entry port, forcing the staircase to cross. Leave such a section on its priority order instead. - the staircase handler always built the per-leg fan, which sizes each line's half-corners independently and pinches the bundle when the descent is too short for two corners to fit. A rigid bundle now takes the concentric builder there, sizing the merged bend wholesale. Adds examples/topologies/fold_left_exit_right_entry.mmd (minimal repro, in the gallery), a regression test over it plus the epitopeprediction motivating case, and the guard golden baseline entry for the new fixture. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ry (#1161) A folded vertical-flow (TB/BT) section that exits LEFT/RIGHT into a relocated section one row below aligns its exit port to the target's entry Y in Stage 3.4. When the target spans several sub-rows its entry Y keeps descending as those sub-rows settle (Stages 6.13-6.15), after the exit was aligned, so the exit ends a sub-row above the final entry and the inter-section run renders with a jog. Follow the exit down to the now-settled entry at Stage 6.15a, before the structural-extent snapshot, so the snapshot and the next relay's inter-row cascade both see the lowered exit. Only a target that settled along the flow from the exit is followed; one seated against the flow keeps its own descent (an intentional staircase), and the bbox-contained alignment target keeps the snapped exit inside its section. Adds a runtime guard (_guard_fold_lr_exit_follows_target) and a parametrised layout invariant covering the whole multi-section corpus. Reframes the fold_left_exit_right_entry regression to assert the run is straight (the offset-desync bundle-order guard still applies). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…#1161 The fold LEFT-exit -> RIGHT-entry connector no longer descends through a short staircase: #1161 aligns the exit to the target's settled entry, so the run is straight (descent 0). No corpus fixture reaches the short-descent (0 < descent < 2*radius) rigid arm of _route_left_exit_right_entry_step; only the long-descent staircase (tb_left_exit_step) routes there, via the per-leg fan. Remove the unreachable concentric branch, restoring the per-leg-only handler. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
chore(docs): point root redirect at latest/ instead of dev/
Note the single-branch clone option and why gh-pages history exists and gets manually squashed rather than continuously rewritten. Ref #1240
The site root now redirects to the latest stable release rather than the in-progress dev build, but the README's documentation links still pointed at the dev/ version segment.
docs: document gh-pages clone size and history squash
docs: point README doc links at latest/ instead of dev/
render took exactly one positional INPUT_FILE. A pre-commit hook (or any batch caller) staging more than one .mmd file in a single invocation had no way to render them together short of hand-rolling a manifest for render-many, which only accepts named per-job options (theme, mode, embed_font, ...) rather than passing shared CLI flags through generically. INPUT_FILE is now INPUT_FILE... (nargs='+'). Given more than one file, all render within the same process (amortising interpreter/import startup, like render-many), each write to their own sibling <input>.<format>, every file is attempted even if an earlier one fails, and the command exits non-zero if any failed without dropping the successful outputs. -o/--output is rejected when more than one input is given, since a single explicit path can't serve multiple outputs. Single-file behavior is unchanged: -o still targets that exact path, a failure still raises immediately with the original error message, and the existing "Rendered N stations, N edges, N lines -> path" success message is preserved (the [i/total] OK/FAIL summary lines only appear once there's more than one file).
Every gallery and pipelines page (index and per-entry, now that #1103's dynamic routes exist) gets a distinct 1200x630 Open Graph preview pairing its title/description with the page's own metro map, rendered with baked colours and composited via satori + sharp. Other docs pages fall back to a site-wide default image. Also wires README hero regeneration into the publish workflow so the hero renders track engine changes automatically instead of needing a manual PR each time. Fixes #1105
render's new multi-file loop only caught click.ClickException, so any other exception escaping a file's render (an OSError from an unwritable output directory, a UnicodeDecodeError, an unanticipated layout bug) aborted the whole batch instead of continuing to the remaining files -- contradicting the "every file is attempted even if an earlier one fails" guarantee. render-many already handled this correctly with a bare except Exception; the narrower catch in render was an avoidable regression relative to that existing pattern in the same file. Extract a shared _run_batch(items) helper (attempt every item, print [i/total] OK/FAIL, catch any exception, raise one ClickException with the failure count) and use it from both render's multi-file path and render-many, instead of two independently-drifting copies of the same loop with a duplicated failure-summary string. Go further and have render-many's per-job body call _render_one (the single-file render logic extracted for render's multi-file support) instead of carrying its own ~70-line duplicate of the parse/theme/render/write sequence. This also fixes a latent gap: render-many never passed source_dir to prepare_graph, so a relative %%metro logo:/file: path in a manifest job resolved against the caller's cwd instead of the job's own input directory; render always passed it. source_dir resolution tries the path as-is first and only falls back to source_dir, so this cannot change behavior for a path that already resolved (confirmed against examples/rnaseq_sections.mmd, whose logo path is used by the render-many byte-identical-output test). render_many's own out_path.parent.mkdir(...) moved into _render_one so render -o gains the same missing-parent-dir handling. Verified: full suite 27099 passed / 0 failed (before and after); manually reproduced the original bug (unwritable output dir for the middle of 3 files) and confirmed the other two still render with the overall exit reflecting the one failure.
feat(cli): render accepts multiple INPUT_FILEs
adamrtalbot
force-pushed
the
feat/metro-map-precommit-hook
branch
2 times, most recently
from
July 1, 2026 12:36
8a372b5 to
45b3fa0
Compare
- scripts/render_readme_hero.py now calls nf_metro.api.render_string instead of hand-recomposing parse/layout/theme/render, matching the shared entry point the CLI itself uses. - Extract shared page-meta.ts (title/description) so index.astro pages and their og/*.png.ts siblings can't drift, and og-targets.mjs (fixed .mmd paths) so those three routes' maps are guaranteed to join the batched chrome-less pre-warm rather than coincidentally overlapping with the gallery/pipelines content collections. - Extract a pure ogImageMetaTags() helper shared by astro.config.mjs's site-wide default and og-head.ts's per-page override, removing a hand-duplicated meta-tag shape. - findOgSourceFiles() now warns on a malformed content JSON instead of silently treating it the same as "not generated yet". - Truncate the OG card title (in addition to the subtitle) as a safety net against a future title long enough to overflow the fixed card height. - FONT_DIR now resolves via process.cwd() instead of round-tripping through REPO_ROOT back into website/. - publish.yml's readme-hero job gets a concurrency group so two releases published close together can't race pushing to main.
feat(ci): composite action to render metro maps
adamrtalbot
changed the base branch from
feat/metro-map-reusable-workflow
to
main
July 1, 2026 12:45
adamrtalbot
force-pushed
the
feat/metro-map-precommit-hook
branch
from
July 1, 2026 12:47
45b3fa0 to
1b30854
Compare
Gallery entries are internal layout-regression fixtures (177 of them) that nobody links to externally, so a per-entry OG image for each isn't worth the render/build/storage cost. All gallery pages now share one generic card; only the 8 real nf-core/other pipelines get their own per-entry OG image. Cuts generated OG images from 188 to 11 and returns build time and output size to roughly their pre-feature baseline.
Adds `.pre-commit-hooks.yaml` exposing an `nf-metro` hook so consumer repos re-render their metro-map SVG locally on commit. `language: python` installs this repo (hatchling) into an isolated env, exposing the `nf-metro` script. The hook matches `*.mmd` and writes the sibling `.svg`; `nf-metro render` handles one or more staged files in a single invocation, so a commit touching several maps re-renders them all. Consumers override the render flags via `args`. Documents the hook in the CI & automation page. Generated by Claude Code
adamrtalbot
force-pushed
the
feat/metro-map-precommit-hook
branch
from
July 1, 2026 12:55
1b30854 to
8fd485c
Compare
Keeping this PR scoped to the OG image feature - the release-workflow automation (auto-committing regenerated hero renders to main on every release) needs more thought before shipping, particularly around branch protection compatibility and the concurrency/push-race handling.
pinin4fjords
approved these changes
Jul 1, 2026
feat(docs): build-time OG image generation per gallery/pipeline page
Pipeline maps are usually landscape, so a left-side text panel squeezed the map into a narrow vertical strip. Move the title/description to a fixed-height band across the top instead, giving the map the full card width below it. Also swap the text "nf-metro" wordmark for the real logo asset, with its CSS custom-property colours baked to concrete values since satori can't resolve them.
.sidebar-pane is position:fixed and always spans the full viewport height below the nav, but .main-frame had no flex-grow, so on a page short enough that main content + footer fit within one viewport, the footer sat directly below the content instead of at the viewport bottom - leaving a gap where the fixed sidebar's border-inline-end still rendered underneath. Pre-existing (reproduces on gallery pages too), not introduced by the OG-image work.
refactor(docs): OG card layout, real logo, sticky-footer fix
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.
What
Adds
.pre-commit-hooks.yaml— annf-metrohook so consumer repos re-render their metro-map SVG locally on commit.Usage
Details
language: pythoninstalls this repo (hatchling) into an isolated env, exposing thenf-metroscript.*.mmd; writes the sibling.svg(assets/metro_map.mmd→assets/metro_map.svg). Consumers can overrideargs(e.g. add--mode).Verification
pre-commit try-repo . nf-metro --files metro_map.mmd: installs nf-metro from this repo, runs the hook (Passed), produces the sibling SVG.