The Wolf Signs In & Valhalla Opens

A session chronicle of The Wolf Signs In & Valhalla Opens.

──────────────────────────────────────────────── -->

Act I · Identity

The Chain Breaks Free

FiremanDecko

commit and push

The prior session left the wolf mid-step — LICENSE.md half-sworn, the AGPL chain still clinging. The wolf completed the oath: Elastic License 2.0 now guards the ledger. No competitor may host Fenrir's work as their own service. Source remains open, but the pack alone may offer it to the world.

README.md was updated to reflect the new license name, and the commit was pushed to main.

# Commit message
chore: switch license from AGPL-3.0 to Elastic License 2.0 (ELv2)

# LICENSE.md — first lines of the new oath ELASTIC LICENSE 2.0 ACCEPTANCE By using the software, you agree to all of the terms and conditions below.

◈ LICENSE.md ◈ README.md
ᛟ ᚠ ᛁ

Act II · Foundations

The Backlog Unrolls

FiremanDecko

what's next up in the backlog?

The wolf sniffed the sprint plan and found old scent — Sprint 3 still carried Supabase stories, tales written before the pack decided remote storage would wait until GA. The product brief had been updated, but the sprint plan had not caught up.

The sprint plan referenced story-auth-oidc-google.md with a Supabase-centric engineering response, and ADR-004 pointed to a Supabase persistence decision that no longer matched the product brief. The backlog was stale. Time to reforge.

ᚠ ᚾ ᛁ

Act III · New Beginnings

The Sprint is Reforged

FiremanDecko

re-run freya to generate the next sprint since the product brief requirements were changed

Two stale artifacts were destroyed first — the Supabase ADR and the Supabase auth spec. Then Freya, the product owner, rewove Sprint 3 from the updated product brief.

The new Sprint 3 carries five stories: 3.1 Auth (OIDC + localStorage), 3.2 Norse Copy Pass, 3.3 Framer Motion animations, 3.4 Howl Panel, and 3.5 Valhalla + Close Card. Auth stores data under per-household localStorage keys scoped to the Google sub claim — no remote database, no Supabase.

✕ architecture/adrs/ADR-004-oidc-auth-and-persistence.md ✕ development/spec-auth-oidc-google.md ◈ architecture/sprint-plan.md ◈ product/backlog/story-auth-oidc-google.md ◈ product/README.md
ᚾ ᛇ ᛁ

Act IV · Complexity

FiremanDecko Raises Questions

FiremanDecko

Fire off FiremanDecko to review and ask clarifying questions

FiremanDecko reviewed the new Sprint 3 plan and surfaced eight questions — two hard blockers (B1/B2), four risks (R1–R4), and two ambiguities (A2–A3). The wolf had refused to build until the pack answered them.

8 Questions Before the Forge Could Open

  • B1 — Is a custom sign-in page needed, or use Google's hosted flow?
  • B2 — localStorage namespace: global key or per-household key?
  • R1 — Which auth library? Auth.js v5 vs roll-your-own PKCE vs NextAuth v4?
  • R2 — Google OAuth callback only works on production — acceptable for preview?
  • R3 — Valhalla scope: what exactly does it show?
  • R4 — Howl Panel urgency threshold — what value triggers it?
  • A2 — Easter eggs on Valhalla — defer or include in this sprint?
  • A3 — realm-utils.ts as separate utility — proceed?
ᛇ ᚱ ᛁ

Act V · Direction

The Pack Decides

FiremanDecko

Sign-in page: No need for wireframes, we'll use Google's hosted login flow localStorage namespace: Per-household keys (fenrir_ledger:{householdId}:cards) What are we gonna use for the householdId? Auth library: Auth.js v5 (FiremanDecko's call — Freya can't make technical decisions) Google OAuth on preview deployments: Fine, we're still MVP Valhalla: shows closed cards (status: 'closed') only Deleted cards are deleted — users never see them again 'close card' is a distinct user action Howl Panel: no urgency at all — postpone

All eight questions answered. The wolf noted that Freya had been asked to approve a technical library choice — a boundary violation. Only FiremanDecko governs the forge. The householdId question was resolved: use the Google sub claim directly — stable, globally unique, already present in the JWT, no derivation needed.

Story 3.4 (Howl Panel) was dropped entirely from Sprint 3. Story 3.1 auth now uses Auth.js v5 with JWT sessions, HttpOnly cookies, and per-household localStorage keys scoped by sub.

