Skip to content

CLIM-1291: S2D Disable low skill masking in Climatology mode#666

Open
renoirb wants to merge 12 commits intomainfrom
CLIM-1291_S2D-Map-When-Climatology-Hide-Skill-Layer
Open

CLIM-1291: S2D Disable low skill masking in Climatology mode#666
renoirb wants to merge 12 commits intomainfrom
CLIM-1291_S2D-Map-When-Climatology-Hide-Skill-Layer

Conversation

@renoirb
Copy link
Contributor

@renoirb renoirb commented Mar 16, 2026

Description

Disable and visually mute the "Mask Low Skill" checkbox and hide the low skill map layer when Forecast Display is set to Climatology. The "low skill" concept (forecast confidence) is only meaningful for forecast data — it has no
relevance to historical climatology.

  • Checkbox (MaskLowSkillField): Disabled via Radix disabled prop when forecastDisplay !== 'forecast'. Label muted via Tailwind peer-disabled:opacity-50. Checked+disabled state swaps red to grey. State in Redux store
    and URL (&maskLowSkill=) is never mutated — preserved automatically.
  • Map layer (LowSkillLayer): Early-return null from useMemo when not Forecast. WMS tiles are not fetched, Leaflet pane stays empty.

No changes to Redux store, URL sync, map-container.tsx, sidebar-inner-s2d.tsx, or checkbox.tsx.

Changes

File What
apps/src/components/fields/skill.tsx useClimateVariable() for isForecast via climateVariable?.getForecastDisplay(); disabled on checkbox; peer-disabled: on label; disabled:data-[state=checked]: grey override
apps/src/components/map-layers/low-skill-layer.tsx climateVariable?.getForecastDisplay() for isForecast; shouldHideLayer/hasLayerData named booleans; isForecast guard in useMemo early-return
apps/src/features/map/map-slice.ts JSDoc on selectLowSkillVisibility — clarifies "mask" = vector overlay semantics
apps/src/types/types.ts JSDoc on MapState.isLowSkillVisible — clarifies "mask" = vector overlay, not "hide" we often jump to as native French speakers
apps/src/lib/s2d.ts JSDoc on buildSkillLayerName — meteorology context

Test plan

  • Open map with S2D variable (?var=s2d_air_temp&dataset=260)
  • Initial load: checkbox should be enabled (not disabled) when Forecast Display defaults to Forecast
  • Set Forecast Display to Climatology → checkbox is disabled/muted (grey, not red), no diagonal line overlay on map
  • Set Forecast Display to Forecast → checkbox is active, diagonal line overlay appears (if checkbox checked)
  • Toggle between modes — checkbox state preserved (no store/URL mutation)
  • Verify &maskLowSkill= URL param unchanged when switching Forecast Display

Related ticket

  • CLIM-1291

@renoirb renoirb marked this pull request as draft March 16, 2026 17:46
@renoirb renoirb self-assigned this Mar 16, 2026
@renoirb renoirb marked this pull request as ready for review March 16, 2026 18:30
@renoirb
Copy link
Contributor Author

renoirb commented Mar 16, 2026

2026-03-16 Screenshots

2026-03-16 Screenshot Forecasts

Notice the “Forecast Display” and the check box red we can click and when we change to Climatology.

CLIM-1291_Snapshot_2026-03-16_14-51-56

2026-03-16 Screenshot Climatology

It becomes:

  • Unavailable (we can’t change its value)
  • Toned down in color
CLIM-1291_Snapshot_2026-03-16_14-52-04

Copy link
Collaborator

@ChaamC ChaamC left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like when I open the Map and an S2D variable, the Mask Low Skill button is disabled at first, even if Forecast is selected in the Forecast Display.

If I change to Climatology and then Forecast, the button seems to be enabled then and to work as intended for the feature of this PR.

Can you check if you also have that behavior and see if it can be fixed, so that the Mask Low Skill is not disabled when opening the Map on a Forecast variable?

@renoirb
Copy link
Contributor Author

renoirb commented Mar 17, 2026

I was thinking that the button is disabled in any situation when it isn't exactly ForecastDisplay === 'forecast'.

I was thinking to have that button disabled by default, yes, but not to be in a loaded state, be with forecast (as the initial state), and remain disabled.

This feels like it's some race condition

