refactor(atlas): migrate to public warm_solve + golden the placement decisions#35
Conversation
…decisions MakieTextRepel shipped the public warm-start primitive we filed #24 for (PR #27, exported `warm_solve`), so the Atlas no longer needs to reach into unexported internals. Migration (placement-preserving): - Atlas.jl: `using MakieTextRepel: warm_solve` (was ProjectionSolver/RepelParams/solve_cluster). - place.jl: drop the _PARAMS/_SOLVER objects; call `warm_solve(anchors, sizes, bounds; …, _SOLVE_KW...)` with the same params. warm_solve is a thin face over the same seam, so output is identical — verified by a BYTE-IDENTICAL hero re-render (same sha256) and the unchanged geometric golden. - Project.toml: pin rev → 90f87ed3 (past #27/#28). Comments across src updated solve_cluster→warm_solve. Placement golden (now possible — #27 guarantees+documents solver determinism): - golden.jl: `placement_rows`/`atlas_placement_digest` hash the solver's DISCRETE decisions (which side each label leans = offset quadrant, + dropped) from the real COLD solve. Hashing the side, not the pixel offset, keeps it machine-stable (a quadrant only flips on a genuine re-placement) — same machine-independence as the geometric golden. Magnitude/continuity stays covered by test_loop's warm-start delta bound. - New baseline test/golden/atlas-placement.sha256 (+ .rows.txt). Full suite 295/295. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jowch
left a comment
There was a problem hiding this comment.
Review — round 1
Clean, behavior-preserving refactor plus a well-constructed placement golden. The solve_cluster(_SOLVER, …) → warm_solve(…) migration is exact: warm_solve is a thin pass-through to the same solve_cluster(ProjectionSolver(params), anchors, sizes, bounds; init_state, pin_mask, pinned_offsets, obstacles) seam, and place.jl passes its solver params (box_padding=5.0, point_padding=5.5, min_segment_length=2.0, only_move=:both) explicitly, so it's insulated from any drift in warm_solve's own defaults. The new golden hashes discrete side/dropped decisions (sign-quadrant + Int(dropped)), not pixels — deterministic and meaningful. I independently reproduced the committed 475dfc52… sha256 from atlas-placement.rows.txt and ran the full atlas suite (295/295 pass) against the committed sha. Measure-once/layout-many and the no-kerning rule are untouched. No blocking findings.
Nits
examples/atlas/src/golden.jl:115—placement_rows' determinism silently depends on the mutable global_PIN_SLO[](render.jl:106) stayingtrue. Nothing in the test path flips it, but passing the pin flag explicitly (or a one-line comment noting the dependency) would harden the golden against a future test that mutates the Ref.examples/atlas/src/golden.jl:14-17/examples/atlas/test/test_golden.jl:43-45— the "machine-stable" framing leans on the same "needs no Makie" argument as the geometricatlas_digest, butplacement_rowsroutes anchors through the live Makie camera (assemble_frame→_data_to_px→Makie.project). The digest is robust to sub-pixel projection drift, not independent of Makie — a label near a quadrant boundary could flip under a projection change. Wording should say "robust to sub-pixel projection drift" rather than implying full Makie-independence.examples/atlas/README.md:28,55,68— still referencesolve_cluster; now contradictsplace.jl's public-warm_solvereality. Minor doc drift (the archivalprep/spike_timing.jlkeeping the old name is fine).
- golden.jl: harden placement_rows against the mutable _PIN_SLO[] global it depends on — explicitly pin it to the production value (true) so a future test mutating the Ref can't perturb the golden. No-op on the digest (it was already true; baseline unchanged). - golden.jl + test_golden.jl: fix the "machine-stable / machine-independent like the geometric golden" overclaim. placement_rows DOES route anchors through Makie's projection (via assemble_frame), unlike the fully-affine geometric table — so the accurate claim is "robust to sub-pixel projection drift" (a quadrant only flips on a real re-placement), not Makie- independent. Reworded the header, docstrings, and the testset name. - README.md: solve_cluster → warm_solve at the three remaining mentions (now matches place.jl). Atlas suite 295/295; placement digest unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Round 1 — addressing the nitsPushed Fixed
|
jowch
left a comment
There was a problem hiding this comment.
Review — round 2
All three round-1 nits addressed in 0f33077, no regressions. Scoped post-approval re-review of the incremental diff (golden.jl, test_golden.jl, README.md).
The only functional change is _PIN_SLO[] = true at the top of placement_rows (golden.jl:113). I verified it's inert: _PIN_SLO defaults to Ref(true) (render.jl:106), golden.jl:113 is its only writer (and writes true), and it's read only at render.jl:581 — so there's no ordering hazard and the digest is unchanged. The golden data files (atlas-placement.rows.txt/.sha256) are untouched in this diff, so the 475dfc52… digest already verified in round 1 still holds by construction. The remaining changes are comment/docstring/testset-name wording, now correctly stating "robust to sub-pixel projection drift, not Makie-independent."
Resolved since last round
_PIN_SLO[]dependency — now pinned explicitly inplacement_rows; confirmed inert (sole writer, default alreadytrue). Fixed.- "machine-stable" overclaim — corrected throughout
golden.jlheader/docstrings andtest_golden.jlto "robust to sub-pixel projection drift," with the Makie-routing caveat made explicit. Fixed. - README
solve_clusterdrift — lines 28/55/68 updated towarm_solve; archivalprep/spike_timing.jlleft as-is per the note. Fixed.
|
Converged at round 2 — zero blocking throughout; the three round-1 nits (the _PIN_SLO hardening, the "robust to sub-pixel projection drift" wording correction, and the README solve_cluster→warm_solve drift) are fixed and re-approved. Migration is verified placement-preserving (byte-identical hero, unchanged geometric golden); the new placement golden holds at 475dfc52…; suite 295/295. No further changes intended. |
The greenfield gallery has landed (#30/#32/#33/#34/#35) and the engine is stable, so the v0.1.0 deferral is lifted. - CHANGELOG: promote [Unreleased] → [0.1.0] (2026-06-18), drop the stale "deferred until #30" note, fix the Atlas line to public `warm_solve` (post-#35), fix link refs. - Add TagBot workflow (canonical JuliaRegistries example, v1.25.8 pinned by SHA; no explicit permissions block per current TagBot guidance) so the v0.1.0 git tag + GitHub release are cut automatically once the General-registry version merges. Not adding CompatHelper: it's in maintenance mode and .github/dependabot.yml already covers the Julia ecosystem (root + /test) plus github-actions. Registration readiness (verified): [compat] is complete (Unicode + the three weakdeps + julia, all upper-bounded), top-level MIT LICENSE, src/TextMeasure.jl, green test suite, and the name "TextMeasure" is free in General. The registration trigger itself is a manual step. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What
Now that MakieTextRepel shipped the public warm-start primitive we filed #24 for (PR #27, exported
warm_solve), this does two things:Migration (placement-preserving)
Atlas.jl:using MakieTextRepel: warm_solve(wasProjectionSolver, RepelParams, solve_cluster).place.jl: drop the_PARAMS/_SOLVERobjects; callwarm_solve(anchors, sizes, bounds; …, _SOLVE_KW...)with identical params.warm_solveis a thin face over the same seam.Project.toml: pin rev →90f87ed3(past #G — CairoMakie Map Feature Page (examples/map_feature/) #27/#I — README hero, gallery index, CHANGELOG, release hygiene #28). Stalesolve_clustermentions in comments updated towarm_solve.Verified placement-preserving: a byte-identical hero re-render (same sha256, 241,054 bytes) plus the unchanged geometric golden. So the public API produces exactly the same placement — zero behavioral change.
Placement golden (machine-stable)
The existing geometric golden deliberately avoids solver offsets because they flow through Makie's projection, which isn't bit-stable across versions. So instead of hashing raw offsets,
placement_rows/atlas_placement_digesthash the solver's discrete decisions — which side each label leans (offset quadrant) + dropped — from the real cold solve:test_loop.jl's warm-start delta bound (<25px/frame).test/golden/atlas-placement.sha256(+.rows.txt),UPDATE_GOLDEN=1to regenerate.Tests
Full suite 295/295. (Manifest stays gitignored; CI's clean instantiate fetches the new rev.)
Notes
measure_labelsexport) don't affect the Atlas — it useswarm_solvedirectly and measures via TextMeasure. With #H — CairoMakie "Newer Yorker" correctness exhibit (examples/cover/) #24 resolved, the Atlas now depends only on public MakieTextRepel API.🤖 Generated with Claude Code