Calendar weather: Lock days, and a forecast players can see - #984
Conversation
…ional player forecast (#918, #917) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193yvwna56XL2FRDNCMSdT9
…migration Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193yvwna56XL2FRDNCMSdT9
|
Fresh-DB Migration Replay (and Build & Test) fail here for a reason outside this PR: The fix is #977 (renumber stashes to 000037). It isn't ported here: #977's own Build & Test is red, and renaming an existing migration file trips Generated by Claude Code |
Keeps both sides in calendar_view.js: the forecast fetch alongside the Who's free fetch, the forecast mark alongside the era-start mark, and both test exports. Drops this branch's strp test helper now that main declares the same one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0193yvwna56XL2FRDNCMSdT9
Fixes #918
Fixes #917
Security implication: New
POST /campaigns/:id/calendars/:calid/weather/days/lockgated CanAuthorDmOnly like the other day-weather writes (body'slockedis required, so an omitted field can't unlock). NewGET .../weather/forecastuses the same gate asGET .../weather/days. A day'slockedflag is sent only to Directors. Forecast entries carry only a category icon, words, a temperature range and a rounded chance, never the real preset, label, colour, description or wind, so a hidden future day (or an owner's own kind's name) can't leak. Forecasts are deterministic per calendar, day and lead, so refreshing reveals nothing new.Consumer-verified:
static/js/widgets/calendar_editor.js_lockDaysposts{days, locked}and readsw.lockedfromGET /weather/days;static/js/widgets/calendar_view.jsfetchForecastreads[{year,month,day,lead,confidence,icon,words,temp_low,temp_high,precip_chance}]fromListWeatherForecastAPI(internal/plugins/calendar/handler.go).Mockup: Sign-offs cards "Weather: Lock days so Generate leaves them alone" (wx-lock v1, approved with one change: a plain one-colour padlock rather than the emoji, which is what's built) and "Weather: a forecast players can see" (wx-forecast v1, approved).
What this changes
Before: the only way to stop Generate from changing a day's weather was to paint it by hand, and players saw no weather past today, even though calendars have had a "forecasts enabled" flag with no way to set it.
After:
How: calendar migration 025 adds
calendar_weather_days.lockedandcalendar_weather_settings.forecast_days. The generated upsert keeps any manual or locked row as stored; a manual write clearslocked. The forecast is computed server-side inweather_forecast.gofrom the stored real readings (a deterministic Go port of the generator's forecast: confidence falls with distance, a seeded draw sometimes shows a neighbouring sky, temperatures widen with uncertainty), because a player's browser must never receive the real future weather to compute it from. A newCalendar.addDayswalks the calendar's own months and leap rules. The forecast switch is stored on the existingcalendars.forecasts_enabledand written throughUpdateCalendarwith only that field.Why
#918 and #917, the last two follow-ups of the #765 weather plan.
Honest deviations:
weather_forecast.go. Words and confidence follow the generator exactly.Test plan
go build ./...,go vet ./..., golangci-lint v2.13.2 on calendar: 0 issuesgo test ./internal/plugins/calendar/... ./internal/wire/... ./internal/app/... -count=1: ok (lock gating, only-existing-rows, generated write skips locked, paint clears lock,lockedhidden from players, forecast off →[], determinism, no real label/colour, confidence curve, word thresholds, skips empty days, month/year rollover, settings preview/save notes and validation; route snapshot +3)make test-db-up,CHRONICLE_TEST_DB_DSN=…): calendar Integration tests pass (34s), incl. the lock subtest and theforecast_daysround trip. Re-run after mergingmain(with Game nights on world calendars, in your own time, from the sidebar, plus Who's free #962 and Calendar: era colours that blend, era look settings, and era info for players #983): pass, 37.0s.go test ./... -short: all ok after mergingmainmake test-js: 394 pass, 0 fail (after merging main) (9 new: sheet treats locked like painted, Lock/Unlock/disabled label, forecast mark escaping/dashed/faded/accessible text, forecast day-card wording, "Players see")tools/check-*.shall passTenet self-check
.ai.mdupdatedGenerated by Claude Code