Clear the fallow gate on native-product-pages - #1184
Open
busbyk wants to merge 6 commits into
Open
Conversation
The danger map's data is a Product in the ADR-018 sense, so it goes through a source adapter like forecast and warning already do. Issue 02 scoped the adapter to those two; this adds the third. - `model/mapLayer.ts` — the normalized zone model consumers depend on, never a v2 or v3 response. Features carry a top-level `id` (the numeric zone id), which `mapLayerFeatureSchema` had been dropping; Mapbox's `setFeatureState` keys on it. - `mapLayerSourceV2.ts` + mappers, wired through `sources/config.ts` so v3 is reachable by the Control 2 env flag. Defaults to v2, matching every other product. - `getMapLayer` takes an optional `day`, which the NAC API has always supported and we never passed. Cheap now, expensive to retrofit onto issue 09's date picker. - `getForecastZoneDanger` moves from `nac.ts` to `dangerMap/mapLayer.ts` alongside it; the OG route follows the import. - `zoneSlug.ts` resolves a zone's upstream link to its AvyWeb path, so the popup, the zone list and the click handler can't disagree about where a zone goes. Also drops four `z.infer` aliases and one schema with no consumers anywhere. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Everything the map draws is decided here, in pure functions, and tested — the map component only renders the result. The rules are where the bugs are: off-season outranking a stale rating, an unrated zone ignoring the server's grey, naive timestamps that are really UTC. Parity target is the afp danger-map widget, not avy. NAC has built this twice and the two disagree — off-season and no-rating colors are *swapped* between them, opacity and popup copy differ. AvyWeb sits on the same sites as these embeds, so matching the embed is what a reader actually notices. `dangerMapSettings` reads the full `widget_config.danger_map` contract that dashboard-v2 writes, rather than a subset: every tenant has set `saturation`, SAC has `search: false`, and `allCenters` is present on SNFAC/SAC but absent on NWAC. Two departures from the stored config, both recorded in the parity ledger: saturation is not applied (no Mapbox consumer in the NAC stack applies it, and the shared style is already near-grayscale), and the configured zoom is used minus one, because those values were authored against the Google Maps build and Mapbox's 512px tiles render the same number a level tighter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The widget titles the legend "Avalanche Danger Scale" and makes that heading the
disclosure trigger. We had a "Danger Scale" heading plus a separate "Danger scale
definitions" toggle underneath — extra wording the widget doesn't have, and a
second control competing for the same job.
The heading and the color strip both move inside `<summary>` so the strip stays
visible when the definitions are collapsed. Each scale row gains an abbreviation
("Mod", "Cons", "Extr") for the narrow-screen strip, where the full names collide —
the same shortening the legacy legend does.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Establishes Mapbox in web — this is the repo's first map dependency — as shared
infrastructure rather than danger-map-local, so issue 16's station map reuses it.
Style is the `avalanche-org` "AFP Custom" style already shared by afp-public-widgets
and dashboard-v2; the token is NAC's public `pk.` token, which both of those
hardcode as a fallback.
Rendering knowingly diverges from both existing NAC implementations. avy renders
one ShapeSource per zone and the widget does the same with `zone-source-${index}`;
this uses a single `geojson` source with data-driven paint, so hover and the
warning flash are `setFeatureState` + `feature-state` expressions rather than
`setPaintProperty` across per-zone layer ids. Zones arrive pre-styled from
`decorateZoneFeatures`, so the paint expressions just read computed properties and
there is no second copy of the precedence rules to drift.
The map is a client component that fetches on mount through `/api/[center]/danger-map`,
not props from the statically generated home page. The host page renders on a
one-hour window and a map painted from hour-old ratings is exactly the staleness the
widget avoided by refetching every page load. Routing the fetch through our own
endpoint keeps the adapter, the zod validation and the styling rules server-side.
The warning flash is rebuilt on requestAnimationFrame at the widget's ~20fps rather
than its 50ms setInterval, so it pauses with the tab instead of burning a timer.
A Mapbox map is a `<canvas>`, so `ZoneList` renders the same zones as a visually
hidden list of links — otherwise today's avalanche danger would be unreachable by
keyboard or screen reader.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds `dangerMap` to the native product flags and swaps the legacy danger-map widget for the native one on the home page. Ships dark: the flag is false for every tenant, so untick and the widget comes back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`fallow` has been the only red CI job on the integration PR (#1130), and it is wired into the pre-commit hook, so it has been blocking every commit on this branch — 05a and 05b both went in with --no-verify. This is remediation of pre-existing debt across issues 01-05, not a fix for any single issue. Both gates now exit 0, and the audit reports no issues in 166 changed files. Tell the tool about deliberate public surfaces `model/forecast.ts` exists so the model is the single import surface for consumers, so its re-exports have no consumers of their own and read as dead, transitively taking their sources in `types/forecastSchemas.ts` with them. Both are declared in `ignoreExports`, the same treatment `components/ui/**` already gets. This clears 23 of 39 dead-code findings on its own. ADR 018 was cited by `model/forecast.ts` but had never actually been written; it is written here, since it is the justification for that config. Delete what was genuinely dead `getUseNativeForecasts.ts` (every call site had migrated to `getNativeProductFlag`), the unconsumed `getProductDataSource` re-export in `sources/index.ts`, and the `ProductListItem` alias. Suppress the z.nativeEnum false positives Members of `AvalancheProblemType`, `ProductStatus` and `ExternalMediaType` reached only through `z.nativeEnum` look unused because zod validates against member *values*. They are load-bearing — dropping `WindSlab` would make zod reject every forecast carrying a wind-slab problem — so they carry per-member suppressions with a docblock, matching `AvalancheCenterType` in schemas.ts. Waive the two dynamic-segment conflicts Same false positive three sibling routes already waive: route groups keep `(payload)/api/[...slug]` and `api/[center]` separate. Bring the 15 gated complexity findings to zero Mostly by extracting the pure decisions out of components into tested modules, which drops the parent's branch count and covers the logic that moved: `mediaItem.ts`, `datePickerNavigation.ts`, `dangerRatingLayout.ts`, `zoneCardDanger.ts`, plus `parseArchiveWindowQuery` in `archiveDates.ts`. The JSX-heavy components (DangerScale, AvalancheProblemCard, the date picker, NativeForecastView) are split into named sections. 63 new tests. `NativeForecastView` and `ZoneForecastCard` each had their own copy of the highest-danger-band lookup; both now use `zoneCardDanger.ts`. Collapse the 6 gated duplication groups `centerRoutePage.ts` holds the per-center route preamble the built-in pages shared verbatim — static params, metadata shape, and the platform guard. `ZoomPanSurface` holds the zoom/pan interaction the Gallery block and the forecast lightbox had duplicated. Not addressed: the 2 circular dependencies through the Payload config barrel. They are `warn`, gate nothing, and are down from 27 at the July baseline. The only fix that actually clears them is injecting `payload` across ~26 call sites — fallow counts a lazy `await import()` as a graph edge too, so that shortcut does not work. Left as-is deliberately. The baselines are untouched: nothing here was accepted as debt. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
|
Preview deployment: https://fallow-remediation.preview.avy-fx.org |
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.
Description
fallowis the only red job on thenative-product-pages→mainintegration PR (#1130), and it is wired into the pre-commit hook, so it has been blocking every commit on this branch — both #1176 and #1183 went in with--no-verify. This clears it.This is remediation of pre-existing debt spread across issues 01–05. It is deliberately not scoped to any one issue, and nothing here was introduced by #1183.
Stacking: this branch is cut from
issue-05b-native-danger-map, so until #1183 merges the diff below includes 05b's commits. It resolves to just the fallow work once #1183 lands. Ordering doesn't matter for CI —ci.yamlonly triggers on PRs targetingmain, so the fallow job never runs on a PR intonative-product-pages; the result shows up on #1130.Before / after
Measured with
fallow audit --changed-since origin/main(166 changed files):fallow dead-code --baselinefallow dupes --baselinefailpassThe baselines are untouched — nothing here was accepted as debt by re-baselining.
Related Issues
Follows the remediation plan in
.scratch/native-product-pages/handoffs/fallow-remediation-handoff.md. Unblocks #1130.Key Changes
Declare the deliberate public surfaces (clears 23 of 39 dead-code findings)
model/forecast.tsexists so the model is the single import surface for consumers, so its re-exports have no consumers of their own and read as dead — transitively taking their sources intypes/forecastSchemas.tswith them. Both are now declared inignoreExports, the same treatmentsrc/components/ui/**already gets.model/forecast.tscited "ADR 018" from the start, but that ADR had never been written —docs/decisions/stopped at 017. It is written here (018-native-product-page-architecture.md), since it is the justification for the config change.Delete what was genuinely dead
src/utilities/getUseNativeForecasts.ts— an entire unused file; every call site had already migrated togetNativeProductFlag.getProductDataSourcere-export insources/index.ts(the symbol is still used inside the file).ProductListItemalias.Suppress the
z.nativeEnumfalse positivesMembers of
AvalancheProblemType,ProductStatusandExternalMediaTypereached only throughz.nativeEnumread as unused because zod validates against member values, not by-name references. They are load-bearing — removingWindSlabwould make zod reject every forecast carrying a wind-slab problem — so each carries a suppression plus a docblock explaining why. Same pattern asAvalancheCenterTypeinschemas.ts.fallow fixoffers "remove this enum member" as an auto-fix for these. Taking it would break validation of real forecast data.Waive the two dynamic-segment conflicts
forecast-archiveandforecast-freshnesshit the same false positive that three sibling routes already waive: fallow reads(payload)/api/[...slug]andapi/[center]as one dynamic path, but route groups keep the trees separate.Complexity: 15 → 0
Mostly by extracting the pure decisions out of components into tested modules — which drops the parent's branch count and covers the logic that moved:
mediaItem.ts— thumbnail/full URLs, YouTube id, caption, and the slide-kind decisiondatePickerNavigation.ts— month windows, link targets, arrow stepping, the archive fetchdangerRatingLayout.ts— day headings and the "no rating" testzoneCardDanger.ts— the highest-danger-band lookupparseArchiveWindowQueryinarchiveDates.ts— the archive endpoint's parameter validationThe JSX-heavy components (
DangerScale,AvalancheProblemCard,ForecastDatePicker,NativeForecastView,ZoneForecastCard,MediaLightbox) are split into named sections.MediaSlidemoved to its own file so it could be tested without standing up the carousel.Two incidental fixes fell out:
NativeForecastViewandZoneForecastCardeach carried their own copy of the highest-danger-band lookup — both now callzoneCardDanger.ts.63 new tests, all behaviour-preserving refactors.
Duplication: 6 → 0
src/utilities/centerRoutePage.ts— the per-center route preamble the built-in pages shared verbatim:generateStaticParams, the metadata shape (title suffix, canonical, OpenGraph, dynamic OG image), theArgs/PathArgstypes, and the platform guard (assertCenterPlatform, which 9 routes hand-rolled).src/components/ZoomPanSurface.tsx— the zoom/pan interaction the Gallery block'sZoomableImageand the forecast lightbox'sZoomablePhotohad duplicated.Not addressed
The 2 circular dependencies through the Payload config barrel. They are rated
warn, gate nothing, and are already down from 27 at the July baseline. The only change that actually clears them is injectingpayloadacross ~26 call sites — I tried a lazyawait import('@payload-config')first and fallow counts that as a graph edge too, so the cheap version doesn't work. Left as-is deliberately rather than churning hook plumbing for a non-gating finding.How to test
All green: 885 tests pass, and this commit went in without
--no-verifyfor the first time on this branch.Behaviourally this should be a no-op. Worth a click-through of the forecast pages, since that's where the refactoring landed: a zone forecast (danger ratings, problems, media lightbox, date picker), a dated archive view, the all-zones grid, and the Gallery block's lightbox.
Migration Explanation
None — no schema or migration changes.
Future enhancements / Questions
weather/forecast/page.tsxusedlimit: 0(unlimited) in itsgenerateStaticParamswhere the other nine routes usedlimit: 1000. The shared helper normalises to1000. With four tenants this changes nothing, but it is a real difference and I'd rather name it than bury it.seed/index.ts:78(CRAP 1722, 1317 lines) andmiddleware.ts:49(1122) are the big ones. They pre-date this branch and are out of scope here.types/schemas.tsthat Native avalanche danger map on Mapbox (issue 05, Part B) #1183 deleted. Harmless, but the baselines are due a refresh once this branch merges — worth doing deliberately rather than as a side effect.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.