Staging Real data, but not the live site — MFL writes, push and chat posts are switched off here.
Development Knowledge Base · Jan 2026 – Present

Most AI systems forget what they learn.
This one writes it down.

A structured corpus of hard-won knowledge accumulated across every development session. API quirks discovered the painful way. Design patterns refined through iteration. Architecture decisions documented before the rationale evaporated. Loaded into context at the start of every task so the machine doesn't have to rediscover the same pothole twice.

Design System · Frontend Patterns · MFL API Quirks · Feature Architecture · Accessibility
4
Domain knowledge files
8
Feature insight files
35+
Individual insights recorded
0
Times the same mistake was made twice
00

The System

How accumulated knowledge becomes a development accelerant

Every development session produces discoveries. A third-party API that silently returns stale data during the offseason. A CSS token that renders fine on the design system demo but breaks in every real component. A drag-and-drop library that will not work if you wrap its items in a <div> instead of a React.Fragment. Left undocumented, these discoveries evaporate — and the next session rediscovers them from scratch.

The AI Insights System is a structured corpus organized into files by domain (design-system.md, mfl-api.md, frontend.md) and by feature (nav-redesign.md, custom-rankings.md). Before any development task begins, relevant files are loaded into the AI's context. After each task completes, new learnings are written back. The knowledge compounds.

Task begins
→
Load domain + feature files into context
→
Execute with accumulated knowledge
→
Write new learnings back
↺ compounds across sessions
"The AI Insights System is itself a demonstration of the same principles applied to the product: structured context, explicit formats, and a maintained feedback loop. The development workflow is the feature."
01

Design System

Tokens, visual patterns, and the editorial language derived from the PlayerDetailsModal

The design system grew bottom-up from a single modal component. The PlayerDetailsModal established a typographic and spatial language so coherent that it became the canonical reference for every page that followed. Most of what's documented here is "what the modal taught us" — and what had to be written down to survive the next refactor.

Mar 1, 2026 Design

Editorial Design Standard

The left-border accent on uppercase section titles is the most recognizable editorial pattern in the codebase. Every new page must use it. The modal-derived typographic hierarchy — five distinct roles from 0.625rem table headers to 1.35rem hero titles — is now the site standard.

.section-title { border-left: 2px solid var(--color-primary); padding-left: 0.625rem; }
Mar 1, 2026 Design

Button & CTA Hierarchy (Official)

