Skip to content

Calendar: era colours that blend, era look settings, and era info for players - #983

Merged
keyxmakerx merged 9 commits into
mainfrom
claude/calendar-regressions-6rdw6a
Oct 3, 2026
Merged

keyxmakerx merged 9 commits into
mainfrom
claude/calendar-regressions-6rdw6a

Conversation

@keyxmakerx

@keyxmakerx keyxmakerx commented Oct 3, 2026 •

Copy link
Copy Markdown
Owner

Requested by Key Maker · project thread

Fixes #923
Security implication:

  • New visibility rule. Players and Scribes now receive eras that are not hidden. Before this change they received none.
  • Hidden eras. An era marked "hidden until it begins" is removed on the server from every player-facing read until the calendar's current date reaches it: its name, colours, description and events. While it is hidden, the previous era carries on for players.
  • DM note. A Director's note on an era is never sent to players.
  • Paths checked:
    • the calendar page's inline data
    • GET /calendars/:calid and the public route
    • the Calendars list and peek
    • the skybox and widget blocks
    • the syncapi /calendar/eras and /calendar routes
    • event reads: month, single, by-ids, upcoming, timeline, search, the AI export's Safe mode and repeat-rule anchors
  • New endpoint GET /calendars/:calid/eras/:eraID/events.
    • The era must belong to the calendar, and the calendar to the campaign.
    • A hidden era answers the same 404 as a missing one.
    • Events go through the same per-event filters as other reads, capped at 500.
  • Lore page. The page's name is joined only within the campaign and blanked when the viewer can't see that page.
  • Writes.
    • Era create, update and delete need CanAuthorDmOnly; PUT /era-look needs Owner.
    • Era ids are scoped to the calendar.
    • Imports drop lore_entity_id.