renoirb and others added 10 commits March 19, 2026 14:24
undefined is a transient React re-render state, not a valid
forecastDisplay value. Only positively match ForecastDisplays.FORECAST.
Swap brand-red background/border to neutral-grey-medium when
disabled + checked, so the checkbox is visually obviously inactive.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The word "mask" in "Mask Low Skill" refers to a vector graphics overlay
(diagonal lines over low-confidence forecast regions), not "to hide"
(French: masquer). This distinction caused confusion during development
and risked incorrect conditional logic. Added JSDoc with @see links to
MaskLowSkillField at the three authoritative locations: MapState type
definition, selectLowSkillVisibility selector, and buildSkillLayerName.

Co-Authored-By: Claude Opus 4.6
At initial load, state.climateVariable.data.forecastDisplay is undefined
because URL-sync only sets it when the fcastDisp param is explicitly
present. When absent (the default Forecast case), the Redux field stays
undefined while ClimateVariableBase.getForecastDisplay() silently falls
back to ForecastDisplays.FORECAST. This caused the Mask Low Skill
checkbox to render disabled even when Forecast Display is Forecast —
a race condition where the component saw undefined before data loaded
and never recovered because the value was never written to the store.

selectForecastDisplay mirrors the fallback from
ClimateVariableInterface.getForecastDisplay so both access paths agree.
Co-located in map-slice alongside selectLowSkillVisibility because its
consumers already import from there.
@renoirb renoirb force-pushed the CLIM-1291_S2D-Map-When-Climatology-Hide-Skill-Layer branch from e0267ed to ea48021 Compare March 19, 2026 18:24
@renoirb
Copy link
Contributor Author

renoirb commented Mar 19, 2026

Follow-up from @ChaamC earlier review comment.

Race condition fix

The initial implementation read forecastDisplay from Redux via direct selector (state.climateVariable.data?.forecastDisplay). At page load, this value is undefined — not because the data hasn't loaded yet, but because
URL-sync only writes forecastDisplay to the store when the URL param fcastDisp= is explicitly present. For the default Forecast case, the param is omitted (it equals the default config, so addClimateVariableParamsToUrl
skips it). Meanwhile, ClimateVariableBase.getForecastDisplay() silently falls back to ForecastDisplays.FORECAST, so other components behave correctly.

The result: the checkbox rendered as disabled on initial load even when Forecast Display was Forecast, and never recovered because the Redux value was never written.

Fix: centralized selectForecastDisplay() selector in map-slice.ts that mirrors the ?? ForecastDisplays.FORECAST fallback from the class method. Both skill.tsx and low-skill-layer.tsx now use it.

@xfrenette-crim
Copy link
Collaborator

@ChaamC Before approving, I just want to validate the race condition issue, see if there is a simpler solution

Copy link
Collaborator

@ChaamC ChaamC left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks all good to me and works.
Left a minor comment, but I approve the PR.

/**
* Create and return the GeoServer layer name for the Skill layer.
*
* "Skill" in meteorology refers to forecast accuracy relative to a baseline
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I wouldn't include this first sentence of the description. Feels like overexplaining the concept of skill, which is already documented for example in our Confluence.

Copy link
Contributor Author

@renoirb renoirb Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well. I was keeping bumping about what "skill" actually mean. That's why I wanted to clarify. The code has "forecast" and "climatology". With explanation about a "baseline" and that that base is... climatology. It makes sense.

@renoirb
Copy link
Contributor Author

renoirb commented Mar 20, 2026

Follow-up from my own earlier response.

Race condition fix

The initial implementation read forecastDisplay from Redux via direct selector (state.climateVariable.data?.forecastDisplay). At page load, this value is undefined — not because the data hasn't loaded yet, but because URL-sync only writes forecastDisplay (...)

The "race condition" was caused by using a pattern that could have been because I wanted to avoid using climateVariable.getForecastDisplay() but that attempt to avoid "load more code" caused that race condition.

const isForecast = forecastDisplay === ForecastDisplays.FORECAST;
const { releaseDate } = useS2D();
const isLowSkillMasked = !useAppSelector(selectLowSkillVisibility());
const isLowSkillMaskHidden = !useAppSelector(selectLowSkillVisibility());
Copy link
Contributor Author

@renoirb renoirb Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's the opposite of the value, let's make sure we mean "hidden" or "not visible"

Copy link
Contributor Author

@renoirb renoirb Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion, if the outcome of selectLowSkillVisibility is actually the value of isLowSkillVisible from the type MapState. And that we're flipping it to the opposite. We could instead call that variable what it is... the opposite of isLowSkillVisible ... something like notIsLowSkillVisible

…LowSkillVisible and flip later where needed.
@renoirb renoirb force-pushed the CLIM-1291_S2D-Map-When-Climatology-Hide-Skill-Layer branch from 533b3ea to d61643a Compare March 20, 2026 20:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants