The Marketing Massacre

A surface-level reckoning — Odin walked through every page of the marketing site and named what no longer earned its keep. Tools, tests, hero images, newsletters, broken filters — all purged.

Act I · Infrastructure

Throttled at the Limit

Odin

agent pos seem to be exhausing CPU limits. what limit is set what should be increased to?

The agent pods were pegged. Two of them sat at 3998m / 4000m — CFS slicing them at the hard limit, build phases dragging because the kernel refused them more cycles. The per-pod ceiling had been cpu: 2 since the GKE Autopilot migration on 2026-03-13. Untouched for fifty-two days while the codebase doubled around it.

The first instinct is to blame the kernel. Then to suspect a regression. The git log told a quieter story — nothing reverted. The blog cornerstone batch had landed (PRs #2196–#2204) and next build grew teeth. Thirty-eight cornerstone posts. Forty-eight cover images. The same 2 CPU slot was now feeding a much hungrier wolf.

Bumped per-pod 2→4 in PR #2245. Pegged again. Bumped 4→8 in PR #2251. Settled. Then noticed Autopilot quietly upsized memory from 4Gi to 8Gi to honor the 1:1 CPU ratio rule — meaning the namespace quota only fit 16 pods, not the 32 we thought. Bumped memory quota 128→256 GiB in PR #2253 to actually unlock the advertised fan-out.

resources:
requests: { cpu: "2", memory: 4Gi }
limits:   { cpu: "2", memory: 4Gi }
resources:
requests: { cpu: "8", memory: 4Gi }   # Autopilot upsizes mem to 8Gi
limits:   { cpu: "8", memory: 4Gi }   # via 1:1 CPU ratio rule
infrastructure/k8s/agents/job-template.yaml infrastructure/helm/agents/values.yaml memory/project_agent_quota.md

Act II · Deployment

The Workflow That Never Fired

Odin

B (bump per-pod CPU 4→8 + quota 128→256, kill+redispatch)

The first PR merged but the cluster was unmoved — quota still showed 64 CPU. The deploy workflow's detect-changes filter only matched ^infrastructure/k8s/app/|^infrastructure/helm/fenrir-app/. Changes under infrastructure/helm/agents/ never triggered the helm upgrade.

Three PRs in a row required manual helm upgrade fenrir-agents after merge. Filed PR #2254 — added an agents output to detect-changes and a dedicated agents-deploy job. Self-sufficient, mirrors the adopt-and-upgrade block from app-deploy without dragging the full ledger app rebuild along for the ride.

.github/workflows/deploy.yml

Act III · Bug Fix

The Identical Blog Bug

Odin

All blog pages have the same content!!! Why are they all the same content? Do we need to file issues to fill in unique content for each blog?

Two blog post URLs. Different titles in the hero. Different breadcrumbs. Identical body text. The MDX files on disk had 170-200 lines of unique markdown each — the renderer was wrong, not the content.

Found it on line 367 of development/ledger/src/app/(marketing)/blog/[slug]/page.tsx. The page fetched getBlogPostBySlug(slug) for the hero and breadcrumbs, then rendered <ArticleBody /> with no props at all. Same for <RelatedPosts />. Both contained hardcoded static placeholders. The file's own comment confessed: "Static shell — real data wired via CMS in content waves." The wave never landed.

Every cornerstone post Epic #2187 had spent six waves producing was invisible to humans and search engines alike. Filed #2255 with full scope. Dispatched FiremanDecko on GKE.

GitHub Issue #2255 (filed + dispatched)

Act IV · Refactoring

Removing the Tools Section

Odin

Actually remove the entire tools section. The Ledger is the tool. we're not providing others!

Started narrow — "remove this annual fee calendar page." Then expanded: remove all of /tools/. Five routes. Five components. Four libs. Five test files. Plus every reference scattered across navbar, footer, route-index, JSON-LD builder, MDX content, glossary, compare pages.

The Ledger itself is the tool. Marketing pages competing for the same attention with worse versions of the same calculations had been quiet self-sabotage. The standalone calculators were redundant freebies — Wave 5 of Epic #2187, ironically. PR #2262 deleted them all and updated the cards' MDX to point at /ledger instead.

Use the [Fenrir Ledger 5/24 Tracker](/tools/5-24-tracker) to calculate your count.
Track your 5/24 count in [Fenrir Ledger](/ledger) before you apply.
infrastructure/k8s/agents/job-template.yaml development/ledger/src/components/marketing/MarketingNavLinks.tsx development/ledger/src/components/marketing/MarketingNavbar.tsx development/ledger/src/components/marketing/MarketingFooter.tsx development/ledger/src/lib/seo/route-index.ts development/ledger/src/lib/seo/jsonld.ts development/ledger/content/cards/chase-sapphire-preferred.mdx development/ledger/content/cards/chase-sapphire-reserve.mdx

Act V · Tooling

The Test Suite Reckoning

Odin

Remove all those seo tests, they're a waste of time. Only test on the typescript code in the apps - ledger, odins-throne, odins-spear. That should be in the rules and @memory/team-norms.md

The unit tests for marketing surfaces had failed twice in a row — passing in CI, breaking in production. The cluster filter's "All Posts clears the filter" assertion was met by the test, broken by the deployed UI. The pattern was clear: testing static content rendering catches nothing real.

Wholesale removal — entire __tests__/seo/, __tests__/marketing/, __tests__/chronicles/, __tests__/glossary/ directories deleted. Plus blog/cluster/MDX/footer/navbar tests scattered across the lib and pages folders. Thirty-five files gone.

The rule went into memory/team-norms.md under the new heading Test Scope: TypeScript App Logic Only. Vitest covers ledger, odins-throne, and odins-spear. Static content surfaces get Loki's Playwright E2E or nothing. Final count: 239 files, 3535 tests, all green.

memory/team-norms.md

Act VI · Navigation

Two Headers, One Source

Odin

And still, inside the ledger the menu items are different. They're supposed to share code. Are they?

They were supposed to. They weren't. The marketing navbar rendered <DesktopNav /> driven by NAV_SECTIONS — full dropdowns, real structure. The /ledger top bar rendered <MarketingNavLinks /> driven by NAV_LINKS — three flat links. Two data sources, two visual treatments, one promise broken.

While debugging that, also caught a duplicate Free Trial entry in the header — my own prior PR had added flat NAV_SECTIONS entries while a rebase brought back pre-existing duplicates. Both rendered. PR #2263 fixed both: dropped the duplicates and pointed LedgerTopBar at DesktopNav so marketing and /ledger finally render the identical desktop header.

development/ledger/src/components/marketing/MarketingNavLinks.tsx development/ledger/src/components/layout/LedgerTopBar.tsx

Act VII · UI

The Polish Pass

Odin

Remove the ugly huge images at the top of these pages... Remove "Apply Now" links... Remove "The Weekly Ledger" we have no newsletter... Glossary category filtering doesn't work... Share link on blog page doesn't work...

Bundled into PR #2265. The card detail pages lost their oversized cover-image heroes — the cover slot now drops into the existing [Card Art] 1.586:1 placeholder so the slot stops being blank. The compare pages lost theirs entirely. The footer's Weekly Ledger newsletter form, posting to a non-existent /api/newsletter, came out clean. The glossary's ?category= filter sidebar was wired to nothing — same disease as the cluster filter — Odin chose deletion over repair.

The blog Share button finally became a real button. ShareButton.tsx, client component, copies window.location.href via navigator.clipboard.writeText, shows a 1.5-second "Link copied!" tooltip, falls back gracefully on error. The "Apply Now" CTA and the "may earn a commission" disclosure copy came off the cards — there are no affiliate links yet, the disclosure was misleading. Filed Epic #2266 to set up real referral programs across Chase, Amex, Capital One, Citi, Bilt, Discover, Wells Fargo, U.S. Bank, BoA, Barclays, Synchrony.

development/ledger/src/components/marketing/ShareButton.tsx development/ledger/src/app/(marketing)/cards/[slug]/page.tsx development/ledger/src/app/(marketing)/compare/[slug]/page.tsx development/ledger/src/app/(marketing)/glossary/page.tsx development/ledger/src/app/(marketing)/glossary/[term]/page.tsx development/ledger/src/app/(marketing)/blog/[slug]/page.tsx development/ledger/src/components/marketing/MarketingFooter.tsx

Act VIII · Layout

Anchoring the Dropdowns

Odin

The dropdown menus do not align properly with the position of the entry in the header, ensure the drop down menu aligns properly

Filed as #2264 first because it deserved a focused fix. shadcn's NavigationMenu uses one shared NavigationMenuViewport for all dropdowns — Cards, Compare, Blog all opened in roughly the same place because the viewport is positioned relative to the menu Root, not the active trigger.

The fix in PR #2267 dropped the shared Viewport entirely. Each NavigationMenuContent now renders inline within its own NavigationMenuItem with position: relative. The popover styling — border, shadow, rounded corners — moved from Viewport to Content. Lost the cross-trigger slide animation; gained correctness. Left edge of panel = left edge of trigger.

development/ledger/src/components/ui/navigation-menu.tsx development/ledger/src/components/marketing/HeaderDropdown.tsx

Act IX · Identity

Closing the Saga

Odin

Where's the epic about llm friendly content? was it finished?

Verified live: /llms.txt returns valid markdown — H1, blockquote tagline, sectioned link list. /llms-full.txt returns 200. Issue #2226 closed 2026-05-04. The sitemap is dynamic, derived from MDX at request time, 101 URLs, lastmod fresh. No /tools/ entries — PR #2262 already swept those out.

Then ran /epic-manager 2187. Thirty-eight stories across seven waves. All done. Closed Epic #2187 with a note that PR #2262 later removed the Wave 5 tools — the standalone calculators were a strategic mistake, the marketing investment in those routes is sunk cost, and the broader epic remains a win. The wolf doesn't apologize for sharpening teeth.

GitHub Epic #2187 (closed)