MIP-0009 tasks¶
Ordered delivery of MIP-0009 as stacked PRs
(.claude/skills/mip-tasks/SKILL.md, scripts/stack.sh). Branch mip-0009/<k>-<slug>; merge
bottom-up, scripts/stack.sh restack after each squash-merge.
Four tasks, one chain (k based on k-1): the Scala band first because it is the only design
risk (the map's wind word must be Swimability's own threshold, not a copied number), then the
test harness the JavaScript needs before the JavaScript changes, so tasks 3-4 start from a red
assertion rather than a screenshot.
| # | slug | delivers | tests (must exist before the PR) | depends on |
|---|---|---|---|---|
| 1 | wind-level | enum WindLevel { Calm, Breezy, Strong } in core/.../model/Models.scala (next to JellyfishRisk); Swimability.windLevel(kmh: Option[Double]): Option[WindLevel] on the existing private CalmWindKmh/StrongWindKmh, with windDelta refactored to call it (one threshold); Board.hourJson emits "wind_level": "calm"\|"breezy"\|"strong"\|null; site/board.schema.json hours/items gains optional wind_level (enum + null, not added to required; additionalProperties: false is why schema, Board and specs must land together); schema version stays 1. Also carries this tasks file, the MIP's Tasks: row and its Status → Accepted |
SwimabilitySpec: below-calm / between / at-or-above-strong bands, plus windDelta's note text agrees with the band ("breezy" ⇔ Breezy, "strong wind" ⇔ Strong, no note ⇔ Calm); BoardSpec: every hours[] entry carries wind_level consistent with its wind_kmh, the schema validator accepts it, and still accepts a board without it |
– |
| 2 | site-check-harness | scripts/site_check.js (Node, stdlib only, just quality-other + ci.yml): a stub-DOM + stub-L harness that loads site/static/app.js against site/fixtures/board.json (a two-beach board committed as a fixture, one beach with unfit water, hours with and without wind_level), validates the fixture against site/board.schema.json with a minimal in-file checker (type/required/enum/additionalProperties, the subset the schema uses; no library, same stance as BoardSpec), and asserts today's behaviour as the baseline: one marker per beach, tooltip text, card renders. just quality-other runs it; ci.yml gains a site change filter (site/**, scripts/site_check.js) and a step for it; the quality-other job lists its steps explicitly, it does not call just quality-other |
The harness itself, green against the unchanged app.js; node --check site/static/app.js; just quality green |
1 (the fixture carries wind_level) |
| 3 | wave-marker-tooltip | waveIcon(colour, selected) → L.divIcon with one inline SVG wave path (own artwork), fill = score colour, white stroke; render() uses L.marker instead of L.circleMarker, selected wave larger and on top; aspectsHtml(beach, shown): the six-cell grid (wind band+emoji+km/h+dir, water temp, waves+period, jellyfish, whales with "best HH:MM" when it differs, water verdict red when unfit), every emoji followed by its word; bindTooltip(html, { sticky, direction: 'top', className: 'aspects' }); style.css .wave, .leaflet-tooltip.aspects grid; legend line "🌊 a beach, hover or tap" in index.html; a board without wind_level renders without the band word |
scripts/site_check.js: exactly one .wave marker per beach; tooltip HTML holds the six aspects with the fixture's numbers; unfit beach's water cell carries the unfit class; a board without wind_level still renders; node --check |
2 |
| 4 | card-aspect-row | The same aspectsHtml block prepended to renderCard() (touch has no hover; the card is the parity surface); card CSS for the row; docs: docs/2-Building-marola/ARCHITECTURE.md/RUN-LOCALLY.md mention of the hover aspects where the map is described |
scripts/site_check.js: the card's first block equals the tooltip's aspect row for the same beach/hour; manual (§7): just site-build floripa && just site-serve: hover Joaquina on desktop, tap on a phone (Safari + Chrome), zoom to the whole area and check legibility; screenshots in the PR body; "Done" = the live map after merge shows waves + hover aspects, footer panel and list unchanged |
3 |
Decisions taken so the tasks above are unambiguous:
- The MIP's test harness does not exist. §5 says
scripts/site_check.jsis "the stub-DOM harness written for MIP-0008 task 6, checked in", checked 2026-09-06: neitherscripts/site_check.jsnorsite/fixtures/is in the repo, andjust quality-otherhas no Node step. Task 2 creates it, and it precedes the JavaScript tasks so they have a failing assertion to turn green. ci.yml'squality-otherjob enumerates its steps (it does not runjust quality-other) and itschangesfilter has nosite/**group; task 2 adds both, or the harness would never run in CI. wind_levelis additive and optional.hours/itemshasadditionalProperties: false, so emitting the field without the schema change breaksBoardSpec's conformance test, so schema,Boardand the specs are one task by necessity, not convenience.requiredis untouched so an already-published board stays valid (MIP §5: "an old board still renders, without the band").WindLevellives inmodel/Models.scalabesideJellyfishRisk/WhaleSightingLikelihood(derives CanEqual,strictEqualityis on);windLevelreturnsOption[WindLevel]: the MIP's "plusNonewhen the forecast lacks wind" is anOption, not a fourth case, sowindDelta's "no wind data" branch stays distinct from a real band.- Open questions closed as proposed in §11: OQ1 two thresholds, no "gale" band (map and CLI agree); OQ2 the hour's whales value plus "best HH:MM" when it differs; OQ4 one wave glyph in v1. OQ3 (the same row as the first line of the Telegram reply) stays with MIP-0002.
- Task 4 owns the manual check. §7's desktop-hover / phone-tap / area-zoom check and its screenshots go in the last PR's body, once both the tooltip and the card row exist; earlier PRs are judged by the harness alone.
Restack notes: task 2 creates site/fixtures/board.json and scripts/site_check.js; tasks 3 and
4 extend the harness's assertions and edit app.js/style.css in sequence (4 after 3), so no
task rewrites a file an earlier task created, so restacks after each squash-merge should be
mechanical. Task 1 is the only one touching Scala; tasks 2-4 are JavaScript/CSS/HTML/workflow
only and never trigger quality-scala on push.