Commit Graph
9 Commits
Author SHA1 Message Date
mandaloreandClaude Sonnet 5 52402d7547 Color each opponent's column by their disposition
Colors read directly from the template's own "Disposition Matrix"
section (Template!B32 header, B33:H37) via its actual XLSX cell fill
colors (not visible through CSV export, and not guessed) — a clean
1:1 mapping in the row/column headers, same 5 canonical dispositions
and spelling as CANONICAL_DISPOSITIONS in main.js:
  Purge the Foe -> #E06666, Take and Hold -> #93C47D,
  Priority Assets -> #FFD966, Disruption -> #4A86E8,
  Reconnaissance -> #76A5AF
(the interior of that section is a separate disposition-vs-disposition
matchup heatmap, not used here).

Whole opponent column (list/faction/name rows) gets colored, per the
requested scope. Needs the new tab's sheetId, which duplicateSheet's
reply already includes (only .title was being read out of it before) —
sent as its own batchUpdate once that's known, since a single
batchUpdate can't both create a sheet and reference the id it produces.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
2026-09-04 19:00:26 -05:00
mandaloreandClaude Sonnet 5 30276c7ad5 Fix three matrix generation bugs found in a real generated sheet
Diagnosed directly against a real generated spreadsheet the user
shared, rather than guessing:

- Player/opponent names came out blank ("- Necrons" instead of "John
  Smith - Necrons"): fullName() takes the nested BCP user object
  ({firstName, lastName}), not the roster/teamplayer entry itself —
  same convention main.js's own playerRowHTML() already follows, now
  matched here via a shared nameOf() helper (with the same nickname
  fallback playerRowHTML uses too).

- That cascaded into a worse, harder-to-spot bug: with the name blank,
  a win-rate cell became literally " (62.5%)". Writes used
  valueInputOption: USER_ENTERED, which smart-parses values — Sheets
  read "(62.5%)" as accounting notation for a negative number and
  silently replaced the cell with e.g. "-62.50%" instead of the
  intended text. Same setting explains the "#ERROR!" cells: some raw
  list text got parsed as a formula. Switched to RAW, which stores
  exactly what's sent — correct for the plain descriptive labels this
  writes, never intended to be interpreted.

- Lists with a New Recruit "+++...+++" header came through raw,
  uncleaned: parseNRGW/parseNRTournament were vendored (needed for
  NR_GW/NR_TOURMENT detectFormat() results) but never actually wired
  into the format dispatch table, and aren't re-exported from the
  package's own index.js (only from the internal modules/parsers.js,
  which is now imported directly). Verified against the tool's own
  NR-GW/NR-Tournament sample fixtures with real Node before committing
  — both now correctly detected and cleaned.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
2026-09-04 18:26:57 -05:00
mandaloreandClaude Sonnet 5 5bb6268080 Fix Generate Matrix 404: drive.file can't see the pre-existing template
drive.file scope only grants access to files the app itself creates —
it deliberately can't see a file it didn't create, even one the
signed-in user can otherwise view. Drive's API returns 404 (not 403)
for a file outside the token's grant, which is indistinguishable from
a bad file ID — that's what surfaced as "File not found:
1tSG1BxqLwmMtwFnaHozaLqVsKlmk_QsCXRorIxQBcMo" on the very first API
call (confirmed nothing was created in Drive yet, matching a rejected
initial copy rather than a later step).

Added drive.readonly alongside drive.file: readonly covers reading
the pre-existing template to copy it, drive.file continues to cover
the generated copies and everything written to them afterward.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
2026-09-04 18:18:26 -05:00
mandaloreandClaude Sonnet 5 06b0cf4418 Add Generate Matrix: auto-build the team scouting matchup matrix
Duplicates the team's Google Sheets matchup-matrix template once per
opposing team at a loaded event, pre-filled with names, factions,
career win% (all already computed by this page), and cleaned army
lists. New "Which team is yours?" selector + Generate Matrix button,
team-events only.

- Auth: Google Identity Services token client (drive.file +
  spreadsheets scopes) — the viewer signs in with their own Google
  account via a real OAuth popup, no backend, no client secret.
- Template layout is located at runtime by searching each tab for its
  own placeholder text ("Player N - Faction", "Opponent N") rather
  than hardcoded A1 ranges, so it survives the template changing later.
  Distinguishes the real input area from the template's own "mobile
  matrix" mirror and reference sections (which repeat the same
  placeholder text) by checking for literal typed values vs formulas —
  CSV export can't tell these apart, only the real Sheets API response
  can, which is how this was actually verified before writing this.
  Auto-picks between the template's 5-man/8-man tab variants by team
  size.
