Wireframes & Modals — The Forging Record

Wireframes & Modals — The Forging Record — a session chronicle from the Fenrir Ledger forge.

Act I · The Form Forging

CardForm Layout — Fieldsets & Side-by-Side Sections

🔥 FiremanDecko
Update the Add Card page in the app to match the add card wireframe EXACTLY. Currently the sections are not contained within their own box. Annual fee and Signup Bonus should be side by side.

Restructured CardForm.tsx — every <section> + <h2> replaced with <fieldset> + <legend>, giving each section a visible bordered box that matches the wireframe's fieldset { border: 1px solid } pattern.

Annual Fee and Sign-up Bonus wrapped in a grid grid-cols-1 md:grid-cols-2 gap-4 container. Card Details and Notes remain full width.

  • section + h2 (×4) fieldset + legend (×4)
  • vertical stack only 2-col grid: Annual Fee | Sign-up Bonus
  • "Card Name *" "Card name *"
  • "Open Date *" "Date opened *"
  • "Credit Limit ($)" "Credit limit"
  • "Annual Fee Amount ($)" "Annual fee"
  • "Next Fee Due Date" "Annual fee date"
  • "Spend Deadline" "Bonus deadline"
  • "Card Status" "Card status"
✎ development/frontend/src/components/cards/CardForm.tsx
Act II · The Limit Reckoning

Credit Limit — Tiered Dropdown with "Not Set" Entry

🔥 FiremanDecko
Make Credit Limit selectable in these increments: $1,000 up to $10,000 in $1,000 increments $10,000 up to $100,000 in $5,000 increments

[NOT SET] as first entry. [NOT SET] should represent 0.

Replaced Input type="number" with a Select. Options generated programmatically — no duplicate at the $10,000 tier boundary. 29 total options including the sentinel entry.

// Sentinel: Not set → stores $0 (0 cents)
<SelectItem value="0">Not set</SelectItem>

// Tier 1: $1,000 → $10,000 (10 options) Array.from({ length: 10 }, (_, i) => (i + 1) * 1000)

// Tier 2: $15,000 → $100,000 (18 options) Array.from({ length: 18 }, (_, i) => 15000 + i * 5000)

// Display: ${v.toLocaleString()} → "$15,000", "$100,000"

  • Input type="number", free text Select, 29 options
  • value="0" for "Not set" — dollarsToCents("0") → 0 cents ✓
✎ development/frontend/src/components/cards/CardForm.tsx
Act III · The UUID Fracture

crypto.randomUUID — Non-Secure Context Fallback

⚡ Runtime Error — Reported by Loki 🐍
CardForm.tsx:206 Failed to save card: TypeError: crypto.randomUUID is not a function at onSubmit (CardForm.tsx:174:41)

crypto.randomUUID() is restricted to secure contexts (HTTPS / localhost). Dev servers accessed over a LAN IP — common on local network builds — silently lack it.

Added generateId() to card-utils.ts: uses randomUUID when available, falls back to getRandomValues which is unrestricted and produces a valid UUID v4.

export function generateId(): string {
  if (typeof crypto.randomUUID === "function") {
    return crypto.randomUUID();           // secure context
  }
  // Fallback — works over HTTP / LAN IP
  const bytes = crypto.getRandomValues(new Uint8Array(16));
  bytes[6] = (bytes[6]! & 0x0f) | 0x40;  // version 4
  bytes[8] = (bytes[8]! & 0x3f) | 0x80;  // variant bits
  const hex = Array.from(bytes).map(b => b.toString(16).padStart(2, "0"));
  return `${hex.slice(0,4).join("")}-...-${hex.slice(10).join("")}`;
}
✦ generateId() added to development/frontend/src/lib/card-utils.ts ✎ development/frontend/src/components/cards/CardForm.tsx
Act IV · The Scroll Pact

Validation — Scroll to First Error on Submit

🔥 FiremanDecko
Ensure that the page scrolls to any element that fails validation when the save button is clicked. Save this as a team standard pattern / team norm.