Key Architectural Decisions

  • householdId = Google sub claim (no derivation)
  • localStorage key format: fenrir_ledger:{householdId}:cards
  • Auth library: Auth.js v5 (FiremanDecko's call)
  • Valhalla: status: "closed" cards only; deleted = gone forever
  • Story 3.4 (Howl Panel): postponed indefinitely
  • Easter eggs on Valhalla: deferred to Sprint 4
ᚱ ᛒ ᛁ

Act VI · Design

Luna's Consultation

FiremanDecko

Any wireframes needed from Luna first?

Luna was consulted before the forge opened. The answer was swift: no new wireframes were required. Luna had already delivered ux/wireframes/valhalla.html with the Valhalla layout, and ux/interactions.md already contained the close-card animation specification. The pack had what it needed. FiremanDecko was cleared to build.

ᛒ ᚹ ᛁ

Act VII · Story 3.2 · Norse Copy Pass

The Realms Are Named

FiremanDecko

Have Loki validate and test after each story

FiremanDecko forged realm-utils.ts — the authoritative Norse display layer. Card statuses gained their realm names: Ásgarðr for active, Muspelheim for fee-approaching, Jötunheimr for promo-expiring, Valhöll for closed. Norse headings replaced placeholder English copy across the dashboard and card forms.

Loki reviewed and raised three defects — DEF-001 through DEF-003: tooltip strings in realm-utils.ts had drifted from the approved copy in product/copywriting.md. FiremanDecko corrected all three strings to exact specification. Loki re-validated: SHIP ✓.

🐺 DEF-001/002/003 — Tooltip Copy Drift

fee_approaching: "fire approaches" → exact approved string promo_expiring: "draws near" → exact approved string closed: extra "chain broken" phrase removed

Loki Verdict — SHIP ✓

Story 3.2 approved after one defect cycle. All three tooltip strings corrected to copywriting.md specification. Norse headings pass on all routes.

✦ development/frontend/src/lib/realm-utils.ts ◈ development/frontend/src/lib/constants.ts ◈ development/frontend/src/components/dashboard/StatusBadge.tsx ◈ development/frontend/src/components/dashboard/EmptyState.tsx ◈ development/frontend/src/app/page.tsx ◈ development/frontend/src/app/cards/new/page.tsx ◈ development/frontend/src/app/cards/[id]/edit/page.tsx ✦ quality/story-3.2-verdict.md
ᚹ ᛊ ᛁ

Act VIII · Story 3.3 · Framer Motion

The Cards Begin to Dance

FiremanDecko summoned Framer Motion's AnimatePresence. Cards now enter the grid with a saga-stagger — each one fading up from below, delayed by index × 70ms, so the grid appears to unfurl like a saga being read aloud. The Valhalla exit animation descends through sepia into darkness over 500ms.

The skeleton loading state was forged: six shimmer tiles with a gold-tinted saga-shimmer keyframe pulse, captioned "The Norns are weaving…" Loki reviewed and found zero defects. SHIP ✓.

// AnimatedCardGrid.tsx — stagger entrance
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10, filter: 'sepia(1)' }}
transition={{ duration: 0.4, delay: index * 0.07 }}
/>

Loki Verdict — SHIP ✓

Story 3.3 approved with zero defects. Stagger timing correct, skeleton renders correctly, exit animation fires on close-card action.

✦ development/frontend/src/components/dashboard/AnimatedCardGrid.tsx ✦ development/frontend/src/components/dashboard/CardSkeletonGrid.tsx ◈ development/frontend/src/components/dashboard/Dashboard.tsx ◈ development/frontend/src/app/globals.css ✦ quality/story-3.3-verdict.md
ᛊ ᚲ ᛁ

Act IX · Story 3.5 · Valhalla

Valhalla Yawns Open

The hall of the fallen was built. A new route at /valhalla displays all cards with status: "closed" — tombstone cards rendered with the ᛏ rune, a sepia filter, and a filter/sort bar. The SideNav gained a Valhalla link. A new closeCard() function in storage.ts sets closedAt on the card without deleting it — closing and deleting are distinct user actions.

Loki raised one defect: the Valhalla empty state used the wrong Gleipnir fragment — DEF-001 had aria-description="the spittle of a bird" (Fragment 3) instead of the correct Fragment 6. One line fixed. Loki re-validated: SHIP ✓.

🐺 DEF-001 — Wrong Gleipnir Fragment on Valhalla Empty State

valhalla/page.tsx aria-description="the spittle of a bird" (Fragment 3) → corrected to aria-description="the beard of a woman" (Fragment 6)

Loki Verdict — SHIP ✓

Story 3.5 approved after one defect. Valhalla route renders, close-card action works, Gleipnir fragment correct.

✦ development/frontend/src/app/valhalla/page.tsx ◈ development/frontend/src/lib/types.ts ◈ development/frontend/src/lib/storage.ts ◈ development/frontend/src/components/cards/CardForm.tsx ◈ development/frontend/src/components/layout/SideNav.tsx ✦ quality/story-3.5-verdict.md
ᚲ ᛁ ᛊ

Act X · Story 3.1 · Authentication

The Wolf Signs In

Auth.js v5 was wired in while the pack attended to other tasks. The wolf ran in the background, unobserved, and emerged with the full authentication layer complete: Google OIDC via auth.ts, route protection in middleware.ts, a SessionProvider wrapper, TopBar sign-in/out state, and TypeScript augmentation adding householdId: string to the session shape.