Dark blue (#1c497c) is the primary CTA. Green (#2e8743) is reserved only for affirmative/go actions like submitting a bid or confirming a roster change — not general-purpose CTAs. Ghost buttons pair with primary buttons, never solo.

Use <a href> for navigation, <button> for in-page actions. Same CSS classes, different elements. Never use <a> without an href.
Mar 2, 2026 Design

Broadcast Diagonal Cut (Flair Pattern)

ESPN/FOX-inspired parallelogram clip-path for hero sections that need sports-forward energy. Both edges use an identical 20% horizontal slope — creating true parallel lines that convey motion. Primary-blue accent stripes run along each diagonal edge via pseudo-elements.

One per page maximum. Hide below 767px — the geometry doesn't scale to narrow viewports. Used in the Free Agents hero (players.astro).
Mar 2, 2026 Design

Negative State: Subtle Accents, Not Color Floods

Negative/warning states use a red left-border accent (border-left: 3px solid var(--color-error)) and red text on key values. Never use large colored backgrounds or gradient fills. The gray-50 card background stays neutral — the accent does all the signaling.

/* ❌ Not this */ background: var(--color-error-light);
/* ✓ This */ border-left: 3px solid var(--color-error);
Mar 2, 2026 Design

WCAG Contrast Correction: gray-400 vs gray-500

--color-gray-400 (#9ca3af, ~2.86:1 contrast) fails WCAG AA for text. Labels, hints, and any readable text must use --color-gray-500 (#6b7280, ~4.63:1) or darker. Reserve gray-400 for decorative borders and disabled controls only.

This was a widespread issue. Audit any component that used gray-400 as a text color.
Jan 18, 2026 Design

Nav Tokens Must Live in tokens.css

Nav CSS variables defined only in _nav-tokens.scss fail on pages that import tokens.css but not the full compiled SCSS bundle. All --nav-* tokens must be defined in both files — tokens.css is the canonical source, SCSS is a mirror.

Always include fallback values in component CSS as defensive coding: var(--nav-team-logo-size, 40px).
Mar 1, 2026 Design

Modal Shell Spec (for new modals)

Overlay: rgba(15, 23, 42, 0.45) + backdrop-filter: blur(2px). Modal: max-width: 580px, entry animation 0.32s ease-out with scale(0.96→1) + translateY. Mobile: bottom-sheet slide-up with top-only radius. Close button: 32px circle, absolute top-right.

02

Frontend Patterns

Component architecture, player display, headshots, and the gotchas that cost real time

Frontend work in this codebase has a consistent source of friction: third-party data that doesn't quite match what you expect, and small normalization gaps that produce broken logos or missing headshots for specific teams. The Player Lockup standard exists because every component that skipped it eventually had broken Kansas City Chiefs logos.

Feb 23, 2026 Frontend

Player Lockup Standard (and why it exists)

Multiple components had slightly different player display patterns — some missed DEF handling, some skipped normalizeTeamCode(). The result: broken logos for KCC (file is KC.svg), JAC, NEP, NOS, and DEF players showing a generic silhouette instead of the team logo.

Always use PlayerCell.astro or buildPlayerCellHTML(). Test with Kansas City, Jacksonville, New England, and New Orleans. DEF handling is automatic — don't special-case it.
Mar 1, 2026 Frontend

ESPN Headshot URLs + College Fallback Chain

ESPN uses different athlete IDs for college and NFL — a player's college athlete ID is NOT the same as their NFL player ID. MFL stores ESPN NFL IDs as espn_id but 2026 draft prospects have none. College headshots require a separate mapping file (data/theleague/espn-college-ids.json).

ESPN NFL → ESPN College → MFL headshot → placeholder
Feb 12, 2026 Frontend

Franchise History for Name/Logo Changes

Heavy Chevy rebranded to Dead Cap Walking for 2026. Historical views need to show "Heavy Chevy" for 2024 data. A history[] array in theleague.config.json with yearStart/yearEnd plus all identity fields solves this via getTeamIdentityForYear().

Pages showing historical data must use getTeamIdentityForYear(team, year) from src/utils/team-names.ts. Future rebrands only need a new history entry in config.
Jan 18, 2026 Frontend

Dark Mode: Both Media Query AND .dark Class

All new components must support dark mode via two mechanisms: @media (prefers-color-scheme: dark) for OS-level preference, and .dark class on :root for a manual toggle. Use :global(.dark) in Astro scoped styles to target the class.

Mar 2, 2026 Frontend

Clean URL Rewrites Break on Prerendered Pages

Host-level clean URL rewrites (/trade-builder → /theleague/trade-builder) depend on runtime route resolution in middleware.ts. Pages with prerender = true only exist as static output and don't register as runtime route entries — the middleware rewrite cannot resolve them.

Any TheLeague page linked from the nav drawer must use prerender = false while clean URL rewriting is active. Affected pages: calendar, league-summary, trade-builder, import-rankings.
03

MFL API Quirks

Third-party API behavior that is not documented, not obvious, and not forgiving

MyFantasyLeague's API is mature but idiosyncratic. Most of what's documented here is behavior that only becomes visible under specific conditions — the pre-rollover window, a serverless execution environment, a league with a non-standard draftPlayerPool setting. The notes exist so these conditions don't produce mysterious failures that take hours to trace.

Feb 26, 2026 MFL API

Login Authentication: XML, Not JSON

The MFL login endpoint does NOT support JSON=1 — it returns an empty body. Use XML=1 instead. The response is XML: <status MFL_USER_ID="base64cookie"/> on success, <error>Invalid Password</error> on failure. The standalone /myleagues endpoint always returns HTML from server-side fetch — use export?TYPE=myleagues&JSON=1 instead.

Step 1: POST /login?XML=1 → parse XML for MFL_USER_ID
Step 2: GET /export?TYPE=myleagues&JSON=1 with Cookie: MFL_USER_ID=…
Feb 13–14, 2026 MFL API

Rosters API Staleness During Pre-Rollover Window

Between season end and the Feb 14 league year rollover, the rosters endpoint can return stale data for recent drops — confirmed 31+ hours after drops occurred. The transactions and freeAgents endpoints correctly reflected changes. The rosters endpoint is not the sole source of truth during the offseason.

During the offseason, cross-reference rosters with recent TRANS_TYPE=FREE_AGENT transactions. Filter out players in drop-only transactions (|{id}, format). Transaction endpoint requires following HTTP 302 redirects — use -L or a redirect-following client.
Feb 27, 2026 MFL API

myWatchList vs myDraftList

myDraftList is ordered but destructive (complete overwrite) and possibly restricted to draftPlayerPool players. myWatchList supports incremental ADD/REMOVE and has no documented player restrictions — but is unordered (a set, not a ranked list).

For custom rankings: use myWatchList as the MFL-synced "flagged" set. Store ranking order in Upstash Redis or local storage. Use playerRanks (FantasySharks) as the default seed.
Feb 24, 2026 MFL API

pointsAllowed: Full Season Only, Strings Not Numbers

The pointsAllowed DVP endpoint accepts a W (week) param but completely ignores it — all requests return identical full-season cumulative totals. Point values are strings — always parseFloat() before math. MFL uses non-standard team codes (KCC, JAC, NEP) — always normalizeTeamCode().

For weekly DVP context, combine with nflSchedule. For season-long averages, divide by 17. Filter out entries with name: "".
Feb 12, 2026 MFL API

No Dedicated Franchise History API

MFL has no endpoint for franchise name/logo history. The year-specific league export returns names as they were that year. Pre-2016 seasons used different league IDs (e.g., 2007: 76273 on www42) — all mapped in the history.league array of the current year's league data.

Project advantage: data/theleague/mfl-feeds/{YEAR}/league.json is cached for 2011–2025. No additional API calls needed for historical names.
Feb 13, 2026 MFL API

Free Agent Status "locked" in Offseason

Players dropped during the offseason appear in freeAgents with status: "locked" — they cannot be claimed until the new league year begins or waivers open. This is distinct from status: "available" which means claims are open.

Display status: "locked" players with a visual indicator (grayed out or lock icon) to signal they're not claimable.
04

Feature Architecture

What building each major feature taught us about the next one

Feature development is where abstract architectural decisions meet concrete implementation reality. The nav redesign taught us about the limits of prerendering and the importance of cookie-first server rendering. Custom rankings exposed edge cases in drag-and-drop library assumptions. Each feature's learnings file is a post-mortem for the next engineer.

Feb 28, 2026 Custom Rankings

@dnd-kit: Three Silent Killers

Drag-and-drop simply stops working if: (1) the sortable container has overflow: hidden — dragged elements appear not to move; (2) restrictToParentElement modifier is used — constrains drag too aggressively; (3) sortable items are wrapped in extra <div>s instead of React.Fragment — confuses position tracking. All three issues can be present simultaneously and interact.

Only use restrictToVerticalAxis modifier. Never add overflow: hidden to the sortable container. Use React.Fragment for item wrappers. Check ManageImportsSection.tsx for a working reference pattern.
Feb 28, 2026 Custom Rankings

Upstash Redis for Cross-Device KV

For per-user state (<10KB) that needs cross-device sync without a full database, Upstash Redis via @upstash/redis is the answer. Auto-reads env vars (UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN). Each request is a standalone HTTP call — no connection management.

key pattern: {feature}:{franchiseId}
e.g. cr:0003 for franchise 0003's custom rankings
Feb 28, 2026 Custom Rankings

Edit Mode Prevents Accidental Drag

@dnd-kit supports empty useSensors() which completely disables drag without unmounting the context. Passing sensors={isEditing ? editSensors : noSensors} gates drag to an explicit edit mode, preventing accidental reorders while browsing the list.

Mobile touch targets for hidden-on-hover UI need explicit fallbacks: always-visible at 0.4 opacity on mobile, full opacity on :active.
Feb 28, 2026 Custom Rankings

Composite Hash for Staleness Detection

When rankings derive from user-configured composite sources, a hash of the source config (sorted member IDs + player count) reliably detects when the source data changed. On mismatch, mergeWithOverrides() re-seeds from the new composite while preserving manual customizations.

For any feature that derives from user-configured data: store a hash alongside the derived state. This enables automatic reconciliation without losing overrides.
Jan 18, 2026 Nav Redesign

MutationObserver Beats Custom Events for State Sync

The page wrapper needs to respond to drawer state changes (open/close) by adjusting its margin-right. Using MutationObserver to watch for class changes on the drawer element is more robust than custom events — it catches all state changes regardless of how they were triggered, including programmatic changes from window.navDrawer API calls.

Jan 18, 2026 Nav Redesign

Playwright Surfaced Critical Spacing Issues

E2E testing revealed nav links were 26.59px tall instead of the 40px minimum touch target. SVG icons were rendering at browser default size (hundreds of pixels) because Astro scoped CSS wasn't targeting nested SVGs — required :global(.nav-links__icon svg) and explicit width/height attributes on the SVG element itself.

Run Playwright after any CSS changes to catch spacing/sizing regressions. Always add explicit width and height attributes to SVGs used as nav icons.
05

Patterns That Compound

The meta-observations that emerge after enough individual insights accumulate

After enough individual insights accumulate, second-order patterns start to emerge. These aren't observations about specific API endpoints or CSS tokens — they're observations about the shape of problems in this codebase.

I
Third-Party Data Is Wrong Under Specific Conditions

MFL's rosters endpoint returns stale data in the pre-rollover window. ESPN's headshot CDN serves 404s for players without photos instead of a fallback. pointsAllowed ignores the week parameter silently. None of these are bugs in the traditional sense — they're edge conditions that exist at the boundaries between systems. The documentation for these conditions is the codebase.

II
Small Normalization Gaps Compound into Broken UX

A missing normalizeTeamCode() call doesn't fail loudly — it silently serves a 404 for a logo file. Skipping DEF handling in a player component doesn't crash — it shows a generic avatar instead of the team logo. These failures are invisible until someone looks at the specific player or team that triggers them. The Player Lockup standard exists because every shortcut eventually produces a broken logo.

III
Architecture Decisions Cost More to Change Than to Get Right

The clean URL rewrite system requires pages to be server-rendered. The token system requires tokens.css as the canonical source. The franchise history system requires a history[] array in config rather than dynamic API calls. Each of these decisions has a straightforward rationale that is obvious in hindsight — and expensive to retrofit when discovered late. The insights corpus exists to make them obvious upfront.

"The best time to document a quirk is immediately after discovering it, before the workaround becomes load-bearing and the original problem is forgotten. The second best time is now."

The File Structure

The corpus lives in docs/claude/insights/. Domain files cover cross-cutting concerns: design-system.md, frontend.md, mfl-api.md, accessibility.md. Feature files cover the learnings specific to each major feature: nav-redesign.md, custom-rankings.md, and growing.

Each entry follows a consistent format: date, context, insight, evidence, recommendation. New entries are appended, not edited — the history of how understanding evolved is part of the value.