Repository navigation
Phases 4–5.1: Ads live, Discord community, ParseForge bot (LootList+ parity) + cleanup - #17
Open
alexandermayes wants to merge 288 commits into
Open
alexandermayes wants to merge 288 commits into
alexandermayes wants to merge 288 commits into
Conversation
…0 gaps, 3 manual-only observed
…CT.md and STATE.md
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Seven plans for Phase 3 (Share Loop), tracer-first across five waves: awards engine + OG branch tracer, pool expansion, player permalink receipts, analyze-page share surfaces, the protected-elements gate, preview review, and the approval-gated production close-out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…teMetadata - Add lib/awards-engine.ts: pure computeAwards() over RaidOverviewResult with a 5-rule seed pool (first-to-die, top-dps, top-hps, flaskless, best-prepared) and lib/awards-engine.test.ts (13 tests) - Add AwardWinner/AwardTone/AwardRow/AwardsResult types to lib/wcl-types.ts - Extend app/og/route.tsx with a view=awards branch (AwardsCard) that POSTs /api/raid-overview + GETs /api/report, falling back to the existing ReportCard on any failure or fewer than MIN_AWARDS_FOR_CARD fired awards - Forward view into the OG URL from generateMetadata, canonical stays param-free Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- buildReportShareUrl/buildPlayerShareUrl/buildAwardsShareUrl build normalized, tab-free permalinks with the correct ref value (D-09/D-16) - buildAwardsOgPath builds the relative /og preview path with no ref - parseShareRef allowlists ref to exactly share|parse|awards (RESEARCH Pitfall 5) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Append graveyard-shift, gcd-tourist, fire-dancer, naked-slots, skipped-breakfast, dull-blade, iron-man, watering-the-garden, kept-them-breathing and punching-up to AWARD_POOL at priorities 6-15, each reading only RaidOverviewResult fields (D-03) - Add a median() helper shared by fire-dancer's and punching-up's threshold checks - Extend lib/awards-engine.test.ts with a fires/does-not-fire case per new rule plus a fifteen-rule fixture proving every rule fires and the card caps at the six lowest priorities - Adjust the pre-existing ordering/cap fixture so the new iron-man and punching-up rules cannot bleed into a test scoped to the original 5-rule tracer pool
- Add an AWARD_POOL pool-wide invariants describe block: unique ids, contiguous priorities 1-15, non-empty title/icon on every rule, tone restricted to praise|jab - No-unconditional-firing test loops AWARD_POOL against a minimal all-zero fixture with an explicit allowlist (top-dps, best-prepared) so a future rule that fires on an empty fight fails the suite - Every fired row across a representative set of fixtures in the suite carries a non-empty stat and 1..MAX_WINNER_NAMES winners, capped at MAX_AWARDS_SHOWN - Stable-sort and non-mutation (structuredClone snapshot) assertions on computeAwards
Records the shipped fifteen-row award pool verbatim for the developer's D-04 tone review at the 03-06 preview gate.
…eipts to the player OG card - Player branch now Promise.all's /api/analyze + /api/report to resolve the fight outcome, degrading to a null outcome on any meta-fetch failure - PlayerCard renders a Kill/Wipe pill, fight length (mm:ss), a vs-top-N comparison label, and a healer (effective HPS + overheal) or DPS (Active Time) proof line, reading only data.healer and the metricPercentiles activeTime entry — no arithmetic of its own Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…scorecard
- ComparisonSummary now takes reportCode/fightId/sourceId and builds one normalized playerShareUrl via buildPlayerShareUrl — no share surface in this component reads window.location.href anymore
- Share my parse (default variant, data-protected="share-player") copies the ref=parse permalink and fires share_action{kind:player_link}; only renders when fightId and sourceId are known
- Copy for Discord (outline variant, data-protected="share-discord") stays secondary, keeps discord_copied, and now also dual-emits share_action{kind:discord_text}
- AnalysisView forwards reportCode/fightId/sourceId down to ComparisonSummary, no new fetch
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…-and-share-my-parse plan
- Widen RaidOverviewProps with reportCode, fight and openAwards - Add AwardsPanel (Copy awards link button, award rows, inline /og image) computed via computeAwards over the in-memory RaidOverviewResult - Panel renders above Raid Buff Coverage so its button sits above the fold on mobile (D-13); scrolls into view once when openAwards is true Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- handleShareLink now builds from buildReportShareUrl(origin, ...) instead
of reading the live address bar; dual-emits share_action{kind:report_link}
alongside the legacy share_link_copied
- Header Share button carries data-protected="share-header"
- Removed the bottom glass "Found this useful?" share bar entirely
- RaidOverview call site now passes reportCode and the selected fight entry
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Initial tab defaults to Player when source is present and tab is absent (explicit tab still wins); awards-view visitors keep the Raid default and the panel scrolls itself into view (D-12) - One mount effect, guarded by a ref so it runs exactly once, resolves ref via parseShareRef, captures share_landing with the allowlisted value only, then strips ref from the address bar via updateUrlParam (D-16) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…face - docs/PROTECTED-ELEMENTS.md lists all 6 shipped data-protected attributes (share-header, share-player, share-discord, awards-panel, awards-preview, share-awards), the /og route and URL contracts, the enforcement mechanism, and the change procedure - Every attribute value verified present in its owner file before being recorded - Names Phase 4 (ad-placement whitelist) and Phase 7 (per-route SEO gate) as the consuming phases
…he checklist - Plain ESM, no new dependency; mirrors seo-invariants.mjs/token-audit.mjs header shape and exit-code convention (0/1 gate, --report always 0 on check failures, 2 on fatal) - Parses docs/PROTECTED-ELEMENTS.md's DOM-attributes table and confirms each data-protected value is present in its owner file - Fetches the live awards/player/report /og cards and the analyze canonical against a resolved --base (default production) - Missing/empty checklist, a nonexistent owner file, or an unreachable base are all fatal (exit 2) rather than a silent pass - npm run protected-elements script entry added alongside seo-invariants and token-audit - Verified: 10/10 checks pass against production; a synthetic bad row makes the gate exit 1; an unreachable base makes it exit 2; eslint clean
…S-01 + roadmap - OPS-01 Part 1 item 4 gains a share-rate HogQL sub-block (D-14): distinct sessions with share_action over distinct sessions with analysis_complete, vs the ~2.8% baseline; notes share_link_copied/discord_copied keep dual-emitting this phase - OPS-01 Part 1 gains item 8, Protected elements: npm run protected-elements is a gate item, a fatal exit is never a pass, and it's named as a hard input to Phase 4 and Phase 7; also records the two carried-forward open items (dpl_CDCu1FVfPZcd8dHr4RpLcrHWJcJ5 unsigned item-7 row, GSC crawled-not-indexed investigation) as open, not resolved - Items 1-7 untouched — purely additive edit (0 lines removed) - ROADMAP.md Phase 4 Depends-on/Notes and Phase 7 Notes now name docs/PROTECTED-ELEMENTS.md explicitly
- Records the protected-elements checklist, the machine-check script, and the OPS-01/ROADMAP wiring completed by this plan
…ntent
- scripts/build-bot-data.mjs: second generator extracts bot/src/generated/
help-seed.ts from app/guides/*/page.tsx (5 guides), app/tbc-audit/page.tsx's
FAQ (6 items) and docs/DISCORD-SEED.md's four named copy blocks — JSX/HTML
stripped to plain prose (headings, list items, paragraph reflow), entities
decoded, no className=/tags/braces left, 200-8000 chars, fails loudly on a
thin extraction; --check now covers both generated files
- bot/src/help-kb.ts: seedHelpArticles upserts every seed as a published
seed-{slug} article (sha256 content-hash dedupe), archives a seed no
longer in the list, never touches pending drafts
- bot/src/index.ts: calls seedHelpArticles on ClientReady when the store is
configured, in its own try/catch
- 7 new tests in bot/src/help-kb.test.ts (generated-seed shape + idempotent
seed/update/archive); root lib/bot-data.test.ts still passes; bot
tsc/build/lint and root tsc/test clean
- bot/src/help-kb.ts: queueDraftArticle (pending_review, sanitised origin question, no asker id), setReviewMessage/articleIdForReviewMessage, publishArticle (slugify + pending->published), archiveArticle - bot/src/help.ts: a novel answer's draft is now queued after the member's own reply is sent — posts the review embed to OPS_CHANNEL_ID, reacts ✅ then 🗑️, records the review message, logs help_draft_queued; every step soft-fails - bot/src/help-review.ts (new): handleHelpReviewReaction ports LootList+'s help-review.js onto the Redis KB — Developer-role-gated, OPS_CHANNEL_ID -scoped, only acts on a still-pending_review mapped article; publish/ archive edits the embed footer to the Zev-signed line - bot/src/index.ts: second messageReactionAdd listener, its own try/catch - 8 new tests in bot/src/help-kb.test.ts (draft queue, review publish/ archive, non-Developer/bot/wrong-emoji/wrong-channel refusals, listing); bot suite 130/130, root 518/518, no site file touched
… gate - scripts/discord-bot.mjs: feedback-forum --open and archive-channel subcommands (dry-run by default, --execute to write), both refusing on the wrong channel type; no subcommand deletes a channel. preflight gains feedback_archive and feedback_forum's public: yes|no. - docs/DISCORD-SEED.md: #feedback-archive topic copy. - docs/DISCORD.md: #feedback-archive and #ops channel rows, ### Limits (D-16) with the chosen cooldown/cap numbers and override vars, full metric-name list through plan 05.1-07, and the help-KB/spend-guard Redis keys under ### Data the bot keeps. - docs/OPS-01-SHIP-GATE.md Part 8: What-shipped rows for plans 05.1-04 to 05.1-07, and #### Local gate — deploy 2 with this run's exit codes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Records Task 2's checkpoint:decision outcome in Part 8 deploy-2 — the developer's verbatim "go live" choice at 2026-10-02T17:45:05Z (archive, not delete, the old #feedback channel), the forum-permission side finding (the private feedback forum was already open to @everyone before this deploy, per the developer's own prior manual change), and a doc-drift note on the Developer role's display name. Deployment id, commit, and variable names are left for Task 3. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Deploy 2 (developer-run, deployment 5bd7e083-f993-46cb-b034-4a10958ebf78, commit a1f48ad) confirmed bot_ready/help_seeded/backfill_run. Executed the two authorized channel changes: feedback-forum --open (D-11, forum now public) and archive-channel --id 1552413587094376620 --name feedback-archive (read-only, never deleted). Recorded Part 8 deploy record/evidence, the forum's opening date and role-name drift in docs/DISCORD.md, and the guidelines-applied note in docs/DISCORD-SEED.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- scripts/discord-bot.mjs: set-interactions-endpoint accepts --clear (prints requested: none; --execute sends an empty string, falls back to null on a 400, reports which form Discord accepted) alongside the existing --url form - docs/OPS-01-SHIP-GATE.md Part 8: #### Cutover plan — deploy 3 records the D-05/D-08 relay steps a-e, who runs each, the one-command rollback, and the pre-cutover gate outputs (tsc/lint/518 root + 130 bot tests/commands list/preflight/sitemap, all clean) — verify prints cutover-prep-ok Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Developer chose "cut over" (claude-opus-5 for /help) at 2026-10-02T21:29:40Z after the relay, rollback, cost trade-off, and site-diff context was presented. Phase 5 UAT test 19 recorded as superseded by this plan's Task 3 step-c tap test rather than run independently. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
BOT_INTERNAL_API_KEY added to Vercel Production (sensitive, piped from a mode-600 scratch file, confirmed by name only). The production deploy command was denied by the harness's auto-mode classifier before it ran; no new deployment exists. Stopped per the plan's own guidance rather than working around the block — probes, scratch-file cleanup, clipboard copy, and Task 3 step b are all withheld until the developer runs the deploy. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…cleared Production deploy (dpl_6FwgsubwLVrb96ZdfbTDSPSESavF) was run by the developer after the harness blocked Claude's attempt; records the deployment id/url/time, all four public-page probes (200), both bot-lane probes (valid key -> 400 "Missing or invalid caller id."; no key -> different 400, confirming the caller-id gate), the pre-existing build-log observation, the clipboard/header- file handling, and the kept-key-file deviation (header file deleted, key file kept pending the Railway paste at step d). Step b: `set-interactions-endpoint --clear --execute` cleared the Interactions Endpoint URL (current: none, confirmed via dry run and preflight). Every interaction now routes to the gateway bot; the HTTP route and its rollback command are untouched. Step c (the real tap test) is next, awaiting the developer. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Task 3 steps c-e of the D-05 relay.
- Step c: the developer's real tap test passed on the gateway path
("Tapped, toggled fine", "Good to move on").
- Re-key: step a's key never reached Railway (clipboard and kept key file
both gone), so a fresh key was generated; the developer swapped it into
Vercel Production and redeployed (dpl_DoWDyuJTU1ntLGJVBqamQ8F992XH,
Claude's own secret-store write was blocked). All probes re-passed.
- Step d: three Railway variable redeploys (a colon-suffixed variable
name, then a missing COMMANDS_ENABLED) ending at a1a7e859 with
commands_registered count=3; `commands list` shows guild: 3. Key parity
between Railway and Vercel verified (`keys match`); key file deleted and
clipboard cleared.
- docs/DISCORD.md `## Role picker` rewritten for the gateway bot, the
rollback route kept until 05.1-10, and the registered commands.
- Task gate printed cutover-ok (run by the developer).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SUMMARY for plan 05.1-09 (choice, model, both Vercel deployments, the three Railway redeploys, probes, endpoint before/after, tap outcome, commands list, key parity, deviations); ROADMAP 9/10; STATE advanced. Human-check (/analyze, /raid, /help in #log-review) recorded PENDING. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rate-limit bucket - Delete app/api/discord/interactions/route.ts(+test) and lib/discord-interactions.ts(+test); the gateway bot owns the picker since 05.1-09 (D-08) - Drop the interaction-only RATE_LIMITS["discord-roles"] bucket; isRateLimitedKey and checkCallerRateLimit stay - Reword scripts/discord-bot.mjs comments to point at bot/src/roles.ts and note the endpoint URL is cleared - lib/discord-roles.json untouched (bot's generated copy and the ops script read it) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Part 8 deploy 4 record: choice "retire route", the developer's words and the UTC time (2026-10-13T00:09:57Z) - Notes that the decision ran nothing; Task 3 still gates the deploy and the env removals on the developer's words Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Production deploy denied by the auto-mode classifier before it ran - vercel ls confirms no new deployment; both Discord secrets untouched Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Deploy dpl_GacDSHqUmFCPs1DrA2Q6qrMt8vfn live and aliased to parseforge.gg - Interactions route 404; sitemap, home, guide and demo report 200 - env ls names for production and preview; no env rm has run Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…se 5.1 sign-off - Part 8 deploy 4: step b words and commands, env ls before and after, preflight (endpoint none) and commands list (guild 3) - Sign-off: criteria 1-5, measurement per metric with no-data re-check, PostHog item 7 not applicable, SEO checks, open items incl. #ops flag - DISCORD.md Role picker: route and Vercel Discord secrets removed Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Status human_needed — 6/10 truths verified, 3 behavior-unverified in production, 1 uncertain (OPS-01 measurement is log-only). Flags #ops visibility (D-04), the never-closed PAT/Upstash rotation, and the pending human checks from 05.1-08/09/10. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
29 findings (1 critical, 19 warning, 9 info) across the 50-file phase diff. CR-01: /analyze and /raid silently fall back to the first pull (fights[0]) for fight=last or bare links, so wipes can be scored against kill parses. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…, when the link names no fight - add pickDefaultFight/describeFightPick (bot/src/commands/pick-fight.ts): last kill by highest fight id, last pull only when there are no kills - /raid uses the helper for a bare link and #fight=last, and discloses the pick in one line on success and error replies - log fight_pick (explicit | last_kill | last_pull) on command_run - tests for the helper and the /raid bare, fight=last, no-kill, explicit and error paths (CR-01) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ull, when the link names no fight - /analyze uses the shared pickDefaultFight/describeFightPick on both the player-analysis and raid-overview paths - pick note precedes the existing no-source note and any API error, so the member always sees which fight was tried - tests for fight=last, bare code, no-kill, player-not-in-fight, explicit-fight and no_fights paths - document the fight_pick command_run log field in docs/DISCORD.md (CR-01) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…last kill for fight=last or bare links Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Six uncovered behaviors get direct tests: the public sanitiser (D-10), closure thread prefixes (D-11), one anonymous log line per OPS-01 metric, the bot lane on the three site routes (D-16), bot/site URL parser parity (D-02) and the retired interactions route (D-08). Root 471→490, bot 150→166. VALIDATION.md: status validated, nyquist_compliant true, wave_0_complete true; WR-01…19 left to 05.1-11. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
64 threats: 41 closed, 14 partial, 6 open, 3 accept-pending. Blocking: T-05.1-13 (PAT + Upstash token exposed in 05.1-03, never rotated) and T-05.1-41 (/help never-guess and gameplay-redirect rules live only in the prompt — WR-05/06). New: lib/rate-limit.ts logs the Upstash error message including rl:bot-member:discord:<id>. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rity threats Seven tasks (6 code + Railway redeploy checkpoint) closing WR-01…WR-19, T-05.1-41/-10/-26/-06/-18/-33/-37/-02 and R-01…R-04. ROADMAP wave 10, 05.1 now 10/11. Credential rotation and #ops visibility stay human. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This branch carries Phase 4 (Ads Live) plus the still-unmerged Phase 2.1 and Phase 3 work that
has accumulated on
growth/phase-2-review-fixessince PR #16. Phase 4's own commits (04-01through 04-07 Task 1) are the focus of this PR:
pre-ad Core Web Vitals baseline, D-10 rollback trigger, D-08 rollback runbook, and the D-11/D-14
observation and escalation clocks into
docs/OPS-01-SHIP-GATE.mdPart 6.report-only CSP entries for Google ad hosts,
/ads.txt, and the ad-slot whitelist gate inscripts/protected-elements.mjs.parse-lensengine, and a Redis-backed WCL rate-budgetgate — tested, unwired, imported by nothing yet (R0-3, by design).
/tbc-auditand/analyze/[reportCode], rewrote/privacy's advertising and Warcraft Logs data-sourcedisclosure, and wired the AdSense account (unit ids, env vars, Auto ads confirmed off).
bug and an occluding unfilled-ad-frame visual mismatch, both confirmed by the developer's
first-hand review of the fourth preview.
approval request drafted in 04-01 was never sent, so there is no reply) and the developer's
explicit, twice-given, dated operator-override decision to proceed with the production ad
deploy without that approval on record, after being told the real consequence (RPGLogs could
revoke the WCL API client entirely). See
docs/OPS-01-SHIP-GATE.mdPart 6### Deploy decision (Phase 4, 2026-09-22)for the full record.Earlier commits on this branch (Phase 2.1's consent-gate hotfix, Phase 3's share loop) are
already deployed to production in prior sessions but have not yet been merged to
mainvia aPR — this PR brings
mainup to date with everything currently live plus Phase 4's work so far.Test plan
npx tsc --noEmitcleannpm test— full vitest suite passing (285/285 as of 04-06)npm run lint— no new findings (pre-existing debt incomponents/ui/meteors.tsxandlib/analysis-engine.tsleft as-is)npm run protected-elements— all rows passingof live traffic to measure)
🤖 Generated with Claude Code
Phase 5.1 — ParseForge Discord bot (LootList+ parity) + cleanup
Phase 5.1 is executed 10/10 (plan 05.1-11 gap closure added, not yet run). Highlights on this branch since the Phase 5 commits:
app/api/discord/interactions/*andlib/discord-interactions*; prod deploy 4 (dpl_GacDSHqUmFCPs1DrA2Q6qrMt8vfn) verified —POST /api/discord/interactions→ 404, sitemap/home/guide/demo report → 200;DISCORD_BOT_TOKENandDISCORD_PUBLIC_KEYremoved from Vercel Production. Sitemap pipeline files unchanged since 297cb98./analyzeand/raidnow score the last kill (highest fight id withkill) for#fight=lastor bare links, falling back to the last pull only when the report has no kills, and say which fight was picked. Newbot/src/commands/pick-fight.ts; parsers and site untouched. Needs a Railway redeploy to go live.05.1-VALIDATION.mdvalidated.05.1-VERIFICATION.md(human_needed, 6/10 verified, 0 failed),05.1-REVIEW.md(1 critical → fixed, 19 warnings → planned in 05.1-11),05.1-SECURITY.md(blocked on T-05.1-13 credential rotation and T-05.1-41/helpguardrails — the latter planned in 05.1-11).Gates at HEAD: root
tscclean, vitest 490/490; bottscclean, vitest 166/166; lint unchanged (1 pre-existing warning).Still human before 05.1 can close: rotate the GitHub PAT + Upstash REST token (never rotated after the 05.1-03 exposure); confirm
#opsis not visible to@everyone; #welcome picker tap-twice;#log-review/analyze//raid//helprun; 05.1-08's three checks; #announcements purge (Phase 05 UAT test 20, deadline passed).