Skip to content

Release v0.65.0 — /archives enhancements (0.61.0 → 0.65.0)#452

Merged
elfensky merged 43 commits into
mainfrom
develop
Jun 29, 2026
Merged

Release v0.65.0 — /archives enhancements (0.61.0 → 0.65.0)#452
elfensky merged 43 commits into
mainfrom
develop

Conversation

@elfensky

Copy link
Copy Markdown
Owner

Release v0.65.0

Promotes developmain for the v0.65.0 production release. main was last released at v0.60.0; this bundles six versions of /archives enhancements (43 commits).

Highlights (0.61.0 → 0.65.0)

  • 0.65.0 — War Narrative enrichment. Per-season phrasing variety (deterministic / SSR-stable), player surge/collapse beats, offensive conquest milestones (a faction driven to its homeworld's gates / the first homeworld to fall), and a "war by the numbers" telemetry beat — all computed server-side, so getCampaign and the rest of the app are untouched.
  • 0.64.1 — War Narrative toggle restyled to the shared SHOW/HIDE primary button (yellow border, square); the Ministry subtitle stays visible in both states.
  • 0.64.0 — Faction introduction markers (Phase 11: Timeline — faction introduction order visualization #157) — "a faction enters the war" dividers interleaved in the Event Log, faction-colored. Archives-only, opt-in.
  • 0.63.0 — Players Over Time — a per-war player-count line (driven by the faction toggle) replaces the Player Engagement scatter; getCampaign gains an additive playerTimeseries field.
  • 0.62.0 — War Narrative (Phase 09: Auto-generated war narrative #274) — the in-world Ministry-of-Truth chronicle of each season.
  • 0.61.0 — Cascade deep-linking — click a cascade → scroll the event log to it + a persistent faction-tinted highlight, shareable via URL hash (rehydrates on direct load / back-forward).

Verification

  • Each feature was brainstormed → spec'd → built TDD via subagents, with per-task spec+quality reviews and a final whole-branch review.
  • develop: lint clean, typecheck clean, 1572 unit tests passing, next build OK, DevTools-verified.

Post-merge release steps (project workflow)

  1. Tag v0.65.0 on the main merge commit and push it — the production Docker build triggers only on version tags.
  2. Merge main back into develop so the next release PR doesn't trip the "branch not up to date" check.

🤖 Generated with Claude Code

elfensky and others added 30 commits June 26, 2026 13:31
A collapsible, in-world "war narrative" for each season's /archives page —
a chronological chronicle generated in the Ministry of Truth propaganda voice
from existing getCampaign data (no schema/DB/query changes).

- buildWarNarrative(data): pure helper → ordered [{day, text}] beats — opening,
  faction arrivals (introduction_order + first_seen), cascade runs collapsed via
  findAllCascades, per-event field reports, and a getWarOutcome cap. Defensive:
  empty → []; reduce-min war_start fallback (no Math.min spread).
- NarrativeSection.jsx: native <details>/<summary> collapsible, hide-when-empty,
  data-umami-event="archive-narrative-toggle". Wired into ArchivesClient after
  the Player Engagement section.
- 12 unit tests incl. an Illuminate-cascade regression for the article-stripped
  faction name ("The Illuminate", not "The The Illuminate").

Verified: lint, typecheck, test:unit (1515), build all pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…157)

Surface WHEN each faction entered the war on the /archives timeline, as
synthetic "<Faction> enter the war" markers interleaved chronologically with
the event rows. Archives-only; the live dashboard is untouched.

- buildIntroMarkers(data): pure helper → one marker per faction with a positive
  introduction_order slot AND a non-null first_seen; day math + article strip
  mirror buildWarNarrative; reduce-min war_start fallback; missing data → [].
- EventLog: opt-in `introMarkers` prop (default []). When empty, output is
  byte-identical to today (HomeClient passes nothing) — a test asserts default-
  vs-empty render identical. Markers render via a faction-colored IntroMarker
  divider, keyed intro-${enemy}, excluded from the day W/L count.
- ArchivesClient computes + passes the markers.

Verified: lint, typecheck, test:unit (1518), build all pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…catter)

Replace the per-event Player Engagement scatter with a per-war player-count
line that hooks into the existing /archives faction toggle:
- global → total-players line (brand primary) + dots for every event
- a faction → that faction's line (faction-colored) + only its events' dots
Event dots sit on the line at each event's start day with a
type · region · faction · outcome tooltip. No legend (the toggle labels it).

- getCampaign: additive `playerTimeseries` field (per-bucket players from
  h1_statistic via new groupStatisticByBucket); [] for pre-telemetry seasons.
- buildPlayerLine.mjs: pure, unit-tested line+dots builder.
- PlayersOverTimeChart.jsx (+ Loader), wired into ArchivesClient's faction
  section; removed PlayerEngagementChart + buildEngagementSeries (+ scatter).
- Hides for historical seasons with no player timeseries.

Verified: lint, typecheck, test:unit (1514), build; DevTools across all toggle
states + hide-when-empty on a telemetry-less season.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The x-axis plotted the integer day, so multiple intra-day buckets collapsed
onto one column and the line drew vertical stacks instead of a real time
series. Use continuous (fractional) day-into-war anchored to war_start, and
switch the line from monotone to linear so sharp changes read as honest cliffs
rather than smoothed curves.

- buildPlayerLine: `warStart` param; fractional `dayInto` x for points + dots
- PlayersOverTimeChart: declares `warStart`, line type="linear"
- ArchivesClient: passes `warStart={data?.war_start}`
- tests: fractional-x dot, intra-day-distinct-x regression, warStart anchor

Verified: lint, typecheck, test:unit (1516), build; DevTools on S157 — x is
strictly increasing, no vertical collapse.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Show a tick for every day (D0…D<last>) instead of the ~5 auto-thinned ticks,
and anchor the axis 0-based to war_start so the day labels match Conquest
Progress for side-by-side reading. The line stays time-proportional (continuous
fractional x); only the tick set and base changed.

- buildPlayerLine: 0-based dayInto (drop the +1)
- PlayersOverTimeChart: explicit ticks=[0..lastDay], domain=[0, lastDay]
- tests updated to the 0-based convention

Verified: lint, typecheck, test:unit (1516), build; DevTools S157 shows D0…D40.
Two fixes for the players-over-time chart and its comparability with
Conquest Progress (FactionHealthChart):

1. Tooltip was stuck on one event regardless of cursor. It read the dots
   Scatter (a separate data array from the line), whose payload doesn't follow
   the active index. Rewrote it to read the LINE series + active `label` —
   showing "Day N · M players" at the hovered point, with the event details
   appended when an event sits on that day.

2. Conquest Progress used a category x-axis (one slot per snapshot, not
   time-proportional, duplicate day labels). Gave it the same continuous,
   0-based, time-proportional day axis as players-over-time, and a SHARED
   day-domain (warDayMax, passed to both) so the two charts line up day-for-day.

- FactionHealthChart: fractional 0-based day, type=number XAxis, all-day ticks,
  warStart + domainMax props; ChartTooltip rounds the day.
- PlayersOverTimeChart: PlayerTooltip reads line+label+event; domainMax prop.
- ArchivesClient: computes warDayMax, passes warStart+domainMax to both charts.
- Tests: FactionHealthChart intra-day fractional-day regression.

Verified: lint, typecheck, test:unit, build; DevTools — both axes D0-based,
time-proportional, shared [0,44] domain; tooltip reads the hovered line point.
Clicking a cascade card pins a persistent faction-tinted highlight over all
its events and scrolls the event log to it, hash-driven for shareability.
Design corrected by a 5-way model debate and grounded against the code, then
adversarially self-reviewed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cursor

The players-over-time tooltip froze past the first few days. The <Scatter> held
its OWN short data array (~25-78 dots) inside a ComposedChart whose single
activeTooltipIndex sweeps the ~600-point line; past the scatter's indices
recharts clamped the index and froze the tooltip. Conquest Progress never broke
because all its series share one data array.

Render events as <ReferenceDot> markers (non-data decorators) instead of a
Scatter, so the Line is the SOLE tooltip data source — like Conquest. The
tooltip now reports the player count (+ event) at every hovered day.

Root cause + fix from a four-way debate (Opus/Sonnet/Codex/Gemini): unanimous
on the cause, 3/4 on ReferenceDots over Gemini's unified-data merge.

Verified: lint, typecheck, test:unit, build; DevTools differential sweep —
Players now tracks D6/186 → D28/168 → D34/166, identical behaviour to Conquest.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cascade → event-log deep-linking on /archives: clicking a cascade card pins a
persistent faction-tinted highlight across all of its events and scrolls the
event log to the topmost one (sort-agnostic, JS scrollIntoView), shareable via
a URL hash that rehydrates on direct load and back/forward.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Auto-generated War Narrative section on /archives (#274): a collapsible,
hide-when-empty in-world chronicle of each season's campaign in the Ministry of
Truth's voice, built chronologically from event data.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Players Over Time chart on /archives (replaces the Player Engagement scatter):
a per-war player-count line driven by the faction toggle, with event dots and a
shared day x-domain. getCampaign gains an additive playerTimeseries field from
h1_statistic; section hides for pre-telemetry seasons. Resolved ArchivesClient
(combined with cascade + war-narrative) and CHANGELOG conflicts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Faction introduction markers in the /archives Event Log (#157): synthetic
'faction enters the war' dividers interleaved chronologically among event rows,
faction-colored, archives-only via a new opt-in EventLog introMarkers prop.
Resolved EventLog.css + EventLog.test.jsx conflicts with the cascade highlight
work (kept both); EventLog.jsx + ArchivesClient auto-merged cleanly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pecheck)

The faction-intro-viz merge brought a formal @param typedef for EventLog that
predated the cascade highlightedKeys prop, so tsc rejected highlightedKeys.
Documenting the prop restores typecheck. No behavior change.
Replace the chevron <details>/<summary> toggle with the shared primary Button
(yellow border, square) reading SHOW when collapsed / HIDE when expanded. The
Ministry subtitle now stays visible whether collapsed or expanded; only the
beats list toggles. DevTools-verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Four features (seeded phrasing, player surge/collapse, territory turning-points,
war-by-numbers) computed server-side so getCampaign stays untouched. Grounded
against the code; covers perf + page-impact.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
points/points_max is Super Earth's OFFENSIVE conquest progress (high = SE
winning), not enemy pressure — verified vs computeMapState + HD1 API docs. The
inverted 'darkest hour' is replaced with correct offensive conquest milestones
(breakthrough / first homeworld falls), non-redundant with the cascade beats.
Added a coherence/adjacency guard for the new highlight beats.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
War Narrative SHOW/HIDE primary Button + always-visible Ministry subtitle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8 TDD tasks: 4 pure beat-generator modules + getSeasonTelemetryTotals query +
buildWarNarrative orchestrator (phrasing + coherence guard) + server wiring +
changelog. Grounded against the code; Task 6 loosens the existing test's
variant-sensitive assertions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
elfensky and others added 13 commits June 29, 2026 00:51
buildConquestBeats was computing day as Math.floor(time/86400), treating
snapshot.time as a day number when it's actually an absolute unix timestamp
(~1.7e9). Other beats in the feature use war-start-relative day numbering.
Added warStart parameter and fixed dayOf helper to match.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Type array with literal unions to preserve kind discriminants through
contextual typing. Add null guards in dedupe condition for TS narrowing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…; correct guard docs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
War Narrative enrichment on /archives: deterministic per-season phrasing variety,
player surge/collapse beats, offensive conquest milestones, and a war-by-numbers
telemetry beat — all computed server-side so getCampaign and the rest of the app
stay untouched. Brainstormed + 5-way-reviewed (feature 3 corrected from an
inverted territory reading), built TDD via subagents, DevTools-verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@elfensky elfensky merged commit b17e1f3 into main Jun 29, 2026
11 checks passed
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.

1 participant