Consumer-verified: n/a (the new endpoint's only consumer is calendar_view.js in this PR)
Mockup: "Era blend" v3 look and v4 settings and info (approved, with option B), https://claude.ai/artifact/1YXz5K4GiPEV6NkytF8uo1. Placement changes per Key Maker's note are on the Sign-offs card "Calendar eras: where the info and the look settings ended up".

What this changes

Before: an era was a name and one colour, which players never saw. The calendar showed nothing where one era turned into the next.

After:

  • The blend. A faint tint of each era's colours sits behind the days, with a soft, slow curl of colour only where two eras meet. It eases to a stop when the viewer steps away, uses a lighter version on weak devices, and becomes a still tint under reduced motion.
  • Era info. Pointing at the era name above the month shows its dates. Clicking it slides open a panel under the month header with:
    • a timeline of every era (click one to jump there)
    • the era's dates, length and description
    • its key events (the events marked major)
    • a lore page link
    • a Director-only note
  • Era look settings. An Era look section on the calendar settings page sets:
    • colours on or off
    • Still / Slow and subtle / Lively, plus intensity and speed
    • each era's two colours, Gas or Ink style, and its own feel
  • Per-era fields. Each era can be hidden from players until it begins.

How:

  • Migration 024_era_look (plugin, idempotent, with a down file) adds:
    • four calendar-wide columns
    • six per-era columns, including the lore entity FK with ON DELETE SET NULL
  • Painter. calendar_era_blend.js is a canvas painter shared by the calendar and the settings preview.
  • View. calendar_view.js adds the chips, tooltip and inline panel.
  • Settings. calendar_era_look.templ, era_look_view.go and calendar_era_look.js make up the settings section. The per-era hidden flag, note and lore page are in the era form (Eras button in Edit).
  • Export and import round-trip the new fields.
  • Partial updates: absent preserves, null clears, except that the calendar-wide look is saved whole (its only writers always send all four values).

Deviations from the mockup:

  • Era info placement. As Key Maker asked, it expands from the era name instead of sitting on the page.
  • Settings preview. The settings live on the settings page with their own Save and Cancel, without the side-by-side live preview.
  • "Begins" label. It is a plain label, not a button, to avoid nested buttons; keyboard users use the chip.
  • Lore link. It can only point to a campaign page.
  • Era lengths. On calendars with uneven months they are approximate.

Why

#923, from Key Maker's Sign-offs answers on the era blend cards: "i hate this to be just a visual thing", "i'd want my slow and subtle", and "it needs to be something that expands, or hover overs".

Test plan

  • templ generate (v0.3.1001): no changes. go build ./... OK.
  • go test ./... -short -count=1: 50 packages ok.
  • CHRONICLE_TEST_DB_DSN=... go test ./internal/plugins/calendar/... -count=1: ok, 323 tests. This includes the migration apply-twice and roll-back-twice test, the era repository test and the export round-trip.
  • node --test test/js/*.test.mjs: 323/323, including the new calendar_era_blend.test.mjs (13).
  • golangci-lint run ./...: 0 issues. All 9 tools/check-*.sh pass.
  • Live Playwright run against a real server and a fresh DB: 85/85, no console errors. Covered:
    • tooltip and panel open and close
    • jump links
    • the player never sees the hidden era or the DM note
    • 400px with no horizontal scroll
    • reduced motion
    • the loop stops at rest
    • Era look Save and Cancel
    • the era editor's hidden flag, note and lore page
  • Live captures of the real build were shown to Key Maker (Sign-offs card "Calendar eras: where the info and the look settings ended up"). The era form, the begins label, the tooltip and the tint clipping were tidied after the first captures.
  • Adversarial review. Fixed after review:
    • A calendar remounted by the Calendars page opener reused a dead painter, so no colours showed and its loop could not rest.
    • Repeat-rule anchors now pass the secret-era filter.
  • Follow-up: Calendar: a Scribe's own events inside a hidden era vanish for them #982 (a Scribe's own events inside a hidden era vanish for them).

Tenet self-check

  • T-B1 security: server-side hiding on every era and event read path, listed above
  • T-B2 plugin isolation: no cross-plugin imports; the lore link uses the core entities table through the existing viewable-entities gate
  • T-B3 production UI: transitions, loading and error states in the panel, reduced motion, ease to still (Every looping animation slows to still when you step away and restarts when you're back #935), keyboard and Escape
  • T-B4 dual-audience docs: calendar .ai.md covers the era look, hidden eras and the panel

🤖 Generated with Claude Code

https://claude.ai/code/session_01FWwWUo8dgxEwNJ8SWFdJuj


Generated by Claude Code

keyxmakerx and others added 6 commits October 3, 2026 04:34
Eras gain a second colour, a blend style (gas or ink), an optional own
feel, a lore page link, a Director's note and a "hidden until it begins"
flag; calendars gain the whole-calendar era look (colours on/off, feel,
intensity, speed). Migration 023 (plugin) adds the columns idempotently,
with a down file.

Players now receive a calendar's eras, filtered server-side: an era hidden
until it begins is withheld (name, description, colours, note and the
events dated inside it) until the calendar's current date reaches it, and
the era before it reads as still going. A Director's note never reaches a
player. Lore links a player cannot see are blanked. The boundary is the
same as dm_only content: the Owner or a granted co-Director see
everything; Scribes and players are filtered.

New PUT /calendars/:calid/era-look (Owner) saves the look in one
transaction with partial per-era fields. Calendar-format and campaign
exports carry the new fields (the lore link by entity slug in campaign
export only); the AI export's Safe mode skips secret eras and their events.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWwWUo8dgxEwNJ8SWFdJuj
GET /calendars/:calid/eras/:eraID/events (Player+, and the public group)
returns the events dated within an era, oldest first, capped at 500 with
a total. The era is resolved as the viewer sees it: a player asking for an
era still hidden until it begins gets 404, and an era whose end was
cleared because a secret era follows runs until the next era the player
can see. Events are filtered exactly like the month read (per-user rules,
unannounced future events, secret-era events, hidden page links, rule
references).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWwWUo8dgxEwNJ8SWFdJuj
The eras now show on the calendar itself:

- calendar_era_blend.js paints a faint tint of each era's two colours
  behind the month's days, and a slow curl of colour only at the seam
  where one era gives way to the next, in the entering era's style (gas
  mixing or ink in water). A weak device gets a coarser texture at most
  ~30 frames a second; reduced motion gets a still wash; the motion eases
  to rest over ~2 s when the tab is hidden, the window loses focus, the
  pointer leaves or nothing is touched for 8 s, and the loop then stops.
  When nothing should move it draws once.
- The month header's era name is a chip (two with an arrow when an era
  begins mid-month). Hover or focus shows its dates and length; a press
  unfolds a panel under the header that pushes the grid down: a ribbon of
  every era with "you are here" (a segment jumps to the era's first
  month), then the era's dates, description, lore page, key events (the
  era's major-tier events, each going to its day) and, for a Director,
  the private note. Escape, the close button or the chip folds it away.
- An era's first day carries a "begins" label that opens the panel.
- The era manager moves to an Eras button in the edit strip, and its form
  gains "hide from players until it begins", the Director's note and a
  lore page picker. Editing an era no longer resets its colour and order.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWwWUo8dgxEwNJ8SWFdJuj
The structure editor (the gear in Edit mode) gains an Era look part with
its own Cancel and Save: colours on/off, the overall feel (Still, Slow and
subtle, Lively, or Custom from the intensity and speed sliders), and per
era the suggested palettes, first and second colour, style (gas mixing or
ink in water) and an optional own feel. A small month beside the controls
previews each change of era live, painted by the same blend as the
calendar. Save sends the whole calendar look and only the era fields that
changed; Cancel puts back what was saved. The era manager links to it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWwWUo8dgxEwNJ8SWFdJuj
The campaign export round-trip test now carries the era look and the
per-era look, lore, DM note and hidden fields, and its fake service
implements SaveEraLook. The calendar .ai.md describes eras, the era look
part, hidden eras and the era events endpoint.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWwWUo8dgxEwNJ8SWFdJuj
…ecret eras

boot.js reuses the one calendar_view object, so destroy left a dead
painter that the next mount reused: no era colours and a loop nobody
could rest. Rule anchors now pass the same secret-era gate as other
player event reads.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWwWUo8dgxEwNJ8SWFdJuj
@keyxmakerx
keyxmakerx force-pushed the claude/calendar-regressions-6rdw6a branch from f5e1d73 to 6c7dbbb Compare October 3, 2026 04:54

Copy link
Copy Markdown
Owner Author

Build & Test and Fresh-DB Migration Replay will be red here, and the cause isn't this PR. Main currently has two core migrations numbered 000036 (000036_stashes and 000036_campaign_book_edits), so on main itself both checks are red at 6a5bf17.

I first carried #977's renumber into this branch. That tripped check-migration-immutability.sh, because the guard sees renaming 000036_stashes as deleting an existing migration; #977 is red on the same guard. So I dropped that commit, and this PR now fails exactly the way main does.

The fix belongs in #977: either an allowed exception in the guard for a never-deployed renumber, or a different fix. Once #977 lands, I'll merge main into this branch and the checks should go green.

Locally, with the stashes migration renumbered for the run only, go test ./internal/plugins/calendar/... against MariaDB passes (323 tests), and so does everything else listed in the test plan.


Generated by Claude Code

keyxmakerx pushed a commit that referenced this pull request Oct 3, 2026
…migration

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193yvwna56XL2FRDNCMSdT9
claude added 2 commits October 3, 2026 05:23
The era manager's form now reads like the event editor: the era name,
then When (Starts as month list, day list and year under Month/Day/Year
captions, an Ongoing switch, Ends when it is off, prefilled with where
the era ends now), Details (description, lore page) and Players (the
hide switch, the Director's note). The card grows only to the calendar's
bottom edge and scrolls under a fixed Cancel/Delete/Save bar instead of
hanging over the page. Eras are listed oldest first, one row each, with
the span each really covers (an era with no end runs to the day before
the next begins, so "1000-1523" rather than "1000-present"); an open
form keeps the others in a sideways-scrolling row above it.

Era chip tooltip: pressing a chip re-rendered the chips under a resting
pointer, which fired a fresh mouseover and brought the tooltip back over
the opening panel. A press or Escape now puts it away until the pointer
leaves the chips; focus handed back by Escape stays quiet.

The chip row, not the chip, now takes the -6px left offset: the chip's
negative margin shrank the row's measured width and cut a lone era's
name short ("Age of Drag...").

Era colour field: the coarse canvas is clipped to the outline of the
month's real day cells, and drawn opaque under the clip so scaling up
no longer smears clear pixels into the edge days. Leading and trailing
empty cells stay plain. Tint strength and motion are unchanged.

The era's "begins" label wraps (up to three lines) instead of
truncating, with the era's colour as its left edge; on a phone it is a
"New era" tag in the era's colour, with the full name for screen
readers. The era panel's lore row and the editor's chosen lore page use
an inline page glyph rather than an icon-font glyph, so the box is never
empty. The ribbon's segment tooltip sits under the ribbon, where the
panel's edge no longer cuts it off.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWwWUo8dgxEwNJ8SWFdJuj
@keyxmakerx
keyxmakerx marked this pull request as ready for review October 3, 2026 05:33
@keyxmakerx keyxmakerx self-assigned this Oct 3, 2026
# Conflicts:
#	static/css/calendar-view.css
#	static/js/widgets/calendar_view.js
@keyxmakerx
keyxmakerx merged commit dedada8 into main Oct 3, 2026
10 checks passed
@keyxmakerx
keyxmakerx deleted the claude/calendar-regressions-6rdw6a branch October 3, 2026 15:30
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: an animated blend when the calendar moves from one era to the next

2 participants