Skip to content

Calendar weather: Lock days, and a forecast players can see - #984

Merged
keyxmakerx merged 4 commits into
mainfrom
claude/weather-per-day-5guwd9
Oct 3, 2026
Merged

keyxmakerx merged 4 commits into
mainfrom
claude/weather-per-day-5guwd9

Conversation

@keyxmakerx

@keyxmakerx keyxmakerx commented Oct 3, 2026 •

Copy link
Copy Markdown
Owner

Fixes #918
Fixes #917
Security implication: New POST /campaigns/:id/calendars/:calid/weather/days/lock gated CanAuthorDmOnly like the other day-weather writes (body's locked is required, so an omitted field can't unlock). New GET .../weather/forecast uses the same gate as GET .../weather/days. A day's locked flag 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 _lockDays posts {days, locked} and reads w.locked from GET /weather/days; static/js/widgets/calendar_view.js fetchForecast reads [{year,month,day,lead,confidence,icon,words,temp_low,temp_high,precip_chance}] from ListWeatherForecastAPI (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:

  • Lock. The edit bar has a Lock button next to Paint weather and Generate…. Locked days show a small one-colour padlock in edit mode. Generate and Reroll leave them alone and the sheet labels them "Locked". Choosing locked days turns the button into Unlock. The toast says "Locked 3 days." with Undo. Painting a locked day by hand still works (painting always wins, and clears the lock), and Undo of that paint puts the lock back. Players never see locks.
  • Forecast. Calendar settings → Weather gains "Show players a forecast" and "Days ahead" (1–10, default 5). With it on, players see dashed forecast marks for the next few days ("Rain likely", fading to "Cloudy, maybe" and "Hard to call" as confidence falls). Opening one shows a Forecast tag, the words, "8°C to 14°C · 70% chance of rain" and "A forecast, not a promise. The real weather shows on the day." The Director's view is unchanged except for a "Players see: Rain likely" line on forecast days.

How: calendar migration 025 adds calendar_weather_days.locked and calendar_weather_settings.forecast_days. The generated upsert keeps any manual or locked row as stored; a manual write clears locked. The forecast is computed server-side in weather_forecast.go from 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 new Calendar.addDays walks the calendar's own months and leap rules. The forecast switch is stored on the existing calendars.forecasts_enabled and written through UpdateCalendar with only that field.

Why

#918 and #917, the last two follow-ups of the #765 weather plan.

Honest deviations:

  • The forecast's temperature spread uses a fixed 3°C rather than the climate's own spread, and its chance of rain is a simplified rule; both are documented in weather_forecast.go. Words and confidence follow the generator exactly.
  • Forecasts skip days that have no stored weather, so a player can tell which upcoming days the Director has filled in.
  • Temperatures read "8°C to 14°C" (the day card's existing format) where the mockup sketched "8° to 14°".
  • Saving forecast settings writes the settings row then the calendar's switch; if the second write failed, saving again fixes it.

Test plan

  • go build ./..., go vet ./..., golangci-lint v2.13.2 on calendar: 0 issues
  • go test ./internal/plugins/calendar/... ./internal/wire/... ./internal/app/... -count=1: ok (lock gating, only-existing-rows, generated write skips locked, paint clears lock, locked hidden 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)
  • Integration against local MariaDB (make test-db-up, CHRONICLE_TEST_DB_DSN=…): calendar Integration tests pass (34s), incl. the lock subtest and the forecast_days round trip. Re-run after merging main (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 merging main
  • make 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-*.sh all pass
  • Live check after deploy: Calendar → Edit → choose 3 days with weather → Lock → padlocks appear, toast "Locked 3 days." → Generate… on that month shows them as "Locked" and Apply leaves them. Then gear → Weather → tick "Show players a forecast" → Save. As a player, the next 5 days with weather show dashed forecast marks; opening one shows the Forecast tag.

Tenet self-check


Generated by Claude Code

claude added 2 commits October 3, 2026 04:59
…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

Copy link
Copy Markdown
Owner Author

Fresh-DB Migration Replay (and Build & Test) fail here for a reason outside this PR: main carries two core migrations numbered 000036 (db/migrations/000036_campaign_book_edits.* and 000036_stashes.*). The same two checks are red on main's head, and locally go test ./internal/database/ -run GaplessSequence fails on this branch exactly as on main. This PR adds no core migration (its only migration is calendar plugin 025_weather_lock_forecast).

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 tools/check-migration-immutability.sh, so that call belongs with #977. Once main is fixed this branch will be brought up to date and re-checked. I can't re-run jobs from here.


Generated by Claude Code

@keyxmakerx
keyxmakerx marked this pull request as ready for review October 3, 2026 05:32
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
@keyxmakerx
keyxmakerx merged commit 23b976a into main Oct 3, 2026
10 checks passed
@keyxmakerx
keyxmakerx deleted the claude/weather-per-day-5guwd9 branch October 3, 2026 15:51
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.

Calendar edit bar: Lock days so Generate and Reroll leave them alone Calendar weather: show players forecast days when forecasts are on

2 participants