The householdId flows from the Google sub claim through the JWT callback directly to all localStorage operations. Every household's data is now isolated by their identity. The per-household key format fenrir_ledger:{sub}:cards is the new law of the forge.

// auth.ts — householdId flows from Google sub claim
callbacks: {
async jwt({ token, account, profile }) {
if (account) token.householdId = token.sub;
return token;
},
async session({ session, token }) {
session.user.householdId = token.householdId as string;
return session;
},
},

// middleware.ts — protected routes export const config = { matcher: ['/', '/cards/:path*', '/valhalla/:path*'], };

Loki Verdict — SHIP ✓

Story 3.1 approved with zero defects across 18 review areas. Auth flow correct, route protection verified, householdId scoping validated.

✦ development/frontend/src/auth.ts ✦ development/frontend/src/middleware.ts ✦ development/frontend/src/app/api/auth/[...nextauth]/route.ts ✦ development/frontend/next-auth.d.ts ✦ development/frontend/src/components/layout/AuthProvider.tsx ✦ development/frontend/.env.example ✦ architecture/adrs/ADR-004-oidc-auth-localStorage.md ◈ development/frontend/src/components/layout/TopBar.tsx ◈ development/frontend/src/app/layout.tsx ✦ quality/story-3.1-verdict.md
ᛁ ᚺ ᛊ

Act XI · Quick Fixes

The Growl Renamed

FiremanDecko

Rename fenrir-howl.mp3 to fenrir-growl.mp3 and ensure the easter egg dialog is updated Make a small tweak to the footer — Link 'ᛟ FENRIR LEDGER' to the static site. Ensure the wireframes are up to date.

Two small corrections issued while the auth work ran in the background. The audio file was renamed from fenrir-howl.mp3 to fenrir-growl.mp3 via git mv, and all eight Gleipnir component files referencing it were updated. The quality docs were also corrected.

The footer wordmark ᛟ FENRIR LEDGER was wrapped in an anchor linking to /static — the marketing site. The wireframe at ux/wireframes/footer.html was updated to reflect this change.

◈ development/frontend/public/sounds/fenrir-growl.mp3 (renamed) ◈ src/components/cards/EasterEggModal.tsx ◈ src/components/cards/ForgeMasterEgg.tsx ◈ src/components/cards/GleipnirCatFootfall.tsx ◈ src/components/cards/GleipnirMountainRoots.tsx ◈ src/components/cards/GleipnirFishBreath.tsx ◈ src/components/cards/GleipnirBirdSpittle.tsx ◈ src/components/cards/GleipnirBearSinews.tsx ◈ src/components/cards/GleipnirWomansBeard.tsx ◈ development/frontend/src/components/layout/Footer.tsx ◈ ux/wireframes/footer.html ◈ quality/test-cases.md ◈ quality/EASTER-EGGS-AUDIT.md
ᚺ ᛉ ᛁ

Act XII · Validation

Loki Passes Judgment

FiremanDecko

run loki on story 3.1

Loki reviewed Story 3.1 — the authentication layer — across 18 review areas: token flow, JWT shape, route protection matrix, TypeScript types, middleware config, sessionProvider placement, TopBar state, householdId scoping, localStorage key format, environment variable documentation, and more.

The verdict was clean. Zero defects. Sprint 3 was complete: 3.1 ✓, 3.2 ✓, 3.3 ✓, 3.4 postponed, 3.5 ✓.

Loki Verdict — SHIP ✓

Story 3.1 approved with zero defects across 18 review areas. Sprint 3 status: COMPLETE (4/5 stories shipped, 3.4 postponed by PO decision).

ᛉ ᛞ ᛁ

Act XIII · Delivery

Badges of Honour

FiremanDecko

What other cool status badges can be added to README.md that GitHub supports? add a curated set to the README commit and push

Five shields.io badges were added to README.md, joining the existing Vercel CI badge. The pack now wears its colours in the open: last commit, license, framework, language, and styling toolkit — all rendered gold, black, or branded colours against the dark README.


[![Last Commit](https://img.shields.io/github/last-commit/…?color=c9920a)]
[![License: ELv2](https://img.shields.io/badge/license-ELv2-brightgreen)]
[![Next.js](https://img.shields.io/badge/Next.js-15-black?logo=next.js)]
[![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6?logo=typescript)]
[![Tailwind CSS](https://img.shields.io/badge/Tailwind-CSS-38bdf8?logo=tailwindcss)]
◈ README.md
ᛞ ᚹ ᛁ

Act XIV · Polish

The Door Opens Wide

FiremanDecko

Make the marketing link in the footer open in a new window

The final act of the session — a single line of polish. The footer wordmark link to /static was given target="_blank" and rel="noopener noreferrer". The aria-label was updated to announce it opens in a new tab. The wolf does not keep visitors from their destination — it opens every gate.

<a
href="/static"
target="_blank"
rel="noopener noreferrer"
aria-label="Fenrir Ledger — visit the marketing site (opens in new tab)"
>
ᛟ FENRIR LEDGER
</a>
◈ development/frontend/src/components/layout/Footer.tsx