Skip to content

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:

  1. The MIP's test harness does not exist. §5 says scripts/site_check.js is "the stub-DOM harness written for MIP-0008 task 6, checked in", checked 2026-09-06: neither scripts/site_check.js nor site/fixtures/ is in the repo, and just quality-other has no Node step. Task 2 creates it, and it precedes the JavaScript tasks so they have a failing assertion to turn green. ci.yml's quality-other job enumerates its steps (it does not run just quality-other) and its changes filter has no site/** group; task 2 adds both, or the harness would never run in CI.
  2. wind_level is additive and optional. hours/items has additionalProperties: false, so emitting the field without the schema change breaks BoardSpec's conformance test, so schema, Board and the specs are one task by necessity, not convenience. required is untouched so an already-published board stays valid (MIP §5: "an old board still renders, without the band").
  3. WindLevel lives in model/Models.scala beside JellyfishRisk/WhaleSightingLikelihood (derives CanEqual, strictEquality is on); windLevel returns Option[WindLevel]: the MIP's "plus None when the forecast lacks wind" is an Option, not a fourth case, so windDelta's "no wind data" branch stays distinct from a real band.
  4. 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.
  5. 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.