Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
118 commits
Select commit Hold shift + click to select a range
4faab54
Merge branch 'main' into develop — sync v0.47.9 release
elfensky May 21, 2026
32afbff
fix: remove empty "Today" section from the event log (#385)
elfensky May 21, 2026
6d8ce58
Merge branch 'bugfix/eventlog-empty-today' into develop — release 0.4…
elfensky May 21, 2026
8f567dc
fix: defer stats faction-tab re-render with startTransition (#388)
elfensky May 21, 2026
561ad75
Merge branch 'bugfix/statgrid-tab-switch-transition' into develop — r…
elfensky May 21, 2026
e3aef58
feat: war-duration stat card in the dashboard StatGrid (#386)
elfensky May 21, 2026
408d04a
Merge branch 'feature/statgrid-war-duration' into develop — release 0…
elfensky May 21, 2026
79d2be5
feat: card → map hover highlight (#185)
elfensky May 21, 2026
b12e50e
refactor: HELLDIVERS_LOST subtitle labels accidentals "Martyrs"
elfensky May 21, 2026
ad78de7
Merge branch 'feature/card-map-hover-link' into develop — release 0.4…
elfensky May 21, 2026
eab2cb0
feat(stats): ENEMIES_KILLED 24h kill-pace trend arrow
elfensky May 21, 2026
dc768e0
Merge branch 'feature/enemies-killed-trend' into develop — release 0.…
elfensky May 21, 2026
545aad4
fix: card→map hover highlight is additive, not dimming (#185)
elfensky May 21, 2026
710476b
Merge branch 'bugfix/map-hover-additive-highlight' into develop — rel…
elfensky May 21, 2026
7af0a72
feat: map → card hover highlight — reverse direction of #185 (#390)
elfensky May 21, 2026
48087a9
Merge branch 'feature/map-card-reverse-highlight' into develop — rele…
elfensky May 21, 2026
8ea570c
fix: pin lowlighter/metrics action to v3.34 stable tag
elfensky May 22, 2026
2b4df12
Merge branch 'bugfix/metrics-action-pin' into develop — release 0.47.17
elfensky May 22, 2026
69035d9
feat: unify /archives statistics with the homepage StatGrid (#391)
elfensky May 22, 2026
0d2f330
Merge branch 'feature/archives-faction-combat-stats' into develop — r…
elfensky May 22, 2026
9c95455
feat: add cross-season /stats page (#394)
elfensky May 22, 2026
e2e29a8
fix(archives): match hero StatGrid's auto-fit layout for the extras row
elfensky May 22, 2026
85a9bcb
Merge branch 'feature/cross-season-stats-page' into develop — release…
elfensky May 22, 2026
0a3d9a1
docs: add Ministry Interference sitewide easter egg design
elfensky May 22, 2026
3236dcc
docs: revise Ministry Interference spec after adversarial review
elfensky May 22, 2026
d5fda63
docs: resolve open question on losing-tone voice (Underground broadcast)
elfensky May 22, 2026
57c48c2
docs: add Ministry Interference implementation plan
elfensky May 23, 2026
3d95e12
feat(ministry): add hijack cycle hook + pinned timing constants
elfensky May 23, 2026
d407eac
docs: add Cascade Failure Log design spec
elfensky May 23, 2026
a3d9ddd
feat(ministry): add content pools + pickAlt with min-12-per-pool enfo…
elfensky May 23, 2026
9b21605
fix(ministry): preserve curly apostrophes in migrated RESISTANCE_MESS…
elfensky May 23, 2026
fc58eff
docs: add Cascade Failure Log implementation plan
elfensky May 23, 2026
1111b5a
test(ministry): add rng=1.0 guard coverage for pickAlt
elfensky May 23, 2026
88d25e4
feat(ministry): add content pools + pickAlt with min-12-per-pool enfo…
elfensky May 23, 2026
ed68104
fix(ministry): preserve curly apostrophes in migrated RESISTANCE_MESS…
elfensky May 23, 2026
5caf88c
test(ministry): add rng=1.0 guard coverage for pickAlt
elfensky May 23, 2026
b10dc2b
feat(ministry): add getWarTone helper (null = effect disabled)
elfensky May 23, 2026
3170967
feat(ministry): add module-level registry (useRef-friendly, no React …
elfensky May 23, 2026
77b3261
feat(ministry): add MinistryContext + hook scaffolding
elfensky May 23, 2026
bc691a1
feat(ministry): add MinistryProvider shell with disabled-state semantics
elfensky May 23, 2026
0deedf3
style(ministry): lint:fix + clarify context value stability comments
elfensky May 23, 2026
8828465
feat(ministry): add hijack + ambient flicker schedulers with idle/sco…
elfensky May 23, 2026
bb97364
docs(ministry): note AmbientFlicker folded into provider (no separate…
elfensky May 23, 2026
8e73017
feat(ministry): add Hijackable idle render + dev-mode category guard
elfensky May 23, 2026
08c1f06
feat(ministry): add Hijackable hijack/flicker overlay with sr-only truth
elfensky May 23, 2026
30a435e
fix(ministry): stable useCallback dep + explicit null fallback + flic…
elfensky May 23, 2026
338fcfb
feat(ministry): mount provider in root layout + force-dynamic for war…
elfensky May 23, 2026
ba4f595
refactor(archives): replace defeat-only effect in header with Hijackable
elfensky May 23, 2026
02e9b2b
refactor(archives): drop Cyberstan toggle + glitchPhase plumbing from…
elfensky May 23, 2026
381e021
refactor(archives): swap OUTCOME card GlitchText for Hijackable (both…
elfensky May 23, 2026
d058e74
refactor(archives): drop defeatMessageIndex prop (Ministry system own…
elfensky May 23, 2026
8f4778f
refactor(archives): remove retired Cyberstan files + tests (replaced …
elfensky May 23, 2026
ce6fd60
feat(home): wrap headings in Hijackable for sitewide interference
elfensky May 23, 2026
fdfe91e
feat(stats): wrap headings in Hijackable
elfensky May 23, 2026
5da362a
feat(legal): wrap headings in Hijackable
elfensky May 23, 2026
9fd7e56
feat(brandkit): wrap section headings in Hijackable
elfensky May 23, 2026
aad0e3e
feat(sign-in): wrap heading in Hijackable
elfensky May 23, 2026
536462c
test(ministry): add Playwright e2e for hijack overlay lifecycle
elfensky May 23, 2026
4ef57c3
test(ministry): replace Playwright spec with Vitest smoke tests for S…
elfensky May 23, 2026
c7a86ec
chore(archives): drop unused isDefeat + getWarOutcome import
elfensky May 23, 2026
ef63e07
chore(ministry): final review cleanups (dead stubs, visibility test, …
elfensky May 23, 2026
7d76529
chore(release): 0.50.0 — Ministry Interference sitewide easter egg
elfensky May 23, 2026
c2a4867
Merge feature/ministry-interference — Ministry Interference v0.50.0 (…
elfensky May 23, 2026
d6108df
Document worktree-based feature workflow and adjust Git rule #1
elfensky May 23, 2026
e79d4c2
Merge develop into feature/cascade-failure-log
elfensky May 23, 2026
cce5b86
feat(analytics): add findAllCascades alongside findWorstCascade
elfensky May 23, 2026
355564d
refactor(archives): remove findWorstCascade and WORST_CASCADE stat card
elfensky May 23, 2026
7f9e235
feat(db): add getCascadeLeaderboard cross-season query
elfensky May 23, 2026
d24d813
feat(timeline): add groupCascadesBySeason helper
elfensky May 23, 2026
cc63abf
feat(stats): add generateCascadeLede
elfensky May 23, 2026
2d5e1a1
feat(timeline): add useCascadeLogSort hook
elfensky May 23, 2026
005cda8
feat(timeline): add CascadeLogSortToggle button
elfensky May 23, 2026
825e82e
feat(timeline): add CascadeLogCard
elfensky May 23, 2026
0046412
feat(timeline): add CascadeLog component
elfensky May 23, 2026
fbfe5ed
feat(stats): wire CascadeLog into /stats page
elfensky May 23, 2026
4e55ae3
feat(archives): render CascadeLog below the StatGrid
elfensky May 23, 2026
c527f31
refactor(timeline): import compareCascades in groupCascadesBySeason
elfensky May 23, 2026
98b919e
chore(release): 0.51.0 — cascade failure log
elfensky May 23, 2026
d161732
Merge pull request #397 from elfensky/feature/cascade-failure-log
elfensky May 23, 2026
3f21c30
refactor(auth): extract shared session/user/admin guards
elfensky May 23, 2026
317cb7c
docs(changelog): record auth helper extraction
elfensky May 23, 2026
b2a85aa
Merge pull request #407 from elfensky/feature/auth-helpers-extraction
elfensky May 23, 2026
ba42913
feat(types): add z.infer exports + shared enum literal typedefs
elfensky May 23, 2026
8d393fd
refactor(types): tighten widened JSDoc across event + live-data paths
elfensky May 23, 2026
2e03dbb
docs(changelog): record type-safety validator inference + JSDoc tight…
elfensky May 23, 2026
7135e85
Merge pull request #408 from elfensky/feature/type-safety-validator-i…
elfensky May 23, 2026
2169601
refactor(rebroadcast): extract reconstruction logic into db/queries/
elfensky May 23, 2026
1131e7f
feat(season): export SEASON_NOT_FOUND sentinel; fix(rebroadcast): ret…
elfensky May 23, 2026
6437b2d
docs(changelog): record rebroadcast consolidation + SEASON_NOT_FOUND …
elfensky May 23, 2026
ffb110e
Merge pull request #409 from elfensky/feature/rebroadcast-consolidation
elfensky May 23, 2026
a27fd01
docs(jsdoc): add @param descriptions for CascadeLog.props and seasonA…
elfensky May 23, 2026
1e421a4
test: rename per-file makeFactionMap helpers to reflect their shapes
elfensky May 23, 2026
39d99e1
refactor(catch): annotate two intentional empty catches with diagnost…
elfensky May 23, 2026
a5ce58e
docs(changelog): record mechanical cleanup of #399, #400, #401
elfensky May 23, 2026
cdcd9d1
Merge pull request #410 from elfensky/feature/desloppify-mechanical-c…
elfensky May 23, 2026
1852f93
refactor: split src/db/queries/ — relocate boundary helpers + remove …
elfensky May 23, 2026
23b4bb3
refactor(admin): consolidate admin server actions into features/admin…
elfensky May 23, 2026
b270eee
refactor(account): consolidate API-key + user-lifecycle actions into …
elfensky May 23, 2026
bb581af
docs(changelog): record db/queries/ responsibility split
elfensky May 23, 2026
d0db345
Merge pull request #411 from elfensky/feature/db-queries-actions-split
elfensky May 24, 2026
3a9289a
refactor(validators): unify on direct-export shape across all 5 isVal…
elfensky May 24, 2026
bdd593c
docs(changelog): record validator export unification
elfensky May 24, 2026
0d2444f
Merge pull request #412 from elfensky/feature/validator-protocol-unif…
elfensky May 24, 2026
6372b1a
fix(account): Zod input, transaction caps, reorder revoke/delete, sur…
elfensky May 24, 2026
385c6fd
fix(admin): adminRevokeApiKey arity, TOCTOU transactions, check.data,…
elfensky May 24, 2026
11d22d8
fix(api): validateApiKey returns DB_ERROR distinct from INVALID; rout…
elfensky May 24, 2026
cc91d67
fix(authGuards): drop unnecessary 'use server' directive
elfensky May 24, 2026
0e275d7
docs(data-flow): fix isValidStatus/isValidSeason call form in code sa…
elfensky May 24, 2026
ff9c67e
chore(release): 0.51.7 — code-review-driven bug sweep
elfensky May 24, 2026
82cbc41
Merge feature/code-review-fixes — 15 bugfix sweep from max-effort cod…
elfensky May 24, 2026
5d112f5
refactor(ministry): promote forceHijack from dead window hook to cont…
elfensky May 24, 2026
d343ab1
feat(admin): floating "Trigger Ministry" widget for on-demand easter-…
elfensky May 24, 2026
9ade215
chore(release): 0.52.0 — admin Ministry trigger widget
elfensky May 24, 2026
65c6620
Merge feature/admin-ministry-trigger — admin Ministry-trigger widget …
elfensky May 24, 2026
c296942
docs(superpowers): spec for Ministry Interference v2 — per-component …
elfensky May 24, 2026
17d364c
Merge chore/ministry-v2-spec — Ministry Interference v2 design doc
elfensky May 24, 2026
ea68b25
fix(docker): chown runtime image by numeric uid to fix EACCES on .nex…
elfensky Jun 10, 2026
717ee43
Merge bugfix/next-cache-eacces — fix EACCES on .next/cache (0.52.1)
elfensky Jun 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/metrics.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
steps:
# https://github.com/lowlighter/metrics/tree/master/source/plugins/pagespeed
- name: 'metrics: pagespeed'
uses: lowlighter/metrics@v4
uses: lowlighter/metrics@v3.34
with:
token: NOT_NEEDED
committer_branch: metrics
Expand Down
161 changes: 161 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

27 changes: 25 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,29 @@ After any frontend/CSS change, verify via DevTools before declaring done:
- For grid/flex: check parent-child sizing chain
- For interactive changes: programmatically trigger state changes and verify DOM updates

## Worktree Workflow

Features use an isolated git worktree off `develop`; small chores commit directly on a branch (no worktree). Both still follow the rules in § Git Workflow.

**When to use a worktree (features):** new functionality, multi-file refactors, anything large enough to warrant a PR, anything that benefits from isolation while iterating. Default for any task you'd otherwise raise a feature branch for.

**When to skip the worktree (small chores/bugfixes):** dependency bumps, `npm audit` fixes, doc edits, lint/format passes, copy tweaks, single-call-site bugfixes, CLAUDE.md/CHANGELOG updates. Branch from `develop` in the main checkout, commit, merge with `git merge --no-ff` per § Git Workflow. Use judgment; if unsure, default to a worktree.

**Feature workflow (worktree):**

1. Create the worktree off `develop` (run from the main checkout):
`git worktree add .worktrees/<branch-dir> -b feature/<desc> develop`
2. Copy gitignored env files from the main checkout: `cp ../../.env.development .` (and any `.env.local` if present — `*.env*` is gitignored, so the dev server can't boot without them)
3. Install dependencies in the worktree: `npm install && npx prisma generate` (Prisma client outputs to `src/generated/prisma/` which is gitignored, so it must be regenerated per worktree)
4. Do the work in the worktree directory — small, logical commits as you go, not one giant commit at the end
5. Verify in the worktree: `npm run lint`, `npm run typecheck`, `npm run test:unit`, `npm run build` (all four must pass — same chain as § Critical Rules)
6. Merge back from the main checkout: `git checkout develop && git merge --no-ff feature/<desc>` — include the version bump + CHANGELOG move into `## X.Y.Z` in the merge commit per § Git Workflow rule #2
7. Push `develop`, then clean up: `git worktree remove .worktrees/<branch-dir>` + `git branch -d feature/<desc>`

**Worktree directory:** `.worktrees/` in project root (already gitignored). Directory names mirror the branch with slashes replaced by hyphens (e.g., `feature/ministry-interference` → `.worktrees/feature-ministry-interference`).

**Prisma migrations:** If the branch creates a migration under `prisma/migrations/`, remind the user to run `npx prisma migrate deploy` against the local database after merging, before the next dev-server restart.

## Git Workflow

**Branching model:** Simplified Git Flow — no release branches.
Expand All @@ -54,7 +77,7 @@ After any frontend/CSS change, verify via DevTools before declaring done:
**Rules:**

0. **Never squash merge. Never fast-forward merge.** Always use `git merge --no-ff` so every merge creates a merge commit and the branch boundary stays visible in `git log --graph`. Never `--squash`, never `--rebase`, never `--ff-only`.
1. **Create feature/bugfix/chore branches from `develop`.** Features merge back via PR. Bugfix and chore branches merge via `git merge --no-ff` directly into `develop` (branch → commit → `git checkout develop && git merge --no-ff <branch>` → push → delete branch). No PR needed.
1. **Create feature/bugfix/chore branches from `develop`.** Features use a worktree (see § Worktree Workflow) and merge back via PR. Bugfix and chore branches skip the worktree and merge via `git merge --no-ff` directly into `develop` (branch → commit → `git checkout develop && git merge --no-ff <branch>` → push → delete branch). No PR needed.
2. **Version on merge to `develop`:** When merging a branch into `develop`, **in the same commit** move its changelog entries from `## Unreleased` into a new `## X.Y.Z` section and bump `"version"` in `package.json` to match. Do not defer this to a separate commit or ask — it is part of the merge step. Use semver: patch for bugfixes, minor for features, major for breaking changes. Skipping version numbers between releases is fine — not every version on `develop` will be tagged on `main`.
3. **Release process:** Merge `develop` → `main` via PR → **tag `vX.Y.Z` on the merge commit on `main`** (use the latest version from `CHANGELOG.md`) → push tag → **merge `main` back into `develop`** (`git checkout develop && git merge origin/main && git push`). The production Docker build only triggers on version tags, so forgetting to tag means no deployment. The merge-back carries main's PR merge commit into develop so the next release PR doesn't trip the "branch not up to date" check.
4. **Hotfix process:** Cut `hotfix/X.Y.Z` from `main` → fix → update `CHANGELOG.md` with new version section → PR to `main` → tag `vX.Y.Z` → merge back to `develop`
Expand Down Expand Up @@ -142,7 +165,7 @@ All visual properties use CSS custom properties defined in the Tailwind v4 `@the
- **Error tracking (optional):** Sentry SDK configured for self-hosted GlitchTip (`tracesSampleRate` 0.1 in production / 1.0 in dev, `environment` tagging, no replays/logs). Client tunnel (`/api/glitchtip`) bypasses ad blockers. CSP violations reported via `report-uri`. Route-level (`error.jsx`) and component-level (`ComponentErrorBoundary`) error boundaries for graceful degradation. When `SENTRY_AUTH_TOKEN` absent, `withSentryConfig` build plugin skipped.
- **Node version:** mise pins node@24 (ships with npm 11 natively).
- **Server actions:** Most utilities use `'use server'` directive.
- **Shared utilities:** `formatNumber` (`src/shared/utils/format/formatNumber.mjs`) for compact numbers (25.0M, 1.2K — M suffix at 10M+, locale grouping below). `formatTimeAgo` (`src/shared/utils/format/formatTimeAgo.mjs`) for relative timestamps.
- **Shared utilities:** `formatNumber` (`src/shared/utils/format/formatNumber.mjs`) for compact numbers (25.0M, 1.2K — M suffix at 1M+, locale grouping below). `formatTimeAgo` (`src/shared/utils/format/formatTimeAgo.mjs`) for relative timestamps.
- **Map state:** `computeMapState` (`src/shared/utils/game/computeMapState.mjs`) computes galaxy map sector ownership. Sectors 1-10 from campaign `points`/`points_max`; region 11 (homeworld) from attack events only. **Critical:** live views must only pass active events — use the `computeLiveMapState(data)` helper from the same module to keep the filter and the call together.
- **On-demand season fetching:** `/archives` page derives SeasonSelector from current season number (not DB query). Missing seasons are backfilled from the official HD1 API on first request via `updateSeason()` (`src/update/season.mjs`) -- the same shared pipeline the worker runs every poll for the active season and the admin "Refresh" button triggers via `reseedSeason`. `updateSeason` writes `h1_season` (with inlined arrays) + `h1_status` + `h1_statistic` + `h1_event` + `h1_event_progress`, then stamps `h1_season.last_updated`.
- **Live polling:** `useLiveData` hook (`src/shared/hooks/useLiveData.mjs`) polls `GET /api/h1/live` every 10 seconds via `setInterval` + `fetch`. A `visibilitychange` listener fires an immediate poll on tab focus. Tri-state status: `'polling'` (request in flight), `'live'` (last poll succeeded), `'offline'` (last poll failed or PWA offline). Module-level singleton ensures one connection per tab. BroadcastChannel leader election for Web Notifications.
Expand Down
15 changes: 12 additions & 3 deletions Dockerfile.app
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,18 @@ ARG NEXT_PUBLIC_DEPLOY_ENV
ENV NEXT_PUBLIC_DEPLOY_ENV=$NEXT_PUBLIC_DEPLOY_ENV
# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nonroot:nonroot /app/.next/standalone ./
COPY --from=builder --chown=nonroot:nonroot /app/.next/static ./.next/static
COPY --from=builder --chown=nonroot:nonroot /app/public ./public
#
# Chown by NUMERIC uid:gid, NOT the name `nonroot`. This Chainguard runtime
# has no `nonroot` entry in /etc/passwd (uid 65532 is named `node`), so
# `--chown=nonroot:nonroot` silently falls back to root (0:0). That left
# /app/.next root-owned and made the Next.js image optimizer's runtime
# `mkdir('.next/cache/images')` fail with EACCES, flooding logs with
# unhandledRejection on every remote-avatar (Discord/GitHub/Google/Gravatar)
# optimization. Numeric IDs need no passwd lookup, so 65532 (the runtime
# user) owns the tree as intended and the cache dir is created on demand.
COPY --from=builder --chown=65532:65532 /app/.next/standalone ./
COPY --from=builder --chown=65532:65532 /app/.next/static ./.next/static
COPY --from=builder --chown=65532:65532 /app/public ./public
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
Expand Down
Loading