Added scrollToFirstError as the second argument to handleSubmit. Candidates are sorted by compareDocumentPosition — DOM order, not schema insertion order — so the scroll always targets the error that appears first on screen.

Set shouldFocusError: false in useForm to prevent react-hook-form's built-in focus from fighting the manual scroll.

// useForm — disable built-in focus
shouldFocusError: false,

// Scroll handler — passed as handleSubmit second arg const scrollToFirstError = (errs: Record<string, unknown>) => { const elements = Object.keys(errs) .map(key => document.getElementById(key)) .filter((el): el is HTMLElement => el !== null) .sort((a, b) => a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1 ); if (elements.length > 0) { elements[0]!.scrollIntoView({ behavior: "smooth", block: "center" }); elements[0]!.focus(); } };

// Form tag <form onSubmit={handleSubmit(onSubmit, scrollToFirstError)}>

⚖ Team Norm — Codified & Saved to memory/team-norms.md
Every form component in Fenrir Ledger must follow this pattern:
  • 1. Give each field an id matching its react-hook-form field name
  • 2. Set shouldFocusError: false in useForm
  • 3. Pass scrollToFirstError as the second arg to handleSubmit
  • 4. Sort by compareDocumentPosition — DOM order, not schema order
✎ development/frontend/src/components/cards/CardForm.tsx ✦ memory/team-norms.md ◈ memory/MEMORY.md updated
Act V · Luna's Vision

About Modal — Wolf Logo, The Pack & Seven Impossible Things

🌙 Luna
Create a wireframe for an About modal. A 2 column dialog.

LEFT: Wolf logo from the static marketing site on the left side.

RIGHT: Snippets about the fenrir team on the right side in the voice of the wolf. ——separator bar—— Bullet list of 7 Mythical ingredients used to construct the app. One of the ingredients is "Beard of a Woman"

Created design/wireframes/about-modal.html. Two-column dialog: 200px fixed left column (wolf logo from /static/icon.png + wordmark + tagline) and flexible right column (team snippets + hairline separator + 7 ingredients). Right column is scrollable to support future content growth.

The Pack — Declarations from the Wolf 🐺
👑 Freya — Product Owner
"She decides what the wolf hunts next."
🌙 Luna — UX Designer
"She shapes the shadows where the wolf walks."
🔥 FiremanDecko — Engineer
"He forged the chain. Then taught the wolf to wear it willingly."
🐍 Loki — QA
"He tests every lock. He is, after all, the reason locks exist."
Bound by Seven Impossible Things — ᚷᛚᛖᛁᛈᚾᛁᚱ
  • I The sound of a cat's footstep Norse myth · Gleipnir
  • II The beard of a woman ✦ Norse myth · Gleipnir
  • III The roots of a mountain Norse myth · Gleipnir
  • IV The sinews of a bear Norse myth · Gleipnir
  • V The breath of a fish Norse myth · Gleipnir
  • VI The spittle of a bird ◈ Norse myth · Gleipnir
  • VII The first debt willingly forgiven App-original
✦ User-specified ingredient  ·  ◈ Also Gleipnir Hunt fragment #6 (Valhalla empty state easter egg)  ·  VII app-original
✦ design/wireframes/about-modal.html ✎ design/wireframes.md (index entry added)
Act VI · The Chronicle

wireframes.md — About Modal Design Spec Added

🔥 FiremanDecko
Update the wireframe MD with information about this

Added a complete About Modal section to design/wireframes.md — consistent in depth and format with the Easter Egg Modal section it follows.

  • ASCII structure diagram of the 2-column layout
  • Key layout decisions: column widths, scrollability, divider pattern
  • Ingredients table with origin attribution for all 7
  • Note: ingredient VI doubles as Gleipnir Hunt fragment #6 easter egg
  • Z-index (200), accessibility (role, aria-labelledby), mobile behavior
✎ design/wireframes.md
Session Ledger
Files modified
4
Files created
2
Team norms added
1
Bugs slain 🐍
1