- List cleaning via a vendored copy of desjani's 40k-compactor
  (src/vendor/40k-compactor/, see NOTICE.md) — it isn't actually
  published to npm despite documenting `npm install 40k-compactor`
  (verified: 404 against the registry under every plausible name), so
  it can't be a normal dependency; vendored instead, MIT per direct
  confirmation from the maintainer. Verified against 40k-compactor's
  own sample list fixtures with real Node before committing.
- Generating a matrix needs the same BCP auth token already used for
  faction/disposition backfill (list text is a subscriber-gated
  endpoint) — reuses the existing token UI/storage as-is; missing
  lists are reported, not a hard failure.
- nginx CSP updated (script/connect/frame-src) for Google's identity
  script and the Drive/Sheets REST APIs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
2026-09-04 18:09:48 -05:00
mandaloreandClaude Sonnet 5 58d930f289 Add events.gateway-gamers.net: upcoming events across BCP circuits
New 5th site in the monorepo, following the same shared header/footer/
images pattern as the others. Shows upcoming events for a configurable
list of BCP circuits (CONFIG.circuits in sites/events/src/main.js) —
adding a circuit is a one-line edit.

Data comes from the BCP API (`GET /v1/events?leagueId=<circuitId>`,
same auth-free headers the other sites already use) — "circuit" in
BCP's frontend URLs maps to `leagueId` in the API, confirmed via
/v1/leagues/<id> returning circuitLeague: true. Paginates newest-date-
first and stops once a full page has entirely concluded, so it doesn't
walk a circuit's whole history just to find the handful of upcoming
events. Verified against the live API for all 4 configured circuits
before writing this commit.

Nav cross-links with scouting only (per request), not the other 3
sites. nginx conf follows the same shared-asset-alias pattern as the
rest; TLS/DNS for the new subdomain still need to be provisioned
before it can go live (see follow-up).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
2026-09-04 15:55:25 -05:00
mandaloreandClaude Sonnet 5 d0fee77bae Fix broken header styling: stop gitignoring generated header.html
Tailwind v4's automatic content detection skips gitignored files, and
public/header.html was gitignored (generated) — so every header-only
utility class (hero-carousel, navbar-center, swap-rotate,
menu-horizontal, drop-shadow-2xl, etc.) got purged from the compiled
CSS on the last deploy. The header markup was correct, just entirely
unstyled.

Fixed two ways for certainty: added an explicit @source in each
site's style.css (Tailwind's documented way to force-include a
gitignored path), and also stopped gitignoring public/header.html
so automatic detection picks it up regardless — matching how the
original per-site repos worked before this generated a real file.
predev/prebuild still regenerate it on every dev/build, so it can't
go stale.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
2026-09-04 15:39:36 -05:00
mandaloreandClaude Sonnet 5 997e7dcd0b Sync nginx confs to match live production + add shared-asset aliases
The committed .conf files were stale pre-certbot templates that no
longer matched what's actually running on gg1-roc-web1 (certbot --nginx
had rewritten 40k-rankings/aos-rankings/kingmaker's structure, and in
the process 40k-rankings and aos-rankings lost their security headers
entirely). Replaced each with the true live config, plus the new
location /images/ and location = /footer.html alias blocks that were
applied directly on the server.

Also fixed on gg1-roc-web1 while there (not reflected in this repo,
just noting for the record):
- kingmaker's TLS cert had expired 2026-08-26; renewed.
- certbot-renew.timer was enabled but never started (box hasn't
  rebooted in 172 days), so nothing was auto-renewing; started it and
  ran a full certbot renew, which also caught 40k-rankings/aos-rankings
  sitting inside their renewal window.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
2026-09-04 15:18:37 -05:00
mandaloreandClaude Sonnet 5 0ab611b6fc Consolidate gateway-gamers sites, dedupe shared header/footer/images
Merge the four independent gateway-gamers.net site repos (40k-rankings,
aos-rankings, kingmaker, scouting) into one monorepo under sites/,
preserving each site's full commit history via a manual subtree-style
merge (git-subtree isn't available in this environment).

- shared/: header.template.html (nav swapped in per site), footer.html,
  and images/ — previously byte-identical copies duplicated 4x (~100MB).
- Each site's public/footer.html and public/images are now symlinks into
  shared/; public/header.html is generated at predev/prebuild time from
  shared/header.template.html + the site's own nav.html, so nav bars stay
  individual while everything else about the header is shared.
- Single root .gitlab-ci.yml with path-scoped build/deploy jobs per site
  plus a deploy:shared job, replacing the 4 separate per-repo pipelines.
- nginx confs (kept per-site as reference, same as before) gain
  location /images/ and location = /footer.html alias blocks pointing at
  a shared webserver directory, so the deployed site no longer duplicates
  the images either — see README.md for the full layout and the runbook
  for applying this on the webserver.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBzcNkW7JcYipnAX6HfgmK
2026-09-04 14:59:06 -05:00
mandalore 60076ac885 Import scouting.gateway-gamers.net into monorepo 2026-09-04 14:55:10 -05:00