Current coordination: WORKING-AGREEMENT.md. This dated review is preserved as historical evidence. Before merging, verify whether the changed paths trigger publication and whether jurisdiction approval applies. Its split sequence does not resume paused PR #80. Follow the living handoff for current scope and decisions.
Reference for HANDOFF-2026-10-18.md. Snapshot as written by HM Front on 2026-10-18 (SHAs as of that review: #71 head 1d74db5, main b524f3e); not a living doc.
Read-only review. Nothing was pushed, commented, merged or changed in Linear.
Sources: local clone of niagaraassembly/heavymap with all PR heads fetched (gh, authed as peers8862), gh api for Pages, Linear (read), Railway (read).
| Item | Value |
|---|---|
| PR | #71 “Draft: Hamilton v0 frontend spec and synthetic local prototype”, draft, open |
| Head | codex/heavymap-v0-frontend @ 1d74db5 |
| Merge base | d1afda4 (main after PR #75) |
| Current main | b524f3e (PR #79, 2026-10-18) |
| Ahead / behind | 5 commits ahead, 6 behind (#76, #77, #78, #79 landed since the sync) |
| Mergeable | No. GitHub reports CONFLICTING / DIRTY. “Synced with main at 1d74db5” in the brief is out of date: 1d74db5 is the head commit, and it was synced to d1afda4 |
Conflicts (git merge-tree) |
Spreadsheets/00-PLANNING-INDEX.csv, Spreadsheets/19-process-insights.csv (both append-only, easy), Spreadsheets/02-dataset-inventory-v4.xlsx (binary) |
| Size | 33 files, +10,237 lines. web/ 19 files, mockups 10 files (~740 KB of PNG), V0-SPEC.md, PRIOR-ART-GLEAN.md note, 2 sheet CSVs, 2 xlsx |
| Local checks I ran on the head | npm ci ok; npm test 3/3 pass; vite build ok. Playwright was not run (no browser run on the box) |
| Measured build (gzip -9) | index.html 1.5 KB, CSS 13.3 KB, main JS 298.8 KB, MapLibre worker 145.5 KB, PMTiles 0.5 KB → ~459 KB compressed first load |
| PI ids | PI-0070..0077 match the reservation. No clash with main (main has 0065–0069 and 0078–0090; PR #80 starts at 0091) |
heavymap-planning/frontend/V0-SPEC.md: Morgen’s vision quoted in full, layouts A/B/C, six-band dossier anatomy, refusal copy, area shares (now flag-never-hide), lens model, OSM/Overpass options, basemap options, budgets, 8 questions for Morgen.web/: Vite 7 + MapLibre 6.14 + PMTiles 4.3. fixtures.js (3 invented parcels, inline six-band model), dossier.js (textContent-only renderer + JSON export), map.js (MapLibre, pmtiles protocol, conditional camera restore), app.js (lens/search/route). 768-byte synthetic PMTiles in web/public/. Node contract tests + 5 Playwright Chromium scenarios.mockups/: 3 Excalidraw scenes + PNG + SVG.Overall: good craft and the right instincts (refusals visible, no vacancy assertion, textContent-only, failed fetch ≠ empty evidence, Range verified locally, no external calls, no deploy script). It is a synthetic interaction prototype. It does not consume the dossier v1 contract yet, so most contract items are backlog, not merge blockers.
Land before the split (option E) after a short fix list. Do not merge as-is. Two items block. They are the merge conflict and the fact that a merge to main publishes to heavymap.com today. A few cheap doc and fixture fixes should ride along, so that #71 doesn’t become stale front-end canon the moment the split rewrites the tree.
| # | Item | Where | Fix |
|---|---|---|---|
| B1 | Merge conflicts with main | Spreadsheets/00-PLANNING-INDEX.csv, Spreadsheets/19-process-insights.csv, Spreadsheets/02-dataset-inventory-v4.xlsx |
Merge main again. Keep main’s rows and append #71’s (PI-0070..0077 unchanged). Take main’s 02-…xlsx (main added publish_visibility to the CSV and rebuilt the xlsx). Also drop #71’s change to Spreadsheets/09-per-jurisdiction-dataset-catalog.xlsx: it has the same byte size and #71 changes no CSV behind it, so it is a spurious rebuild (xlsx is rebuilt from CSV, never hand-edited) |
| B2 | Merging = public publish today. GitHub Pages is the legacy branch build from main / (gh api …/pages: build_type: legacy, source: main /, last build at b524f3e 2026-10-18 10:27 EDT). The whole tree is live, e.g. https://heavymap.com/Spreadsheets/20-data-gaps-ledger.csv → 200. Merging #71 would serve web/ source, the synthetic PMTiles and the mockup PNGs at heavymap.com/web/… with no sign-off. The page would also be broken, because index.html loads /src/app.js, a Vite source path. This is the HARD-RULES 3 / NA-269 concern in practice, even though #71 adds no workflow |
repo Pages setting, plus optionally a root _config.yml |
Needs Morgen’s decision D1 before the merge. My recommendation: add a root _config.yml exclude: for web/ and heavymap-planning/frontend/mockups/, landed in #71 or just before it. Then switch Pages to “GitHub Actions” when the gated deploy workflow lands |
| # | Item | Where | Fix |
|---|---|---|---|
| S1 | Budgets contradict the accepted beta-gate budgets (BETA-GATE §6, 2026-10-18: startup ≤ 50 KB compressed, shard ≤ 50 KB compressed p95, each .pmtiles ≤ 100 MB, site ≤ 1 GB) | V0-SPEC.md l.9, l.112 (“<300MB”), l.116 (“≤1.5MB compressed”), l.121 (“≤250KB” per response, 40 MB dossier allocation) | Cite §6 as the accepted ceiling. Keep the finer allocations only as planning targets. Mark “what counts as startup payload” as open (decision D2). The measured build is ~459 KB compressed |
| S2 | Gate terminology out of date: “G6 remains closed”, “no deployment tooling” | V0-SPEC.md l.21, l.143, l.145; web/README.md l.5 & l.51; web/VALIDATION.md l.3 | Replace with “Hamilton beta gate (NA-269), Stage B not signed off”. Note that HARD-RULES 3 now allows building and testing deploy tooling. Publishing still needs sign-off |
| S3 | Tax-class fixture contradicts what Hamilton actually has. NPCA has no tax class (GAP-0001). PR #75 moved to neutral wording, and the dossier ships MPAC property code + family range (conf low, stale 2020, GAP-0002/0003). The vacant lens does not depend on tax class | web/src/fixtures.js l.120 ("Tax class confirmed"), l.213, l.269; V0-SPEC l.7, l.68 |
Model the fixture as Property code (MPAC, 2020 snapshot): "590" · 5xx Industrial range with its stale/sparse gap labels. Leave V0-SPEC Q5 (tax-class wording) open for a future tax-class source |
| S4 | The tile key name differs from the contract and from the pending rename. The fixture tile property is id, but the PR #80 §4.1 tile contract names heavymap_id |
web/scripts/make-fixture.mjs l.33; web/src/map.js l.265, l.384; fixtures.js featureCollection |
Put one exported constant (ID_FIELD = "heavymap_id") and the id prefix in one place. Regenerate the fixture. The pending Industrial Atlas rename (package / hm: prefix / heavymap_id) then becomes a one-line change |
| S5 | Share unit mismatch with the v1 record. Fixtures use percent 0–100, while the record share is a fraction 0–1 (4 dp). share.toFixed(1)% would render slivers as “0.0%” |
web/src/dossier.js l.47–55 |
Note in V0-SPEC “Area shares”: record shares are fractions. Render share==0 && m2>0 as “below 0.01%” and < 0.001 as “below 0.1%”, never “0.0%” |
shards/{id.slice(-2)}.json fetch, dedupe or cache. The renderer reads an inline six-band model with per-claim stamp, not the v1 record (area/coverage/roll/addresses/overlays/gaps, src/join/conf/label). V0-SPEC l.45 says the bands are a display of the record, but there is no adapter. → backlog HM-03.dossier.js l.64–69 renders every key in claim.stamp, and packet() (fixtures.js l.400–411) exports structuredClone(parcel.bands) whole. That is fine for fixtures, but with real data the UI needs its own display/export allowlist plus a leak scan of dist/ (defence in depth behind the IA allowlist). → HM-04.fixtures.js l.413–418; contract.test.js l.32–35). None of the 9 gap types are present, there are no gaps_ledger lookups, and there is no placeholder-ARN case (coverage.status = arn_invalid, value not shown). → HM-05.publish_visibility states. → HM-06.playwright.config.js), and the fixture PMTiles is uncompressed, so the Pages range_gzip / Firefox problem (PMTiles #584) is untested. → HM-08…HM-11, HM-15.map.js l.114 attributionControl: false. That is fine with no external tiles, but required before any OSM/Stadia/Protomaps basemap. → NA-37.| Check | Result |
|---|---|
| Build outputs committed? | No. web/.gitignore ignores dist/, node_modules/ and the test reports |
| Data committed? | No real data. 768-byte synthetic PMTiles plus inline invented GeoJSON. Mockup PNGs (~740 KB) are design assets, not data |
| Owner names / mailing / Legal_Txt shown or shipped? | None. All rendering is via textContent |
| Deploy workflow? | None. No .github/workflows on main or #71. vite.config.js has no deploy config. But see B2: the legacy Pages build publishes on merge anyway |
| Railway | Service heavymap (project heavy-map-v1-railway) auto-deploys main. Latest deploy FAILED 2026-10-18 10:26 EDT. No domain, so not public, but it rebuilds on every merge (decision D6) |
| Paths the split breaks (fix in the split’s HM delete/rewrite commit, not in #71) | V0-SPEC relative links to docs that move to IA: l.23 (claim-refusal-contract.md, context-band-ladder.md, parcel-dossier.md, ../focus/hamilton/* ×4, first-attempts/niagara-atlas/README.md), l.45, l.64, l.124 (parcel-dossier.md). PCDP.md and PRIOR-ART-GLEAN.md stay. web/README.md l.47–48 tells readers to run root python3 -m unittest / scripts/check_index_paths.py, which both move to IA. #71’s 00-PLANNING-INDEX.csv rows (V0-SPEC, mockups) sit in Spreadsheets/ → inventory/ on IA |
| Pending naming switch | Only S4 above (hm:ca:on:hamilton:parcel:SYN-00n at fixtures.js l.89, l.183, l.242; tile key id). There are no Python imports or heavymap.* module paths in web/ |
| Pin to a screened IA release | Not applicable yet. #71 has no artifact consumption. → HM-14 |
| Principle | #71 |
|---|---|
| Transparent view mode | Partial: refusals visible, “loading/failed ≠ refusal” ✓; no gap types |
| Withheld data shown as applying-but-held | ✗ |
| Signal held identifying info | ✗ (copy belongs to purchase funnel) |
| No load drag / budgets | Data side ✓ in spirit (lazy shards specified); first load ~459 KB compressed vs 50 KB accepted startup budget (D2) |
| Experience profiles NA-310 | ✗ (palette switch is the only style-swap primitive) |
| Browser notice NA-305 | ✗ |
| Address-no-parcel list NA-309 | ✗ |
| Run records NA-308 | ✗ |
| Slivers flag-never-hide | Spec ✓, fixtures don’t exercise it |
| No vacancy assertion / source-quoted labels | ✓ |
| OSM deferred (NA-304) | ✓ (no live calls; options documented) |