---
stepsCompleted: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
inputDocuments:
  - docs/prds/prd-financials-2026-05-25/prd.md
  - docs/architecture.md
  - docs/epics.md
---

# UX Design Specification — Personal Finance App

**Author:** Sayre
**Date:** 2026-05-26

---

<!-- UX design content will be appended sequentially through collaborative workflow steps -->

## Executive Summary

### Project Vision

A self-hosted, single-user personal finance command center — a precision tool built for one person's exact workflow. Hosted on LAN/localhost, accessed from a modern desktop browser, with no authentication overhead. Three-phase delivery: Phase 1 establishes a fully functional baseline (transactions, budgets, bills, paydown planner); Phase 2 enriches it with historical statement data from 7 issuers (5 via PDF, 2 via CSV); Phase 3 activates analytics and insights.

The defining characteristic: **single-user focus**. Every UX decision optimized for one known person's workflow.

### Target Users

**Primary (and only) user:** The product owner — technically proficient, self-hosting on Apache/mod_wsgi, comfortable with financial concepts (APR, amortization, debt strategies), working from a desktop browser in deliberate sessions.

- **No existing ritual:** Starting from zero financial visibility ("going blind all the time"). This app creates a new habit, it doesn't support an existing one.
- **Session mode:** Focused and periodic — weekly check-in, monthly planning, ad-hoc transaction entry.
- **Goal orientation:** Clarity and control. Not gamification.
- **Device:** Desktop browser only (Chrome/Firefox/Edge). No mobile optimization required.

### Key Design Challenges

1. **First-run onboarding as the gateway to value** — A user with no transaction history has an empty dashboard. The app's value proposition is gated behind getting real data in. First-run import is a first-class UX flow, not setup scaffolding.

2. **Two emotional jobs, one dashboard** — "Am I okay right now?" (present confidence) and "Am I heading somewhere good?" (forward hope) are distinct feelings that require different visual treatments. Mixing them creates noise; separating them creates clarity.

3. **Progressive complexity** — Paydown Planner (5 strategies, amortization tables), PDF/CSV staging screen (bulk categorization, error surfacing), analytics charts — all represent high-complexity surfaces that must be progressively disclosed without losing the user.

4. **Silent failure surface in import** — Parse failures are often quiet (misread amounts, merged rows) rather than loud (crashes). The staging review screen is the user's last line of defense and must surface anomalies, not just rubber-stamp them.

5. **Primer CSS as design boundary** — Strong accessibility and consistency out of the box, but custom patterns required for comparison tables, milestone timelines, and strategy cards.

### Design Opportunities

1. **Single-user optimization** — No median to design for. The method, not a feature: every decision can be exactly right for the actual user's workflow and evolve as that workflow evolves.

2. **Dashboard as ambient awareness** — A 5-second situational scan. One projected payoff date as the confidence anchor. Milestone strip. Green/red trending flag. Each widget answers exactly one question.

3. **Paydown Planner as decision theater** — Three hero-number strategy cards (interest-saved / first-payoff-date / monthly-cost). Sorted by fastest payoff by default. Hover previews the timeline shift. Designed to remove paralysis, not provide flexibility.

4. **PDF/CSV import as trust ceremony** — Staged review flow with original-line-adjacent parsed data, errors surfaced at top, bulk "apply to all matching payees" categorization. Hybrid approach: PDF for 5 clean issuers (Chase, BofA, Discover, Kohls, Target), CSV for Apple Card and DCU, uniform module interface throughout.

## Core User Experience

### Defining Experience

The central experience is a **weekly check-in ritual** the user doesn't yet have but will build. The app's job is to make that ritual rewarding enough to sustain itself: open the dashboard, understand your financial posture in 5 seconds, take one action if needed, close. Repeat.

Every other interaction — entering transactions, running paydown comparisons, importing statements, reviewing analytics — is deliberate work that feeds this core loop. The dashboard is the heartbeat; everything else is what keeps it accurate and meaningful.

### Platform Strategy

- **Platform:** Desktop web only. Chrome/Firefox/Edge, LAN/localhost.
- **Input:** Mouse and keyboard. No touch optimization required.
- **Rendering:** Server-side Flask templates (PRG pattern) with targeted AJAX for dynamic surfaces (dashboard widgets, import staging, chart interactions).
- **UI system:** Primer CSS (local copy). Vanilla JS only. Chart.js (local) for visualizations.
- **Offline:** Not required. Single-machine deployment.
- **Performance contract:** Dashboard renders in under 2 seconds. Import parse+stage under 30 seconds.

### Effortless Interactions

These interactions must feel like zero effort — no hesitation, no friction, no learning curve:

1. **The dashboard scan** — visual grammar (color, position, size) answers "am I okay?" before the user consciously reads. Green/amber/red encode status; no parsing required.
2. **Marking a bill paid** — one click from the bills list. Transaction logged automatically, next due date advanced, done.
3. **Manual transaction entry** — smart defaults (today's date, last-used category, last-used account), keyboard-navigable form, category accessible via quick-select. Under 10 seconds for a known merchant.
4. **Paydown strategy exploration** — hover a strategy card to preview the timeline shift. Zero commitment cost to browse. Click only to commit.
5. **Bulk merchant categorization in import staging** — "apply this category to all [merchant] transactions" available as a single action. The user categorizes a pattern, not individual rows.

### Critical Success Moments

These are the make-or-break interactions — the moments that determine whether the user keeps coming back or abandons the habit:

1. **First data on the dashboard** — the gateway to value. An empty dashboard has not proven itself. The first-run flow exists to reach this moment as fast as possible.
2. **First transaction correctly categorized** — system feels accurate. User trusts the data.
3. **Seeing the projected payoff date for the first time** — a debt has a finish line. This is the emotional peak of the Paydown Planner.
4. **First import completed** — months of history land at once. The dashboard transforms from sparse to informative. The analytics layer becomes real.
5. **First trending-negative warning** — the app noticed something before the user did. This is when it earns daily trust as a monitoring tool, not just a ledger.

### Experience Principles

1. **Ground truth before insight.** Data quality gates everything downstream. Import staging, duplicate detection, and manual correction exist because bad data silently corrupts every budget, projection, and recommendation that follows. The user is always the final authority on their own data.

2. **One question per surface.** Each dashboard widget, each panel, each view answers exactly one user question. Budget Burn answers "am I on track this month?" The payoff date answers "when does this end?" Mixing present state with projections creates noise; separation creates clarity.

3. **Effortless repetition, earned complexity.** Frequent actions (dashboard scan, transaction entry, bill payment) are zero-friction. Complex tools (paydown planner comparison, import staging, analytics) earn their depth through progressive disclosure — the surface is simple, the depth is there when needed.

4. **Show the destination.** The projected payoff date and milestone timeline are always visible. The user should always know where they're heading, not just where they are. Forward visibility is what turns a ledger into a motivational tool.

5. **Reward showing up.** The app is building a habit that doesn't yet exist. Positive reinforcement (milestones reached, debts paid off, budgets held) should be acknowledged, not silent. The moment the projected date moves earlier, that should feel like a win.

## Desired Emotional Response

### Primary Emotional Goals

**1. Calm Confidence** *(primary)*
"I know where I stand." The baseline state this app replaces is low-grade financial anxiety — going blind, not knowing. The goal is not excitement or delight; it's settled certainty. The dashboard should produce the feeling of checking a well-tended system, not bracing for bad news.

**2. Forward Momentum** *(secondary)*
"The plan is working." The projected payoff date. The milestone strip ticking closer. The progress that's invisible day-to-day but visible month-to-month. This emotion is what sustains the habit over months and years — the sense that deliberate behavior is producing real outcomes.

**3. Trust in the Data** *(foundational)*
"What I see is real." Every budget calculation, projection, and insight is only as trustworthy as the underlying transactions. Trust is earned through transparent import staging, visible duplicate detection, and keeping the user as the final authority on their own data.

### Emotional Journey Mapping

| Stage | Current State (Without App) | Target State (With App) |
|-------|----------------------------|------------------------|
| **First open** | Anxiety, dread, avoidance | Welcomed, oriented, a first small step feels achievable |
| **Onboarding / first import** | Uncertainty about what's true | Building confidence: data is entering, the picture is forming |
| **Dashboard check-in** | Vague unease | Settled clarity: 5-second scan confirms status |
| **Completing a task** (entry, payment, import) | Relief that it's done | Accomplishment: small, clean win acknowledged |
| **Seeing a negative trend** | Surprise or avoidance | Informed, not alarmed: calm heads-up, actionable |
| **Seeing progress** (payoff date moving closer) | Invisible, unrewarded | Motivated: the effort is visibly producing outcomes |
| **Returning after time away** | Guilt, catch-up anxiety | Familiar, non-judgmental: the system just shows what's true |

### Micro-Emotions

**Confidence over confusion** — The dashboard must never require interpretation. Visual grammar (color, size, position) encodes status before the user reads a number. Confusion at the primary surface breaks the 5-second contract.

**Trust over skepticism** — Import staging is where skepticism enters. If a transaction looks wrong in the review screen and the user can't easily fix it, trust breaks permanently. Transparency (showing original source data adjacent to parsed data) keeps skepticism from taking root.

**Accomplishment over frustration** — Every completed action should have a clear, quiet success state. Bill marked paid. Import confirmed. Strategy activated. These small wins compound into a habit. Friction at any of these points accumulates into abandonment.

**Informed over alarmed** — When something is trending wrong, the response must be proportional. A calm amber indicator and a one-line explanation ("you're 47 days behind plan") produces the right response: check the planner. A red alert with alarming iconography produces avoidance.

**Curiosity over intimidation** — The Paydown Planner and Analytics views are complex. They should feel like tools worth exploring, not walls of data. Progressive disclosure and hover previews make complexity feel like capability, not difficulty.

### Emotions to Avoid

- **Overwhelm** — Too many numbers competing for attention. Prevented by "one question per surface."
- **Shame** — Budget overruns and trending negatives are information, not verdicts. The app does not judge.
- **Paralysis** — The Paydown Planner comparison view must help commit, not delay. Three clear options, visible differences, confident forward path.
- **Skepticism about accuracy** — Silent import failures or un-caught duplicates destroy trust in everything downstream.
- **Catch-up anxiety** — Returning after time away should feel neutral. The data is just the data; no shame for the gap.

### Design Implications

| Emotional Goal | UX Design Approach |
|---------------|-------------------|
| Calm confidence | Clean hierarchy, color-coded status (green/amber/red), single answer per widget, no competing visual weight |
| Forward momentum | Projected payoff date prominent on dashboard; milestone timeline always visible; progress acknowledged when plan is met or exceeded |
| Trust in data | Import staging shows source text alongside parsed values; duplicate flags are explicit; user confirms before anything commits |
| Accomplishment | Micro-success states on task completion (bill paid → smooth confirmation); projected date moving earlier highlighted briefly |
| Informed, not alarmed | Trending-negative is amber + one-line text, not a modal or alarm; escalates only on sustained deviation |
| Curiosity | Paydown Planner cards are exploratory (hover = preview); Analytics views unfold progressively, not all at once |

### Emotional Design Principles

1. **Anxiety-first design.** The user's starting emotion is financial anxiety. Every design decision should be evaluated against: does this reduce or increase anxiety? Reduce → keep. Increase → justify or remove.

2. **Quiet wins, quiet warnings.** Positive feedback (milestone reached, plan on track) should be present but not performative. Negative feedback (trending off plan) should be visible but not alarming. The app is a calm advisor, not a fire alarm.

3. **No shame, just signal.** Financial data is personal. The app surfaces information without judgment. A category over budget is a number, not a failure. The language and visual treatment must always be neutral and informational.

4. **Trust is infrastructure.** The emotional quality of every insight, every projection, every recommendation depends on whether the user trusts the underlying data. Design for data trust first; design for insight second.

## UX Pattern Analysis & Inspiration

### Inspiring Products Analysis

#### Akeneo PIM
**Why it works:** Complex product data management made navigable through always-current status displays, action-forward navigation, and a clean visual hierarchy that scales to high information density without overwhelming.

Key UX wins:
- **Live status everywhere** — status indicators reflect current state without requiring manual refresh. The user always knows what's current.
- **Action-forward navigation** — every view surfaces the obvious next action. You see the status, you see how to change it.
- **Scalable information hierarchy** — dense data organized into clean, readable structure. Complexity is contained, not hidden.

#### Atlassian Products (Jira, Confluence, Trello)
**Why they work:** Intuitive, minimal surfaces over deep feature sets. Power is accessible when needed; the surface stays clean by default.

Key UX wins:
- **Minimal chrome** — navigation and structural UI don't compete with content. The work occupies the foreground; the app recedes.
- **Consistent status vocabulary** — color, iconography, and label conventions are learned once and hold everywhere. Green/amber/red means the same thing on every screen.
- **Progressive feature access** — features are discoverable, not mandatory. Surface simplicity belies genuine depth.
- **Keyboard-first workflows** — tab order, shortcuts, and form navigation optimized for efficiency. Power users never need to reach for the mouse on common flows.
- **Inline editing** — field values editable in context without navigating to a separate page or triggering a modal. Jira's inline edit pattern is the benchmark for fast data correction.

#### Mint (Anti-Reference)
**Why it failed (for this user):** Chronic data freshness issues eroded trust in every number displayed. No clear primary action or visual hierarchy. Numbers without meaning or actionable context. Alerts with no clear resolution path.

### Transferable UX Patterns

**Navigation Patterns:**

- **Action-forward nav (from Akeneo)** — every view surfaces its primary action immediately. Bills view: "Mark Paid" is one click, no hunting. Transaction view: "Add Transaction" always accessible. The action comes to the user, not vice versa.
- **Minimal chrome (from Atlassian)** — persistent sidebar nav with icon + label, content takes the full remaining width. Navigation recedes; data occupies the foreground.

**Interaction Patterns:**

- **Inline editing in import staging (from Atlassian/Jira)** — category, merchant name, and amount editable directly in the staging table without modals or page navigations. Tab through rows, change what's wrong, confirm everything at once.
- **Keyboard-optimized transaction entry (from Atlassian)** — date, merchant, amount, category, account: all reachable via Tab in logical order. Quick-select category list triggerable without mouse. Target: 10 seconds or fewer for a known transaction.
- **Live status badges (from Akeneo)** — bills show "Due Today / Overdue / In 3 days" as live color-coded badges, not calculated text. Status is instant-readable.

**Visual Patterns:**

- **Consistent three-tone status system (from both)** — green = healthy/on-track, amber = attention-needed, red = action-required. Used for: budget burn bars, bill status, trending flags, import confidence scores. Same colors, same meaning, every surface.
- **Information density with clear hierarchy (from Atlassian)** — transaction list, import staging, amortization tables: dense data is acceptable when row-level hierarchy (primary field large, secondary small) and alternating row treatment guide the eye.
- **Data freshness signals (from Akeneo)** — every data surface shows when it was last updated or confirmed. "Last import: 3 days ago." The user never wonders if they're seeing stale data.

### Anti-Patterns to Avoid

1. **Stale data with no signal (Mint's fatal flaw)** — every data-heavy view must show data freshness. If transaction data hasn't been updated in 2 weeks, that's visible — not hidden. Trust depends on the user knowing what's current.

2. **Equal visual weight for unequal importance** — the dashboard's five widgets must not all compete equally. The projected payoff date is the confidence anchor; it gets more visual weight than the spending summary. Hierarchy is earned by importance, not allocated equally.

3. **Numbers without context** — "$847 on Dining" means nothing without the budget ($600) and the trend (+40% vs. last month). Every number shown must have at least one contextual anchor. Raw figures without reference points are Mint's legacy.

4. **Alerts without exit ramps** — every warning or flag must link directly to the corrective action. "You're 47 days behind plan" → links to Paydown Planner. "Possible duplicate detected" → links to the staging review row. Seeing a problem with no path forward creates anxiety, not awareness.

5. **Modals for routine edits** — modal dialogs for common actions (categorizing a transaction, correcting a merchant name) add two clicks and break flow. Inline editing for routine corrections; modals only for destructive or high-stakes confirmations.

### Design Inspiration Strategy

**Adopt directly:**
- Atlassian's minimal chrome + sidebar nav structure
- Akeneo's live status badges (color-coded, always current)
- Atlassian's inline editing pattern for import staging table
- Three-tone status color system (green / amber / red) applied consistently
- Keyboard-optimized tab order on all entry forms

**Adapt for this context:**
- Atlassian's progressive feature access → applied to dashboard (simple scan by default, drill-down by click) and Paydown Planner (cards on surface, amortization tables collapsed)
- Akeneo's action-forward navigation → adapted for financial workflows where the primary action changes by view (add transaction / mark paid / confirm import / activate strategy)

**Avoid entirely:**
- Any pattern that requires the user to wonder if the data is current (Mint's stale-sync UX)
- Equal visual weight distribution across competing information (Mint's hierarchy-free dashboard)
- Data presentation without contextual anchors (raw numbers without budget/trend comparison)
- Navigation-to-modal for routine field corrections

## Design System Foundation

### Design System Choice

**Primer CSS** (GitHub's design system, local copy)

Pre-selected by technical architecture (TC-2, NFR-6): Primer CSS is loaded from a local copy with no CDN dependency. Vanilla JS only; no component framework. Chart.js (local) for all chart rendering.

### Rationale for Selection

- **Architecture mandate:** NFR-6 prohibits external network calls; CDN-loaded frameworks are excluded. Primer CSS local copy is the established constraint.
- **Alignment with inspiration sources:** Primer's minimal chrome, functional aesthetics, and consistent status vocabulary align directly with the Atlassian/Akeneo patterns identified in Step 5. It recedes behind the content rather than competing with it.
- **Accessibility built-in:** Primer provides WCAG 2.1 AA compliant components out of the box — consistent focus states, color contrast ratios, semantic HTML patterns.
- **Native component coverage:** Progress bars, tables, forms, badges, alerts, and navigation components cover the majority of the app's surfaces without custom work.

### Primer Component Mapping

**Direct use (no customization):**

| Primer Component | Application |
|-----------------|-------------|
| `Progress` / `progress-bar` | Budget Burn progress bars, color-coded via state classes |
| `Label` / badge variants | Bill status (Due Today / Overdue / Upcoming), import flags |
| `Table` with `TableContainer` | Transaction list, import staging review, amortization tables |
| Form components (`FormControl`, `Select`, `TextInput`) | Transaction entry, budget setup, all data forms |
| `ActionList` + sidebar layout | App-wide navigation |
| `Flash` / `Banner` | Trending-negative warnings, import success/error feedback |
| `Blankslate` | Empty states: first-run dashboard, empty transaction list, no active plan |
| Color tokens (`--color-success-*`, `--color-attention-*`, `--color-danger-*`) | Consistent green/amber/red status system across all surfaces |

**Adapted use (Primer utilities + custom CSS):**

| Pattern | Approach |
|---------|----------|
| Dashboard widget grid | Primer layout utilities (`d-grid`, column classes) + custom widget-card component |
| Strategy comparison cards | Primer card-like structure with custom hero-number layout and hover states |
| Debt milestone timeline | Custom horizontal timeline built on Primer's flexbox utilities and color tokens |
| Import staging inline editing | Primer table + custom JS-driven cell-edit state using Primer form inputs |

### Customization Strategy

**Color tokens:** Use Primer's semantic color tokens throughout — never hardcode hex values. `--color-success-fg` / `--color-attention-fg` / `--color-danger-fg` map to the green/amber/red status system. This ensures dark mode compatibility if ever needed.

**Typography:** Primer's type scale used as-is. No custom fonts — system font stack only, consistent with the functional, non-decorative aesthetic.

**Spacing:** Primer's spacing scale (`var(--base-size-4)` through `--base-size-64`) used for all margins, padding, and gaps. No arbitrary pixel values.

**Custom components:** The four custom patterns (widget grid, strategy cards, milestone timeline, inline staging editor) are built as isolated CSS classes prefixed `pf-` (personal finance) to avoid collision with Primer's namespace.

**Chart.js integration:** Chart.js canvases are wrapped in Primer `Box` containers. Chart color palette pulls from Primer's color tokens via CSS custom properties passed to Chart.js at render time, ensuring chart colors match the app's status system.

## Defining Experience

### 2.1 The Core Interaction

**"I know exactly where I stand, financially, in 5 seconds."**

The defining experience is the dashboard scan — the habitual, low-effort check-in that replaces going blind. The user opens the app, lands on the dashboard, and in the time it takes their eyes to settle, they know: is this month on track? Is the debt paydown plan holding? Is anything requiring action today?

This is not a glamorous interaction. It should be invisible. The goal is for the answer to arrive before the user consciously asks the question. Color, position, and hierarchy do the work so the user doesn't have to.

**Secondary defining experience:** The Paydown Planner's strategy selection — "I can see the exact date I'll be debt-free, and I chose the path to get there." This is the emotional peak of the product, the moment a debt acquires a finish line. It provides the motivational foundation that makes the dashboard scan feel meaningful over time.

### 2.2 User Mental Model

**What the user brings:**
- Financial concepts are understood (APR, amortization, categories, budgets) — the concepts don't need to be taught, just represented clearly.
- No existing tracking system — no prior patterns to fight against, but also no established conventions to inherit. All UX conventions must be established clearly on first use.
- Implicit expectation: "spreadsheet-like but smarter" — data in tables and charts, totals and categories, with calculation work done automatically.
- Trust threshold: data must feel accurate and verifiable. Prior experience (Mint) left skepticism of systems that hide their data provenance.

**What the user does NOT bring:**
- A mental model for how paydown strategies differ — this is genuinely new territory. The UI must teach the difference between Avalanche and Snowball without requiring reading.
- A sense of "normal" for spending categories — until history accumulates, there are no baselines. The app cannot judge; it can only surface and compare.

### 2.3 Success Criteria

**Dashboard scan:**
- Status understood without reading a single number (color encodes it)
- Time from page load to "I know where I stand" < 5 seconds
- If something is off-track, the path to correction is immediately visible (linked widget)
- "Last updated" signal visible so data freshness is never ambiguous

**Transaction entry:**
- Known merchant entered and categorized in under 10 seconds
- Form fields reachable by Tab in logical order
- Category selection available without mouse (keyboard quick-pick)
- Duplicate detected and flagged if one exists

**Paydown Planner:**
- Strategy differences legible without reading the amortization table
- Hover on a strategy previews its timeline impact on the chart
- The "activate this plan" action is clearly distinct from "browsing"
- Projected payoff date is the first number the eye finds on each strategy card

**Import staging:**
- Errors and low-confidence rows appear at top, not buried
- "Apply to all [merchant]" available as a single-click bulk action
- Running total shown so user can cross-check against statement summary
- Final confirmation shows a summary (N transactions, N categories, date range)

### 2.4 Novel vs. Established Patterns

**Established patterns (adopt directly):**
- Dashboard widget grid with status-colored indicators → Akeneo/Atlassian style, no learning curve
- Paginated, sortable transaction table with filter bar → universal data table pattern
- Form-based transaction/bill entry with smart defaults → standard web form conventions
- PRG-pattern import flow (upload → stage → review → confirm) → established multi-step form pattern

**Adapted patterns (familiar structure, novel application):**
- Strategy comparison cards with hero numbers → familiar "pricing card" pattern applied to financial strategy selection. Innovation: hero number differs per card (not uniform comparison), each card has a human-voice rationale, hover triggers a chart preview.
- Inline table editing in staging → Jira-style inline edit applied to import review. Adaptation: bulk-apply action on category assignment.

**Novel patterns (require design attention):**
- Hover-to-preview timeline shift in Paydown Planner → no common analogue. Must feel exploratory and low-commitment. The chart morphs (not redraws) to show the selected strategy's payoff curve. Requires smooth animation: continuous enough to feel fluid, fast enough to feel responsive.
- Milestone timeline with trending indicator → horizontal debt-payoff timeline with projected dates and drift warning flag. The metaphor (timeline as progress bar toward a goal) is familiar; the dot-and-date implementation with divergence signal is specific to this app.

### 2.5 Experience Mechanics

#### The Dashboard Scan

**Initiation:** User navigates to the app URL. Dashboard is the default landing page — no redirect, no login, no modal. Page renders in under 2 seconds.

**Interaction:** The eye lands on the confidence anchor first — the projected payoff date or most urgent status indicator. Color communicates before text: green widgets are glanced and dismissed; amber/red widgets pull focus. The user reads only what demands attention.

**Feedback:** Each widget's status is self-contained and color-coded. Budget Burn: progress bar green below 70% spent, amber 70–100%, red over 100%. Upcoming Bills: badge per bill showing "In N days" (green), "Due Today" (amber), "Overdue" (red). Debt Paydown: projected payoff date in green if on track, red if trending behind.

**Completion:** User either (a) confirms green across the board and closes, (b) spots amber/red and clicks the widget to navigate to the relevant view, or (c) opens "Add Transaction" from the dashboard when the recent transactions widget triggers a reminder.

---

#### Paydown Strategy Selection

**Initiation:** User navigates to the Paydown Planner. Three strategy cards visible side-by-side. No dropdown, no radio buttons — the comparison IS the interface.

**Interaction:** User reads each card's hero number (fastest payoff date / total interest saved / monthly payment required). Secondary row shows all three metrics for direct comparison. Hovering a card activates a chart preview: the payoff timeline morphs to show that strategy's curve. User explores freely — no commitment from hovering.

**Feedback:** Chart updates smoothly on hover. The previewed card gets a subtle highlight state. The currently active strategy (if one exists) has a persistent "Active" badge.

**Completion:** User clicks "Activate this plan" on the chosen card. Confirmation banner appears: "Avalanche plan activated — debt-free by March 2028." Dashboard updates to reflect the active plan. Prior plan archived automatically with timestamp.

## Visual Design Foundation

### Color System

**Base approach:** Primer CSS default light theme, used as-is. No custom color overrides. The Primer color system provides all semantic tokens needed for this application.

**Primary palette (Primer defaults):**

| Role | Token | Value | Use |
|------|-------|-------|-----|
| Background | `--bgColor-default` | `#FFFFFF` | Page/content backgrounds |
| Surface | `--bgColor-muted` | `#F6F8FA` | Widget cards, table alternating rows |
| Border | `--borderColor-default` | `#D0D7DE` | Widget borders, table lines, dividers |
| Body text | `--fgColor-default` | `#1F2328` | Primary readable text |
| Muted text | `--fgColor-muted` | `#636C76` | Secondary labels, timestamps, captions |
| Primary action | `--fgColor-accent` / `--bgColor-accent-emphasis` | `#0969DA` | Links, primary buttons, active nav |

**Status color system (three-tone, consistent across all surfaces):**

| Status | Token | Value | Use |
|--------|-------|-------|-----|
| On track / healthy | `--fgColor-success` | `#1A7F37` | Budget under 70%, bills not due soon, plan on track |
| Attention needed | `--fgColor-attention` | `#9A6700` | Budget 70–100%, bills due within 3 days, minor drift |
| Action required | `--fgColor-danger` | `#CF222E` | Budget over 100%, overdue bills, significant plan drift |

**Background variants for status:**

| Status | Background token | Use |
|--------|-----------------|-----|
| Success fill | `--bgColor-success-muted` | Progress bar fill (on-track) |
| Attention fill | `--bgColor-attention-muted` | Progress bar fill (near limit) |
| Danger fill | `--bgColor-danger-muted` | Progress bar fill (over budget) |

**Chart.js color palette** (aligned with Primer tokens, passed at render time):
- Category spending: Primer's `scale.blue.4` through `scale.blue.8` for multi-series
- Debt payoff curves: one color per debt account from Primer's scale palette
- Status-aligned single-series: inherit the three-tone system above

### Typography System

**Typeface:** Primer system font stack — no custom fonts loaded.
```
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans",
             Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
```

**Type scale:**

| Level | Size | Weight | Use |
|-------|------|--------|-----|
| Page title | 24px (`f2`) | 600 | Page headings (Dashboard, Transactions, etc.) |
| Section heading | 20px (`f3`) | 600 | Widget titles, section headers |
| Subsection | 16px (`f4`) | 600 | Card titles, form group labels |
| Body | 14px (`f5`) | 400 | All body text, table content, form values |
| Small / caption | 12px (`f6`) | 400 | Timestamps, helper text, status captions |
| Hero numbers | 28–32px | 600 | Strategy card hero figures, projected payoff date |

**Hero number treatment:** The projected payoff date on the dashboard and the hero numbers on strategy cards use Primer's `f1`/`f0` scale (28–32px, weight 600). These are the only places where oversized type appears — reserved for the one-number-per-surface principle.

**Numeric formatting:** All currency and percentage values use tabular number rendering (`font-variant-numeric: tabular-nums`) to ensure alignment in tables and cards.

### Spacing & Layout Foundation

**Base unit:** 8px (Primer's `--base-size-8`). All spacing is multiples of 8.

**Spacing scale used:**
- `4px` — tight internal padding (badge text, compact labels)
- `8px` — component internal padding, small gaps
- `16px` — widget internal padding, form field gaps
- `24px` — widget-to-widget gap, section spacing
- `32px` — page section separation

**Layout structure:**
```
┌─────────────────────────────────────────────────────┐
│  Top nav bar (48px, Primer Header)                  │
├──────────┬──────────────────────────────────────────┤
│ Sidebar  │  Main content area                       │
│  240px   │  Remaining width (min ~900px)             │
│          │  Max content width: 1200px               │
│ ActionList│  Page padding: 24px                     │
│  nav     │                                          │
└──────────┴──────────────────────────────────────────┘
```

**Dashboard grid:** 2-column widget grid at standard desktop width.
- Row 1: Budget Burn (full width) — spans both columns
- Row 2: Upcoming Bills | Debt Paydown Progress
- Row 3: Recent Transactions | Monthly Spending Summary

**Content density:** Compact-functional. Consistent with Primer's default density (GitHub-style tables and forms). Not artificially airy — every vertical centimeter has a purpose.

**Widget cards:** `--bgColor-muted` background (`#F6F8FA`), `1px solid --borderColor-default` border, `6px` border-radius (Primer standard), `16px` internal padding.

### Accessibility Considerations

**Color contrast:** Primer's default token system is designed for WCAG 2.1 AA compliance. All text/background combinations using standard tokens meet 4.5:1 minimum contrast ratio. The three-tone status colors (success/attention/danger) are never used as the sole status indicator — always paired with text or iconography (color-blind safe).

**Focus states:** Primer's default focus ring (`outline: 2px solid --color-accent-fg`) applied to all interactive elements. Not removed or overridden.

**Semantic HTML:** Primer's component classes applied on top of appropriate HTML elements (`<table>` for data tables, `<nav>` for navigation, `<button>` for actions, `<a>` for links). No `div`-as-button patterns.

**Form accessibility:** All form inputs paired with `<label>` elements. Error messages use Primer's `FormControl.Validation` pattern with both color and text. Required fields marked with `aria-required`.

**Status indicators:** Budget Burn progress bars include `aria-valuenow`, `aria-valuemin`, `aria-valuemax` attributes. Status badges include screen-reader text for the color state ("On track", "Needs attention", "Overdue") not just visual color coding.

---

## Design Direction Decision

### Design Directions Explored

Three layout directions were prototyped and evaluated as interactive HTML mockups (`docs/ux-design-directions.html`):

| Direction | Sidebar | Grid | Character |
|-----------|---------|------|-----------|
| A — Balanced Grid | 240px labeled nav | 2-column | Akeneo/Atlassian feel; 5-second scan; labeled icons, balanced information density |
| B — Hero Numbers | 240px labeled nav | 2-column (taller widgets) | Large 36px hero numbers dominate; more dramatic but requires more scrolling |
| C — Compact + Dense | 56px icon-only nav | 3-column | Maximum horizontal space; icon-only nav requires learned navigation |

### Chosen Direction

**Direction A — Sidebar + Balanced Grid**

### Design Rationale

Direction A best delivers the "calm confidence" primary emotional goal and the 5-second scan success criterion:

- **Labeled sidebar navigation** ensures users always know where they are without learning icon meanings, directly mirroring the Akeneo/Atlassian inspirations (always visible context, clear action paths)
- **2-column balanced grid** provides the right information hierarchy: Budget Burn full-width at top (the macro "am I okay?" signal), Bills + Debt Paydown side-by-side (the forward-looking row), Recent Transactions + Monthly Summary anchoring the bottom
- **Balanced density** — enough detail for confidence, not so much it triggers anxiety; avoids the Mint anti-pattern of undifferentiated numbers without context
- **No excessive scrolling** for the primary dashboard view; the full picture is in one viewport on standard screens

### Implementation Approach

#### Layout Structure

```
┌─────────────────────────────────────────────────────┐
│  App Header (48px)                                  │
├──────────┬──────────────────────────────────────────┤
│          │                                          │
│ Sidebar  │  Main Content Area                       │
│ 240px    │  (flex-grow: 1, max-width ~960px)        │
│          │                                          │
│ ActionList│  ┌─────────────────────────────────┐   │
│ nav items│  │ Budget Burn (full-width)         │   │
│          │  ├───────────────┬─────────────────┤   │
│ - Dashboard│  │ Bills Due     │ Debt Paydown    │   │
│ - Transactions│ ├───────────────┴─────────────────┤   │
│ - Paydown │  │ Recent Transactions (full-width) │   │
│   Planner│  ├─────────────────────────────────┤   │
│ - Import  │  │ Monthly Summary (full-width)    │   │
│ - Settings│  └─────────────────────────────────┘   │
└──────────┴──────────────────────────────────────────┘
```

#### Key Layout Decisions

- **Sidebar**: Primer `ActionList` component, 240px fixed width, `color-canvas-subtle` background, nav items with icons + labels
- **Dashboard grid**: CSS Grid, `grid-template-columns: 1fr 1fr` with Budget Burn, Recent Transactions, and Monthly Summary spanning full width (`grid-column: 1 / -1`)
- **Content max-width**: ~960px centered in main area; prevents over-wide line lengths on large monitors
- **Flash messages**: Full-width banner at top of main content area (above grid), dismissible
- **Milestone timeline strip**: Sits below Budget Burn widget, above the Bills/Debt row — a horizontal scrollable strip of future milestone dates

#### Status Color Application

Three-tone status system applied consistently across all widgets:
- **Green** (`--color-success-fg` / `--color-success-emphasis`): On track, positive delta
- **Amber** (`--color-attention-fg` / `--color-attention-emphasis`): Warning, trending concern
- **Red** (`--color-danger-fg` / `--color-danger-emphasis`): Action required, negative trend

#### First-Run State

Empty state for new users uses Direction A's layout skeleton with friendly placeholder content and a prominent "Import your first statement" CTA centered in the main area. Sidebar nav remains visible so users understand the full app structure from day one.

---

## User Journey Flows

Six critical journeys were designed, covering the full product lifecycle from first run through recurring daily/weekly use.

### Journey 1: First-Run Onboarding

*The gateway to value. Empty state → first data on dashboard.*

```mermaid
flowchart TD
    A([App opened for first time]) --> B[Dashboard: First-Run Empty State\nFriendly placeholder, 'Import your first statement' CTA]
    B --> C{User choice}
    C -->|Click 'Import Statement'| D[Import UI: Upload PDF/CSV]
    C -->|Click 'Add Transaction'| E[Manual Transaction Form]
    C -->|Explore nav first| F[Sidebar nav visible\nAll sections accessible]
    F --> B

    D --> G[File picker: select PDF or CSV]
    G --> H[Issuer auto-detection\nProgress spinner <30s]
    H --> I{Detection confidence?}
    I -->|High| J[Staging screen: parsed transactions table]
    I -->|Low| K[Confirm issuer prompt] --> J

    J --> L[User reviews rows\nAuto-categorized, errors flagged at top]
    L --> M{User actions}
    M -->|Edit merchant/category| N[Inline row edit → save] --> L
    M -->|'Apply to all matching'| O[Bulk category applied] --> L
    M -->|Resolve duplicate flags| P[Keep or skip each duplicate] --> L
    M -->|Confirm all| Q[POST → commit to DB\nRedirect via PRG]

    Q --> R[Dashboard: Live\nBudget burn, recent transactions, monthly summary populated]
    R --> S([First-run complete: value delivered])

    E --> T[Fill form: date, merchant, amount, category, account]
    T --> U[Save → PRG redirect]
    U --> V[Dashboard: 1 transaction visible]
    V --> S
```

**Key design decisions:**
- Empty state keeps full sidebar visible — user sees the whole app, not just a wizard
- Import CTA is the primary action but manual entry is equally accessible
- Staging screen is the trust moment: data never auto-commits

---

### Journey 2: Weekly Dashboard Scan

*The core recurring ritual. 5-second situational awareness.*

```mermaid
flowchart TD
    A([User opens app]) --> B[Dashboard loads <2s\nFull widget layout visible]
    B --> C{Budget Burn status}
    C -->|All green| D[Scan bills row]
    C -->|Amber warning| E[Read which category is trending\nNo action required unless near red]
    C -->|Red alert| F[Flash message: 'Dining over budget'\nOne-click to Transactions filtered by category]
    E --> D
    F --> G[Transactions view: filtered\nUser reviews, closes tab, back to dashboard]
    G --> D

    D --> H{Bills due soon?}
    H -->|None in 7 days| I[Scan Debt Paydown widget]
    H -->|Bill due today/overdue| J[Bill row highlighted red/amber\nMark Paid button visible]
    J --> K{Mark paid?}
    K -->|Yes| L[POST: log transaction, advance due date\nPRG redirect to dashboard]
    K -->|Later| I
    L --> I

    I --> M{Active plan on track?}
    M -->|On track| N[Check milestone strip\nNote next milestone date]
    M -->|Deviation >5%| O[Amber flag: 'Chase balance above plan'\nLink to Paydown Monitor]
    O --> P{View monitor?}
    P -->|Yes| Q[Paydown Monitor: actual vs planned chart]
    P -->|No| N
    N --> R([Scan complete: user closes or continues to action])
```

**Key design decisions:**
- No required actions — scan can complete in 5 seconds with zero clicks
- Amber is informational, red triggers a flash message with a direct action link
- Milestone strip is visible without interaction — passive forward visibility

---

### Journey 3: Statement Import (PDF/CSV)

*The data trust ceremony. Bulk history loaded with user as final authority.*

```mermaid
flowchart TD
    A([User: 'Time to import April statements']) --> B[Nav: Import]
    B --> C[Import page: Upload zone + import history log]
    C --> D[Drag-drop or file picker: 1-N files]
    D --> E[Files queued: list of filenames]
    E --> F[Click 'Parse and Stage']
    F --> G[Server: detect issuer per file\nRun parser modules <30s]
    G --> H{Any parse errors?}
    H -->|Yes| I[Error summary at top:\nFile, page, raw text sample\nUser can note for manual entry]
    H -->|No| J[Staging table: all transactions]
    I --> J

    J --> K[Table: date, merchant raw to normalized, amount, category, duplicate flag]
    K --> L{Review actions}
    L -->|Edit row| M[Inline edit: merchant, category, amount] --> K
    L -->|Apply to all matching payees| N[Bulk category dialog] --> K
    L -->|Duplicate flagged| O{Keep or skip?}
    O -->|Keep| P[Row stays, duplicate flag cleared] --> K
    O -->|Skip| Q[Row greyed out, excluded from commit] --> K
    L -->|All reviewed| R[Summary: X transactions, Y duplicates skipped, Z errors]
    R --> S{Confirm?}
    S -->|Yes| T[POST: commit all kept rows\nImport batch logged\nPRG redirect]
    S -->|Cancel| U[Staged data discarded\nReturn to import page]

    T --> V[Dashboard: history-enriched\nBudget burn spans full month history]
    V --> W([Import complete])
```

**Key design decisions:**
- Errors surfaced prominently at top of staging, never silently swallowed
- Bulk "apply to all matching" reduces categorization from O(n) to O(unique merchants)
- Nothing committed until explicit user confirm — staged data is ephemeral

---

### Journey 4: Paydown Strategy Selection

*First-time strategy pick. Decision theater that removes paralysis.*

```mermaid
flowchart TD
    A([User: 'I want to start paying down debt']) --> B[Nav: Paydown Planner]
    B --> C{Cards already entered?}
    C -->|No| D[Card setup form:\nCard name, balance, APR, min payment, credit limit]
    D --> E[Add another card or Continue]
    E --> F[Extra monthly payment input:\nHow much beyond minimums can you pay monthly?]
    F --> G[Calculate button]
    C -->|Yes| G

    G --> H[Planner results:\n5 strategy hero cards displayed]
    H --> I[Hero cards sorted by: fastest payoff date\nEach card shows: Debt-free date, Total interest, Monthly cost]
    I --> J{User explores}
    J -->|Hover a card| K[Timeline preview: milestone strip updates\nShows this strategy's payoff sequence]
    J -->|Expand amortization| L[Per-card month-by-month table\nExpandable accordion]
    J -->|Scroll payoff chart| M[Chart.js timeline chart:\nAll 5 strategies overlaid for comparison]
    K --> J
    L --> J
    M --> J

    J -->|Ready to decide| N[Click 'Activate' on chosen strategy]
    N --> O[Confirm dialog:\nThis becomes your active paydown plan. You can change it anytime.]
    O -->|Confirm| P[POST: set plan as active\nPRG redirect to dashboard]
    O -->|Cancel| J

    P --> Q[Dashboard: Debt Paydown widget live\nShows per-card progress and projected payoff date]
    Q --> R([Strategy active: milestone strip populated])
```

**Key design decisions:**
- Hero cards sorted by fastest payoff by default (primary decision factor)
- Hover preview has zero commitment cost — safe to explore all strategies
- Confirm dialog sets expectation that switching is allowed anytime (removes lock-in anxiety)

---

### Journey 5: Manual Transaction Entry

*Frequent, repetitive action. Under 10 seconds for a known merchant.*

```mermaid
flowchart TD
    A([User has a receipt to log]) --> B[Nav: Transactions\nor Dashboard 'Add Transaction' button]
    B --> C[Transaction form:\nSmart defaults pre-filled]
    C --> D[Defaults: today's date, last-used account, Uncategorized]
    D --> E[User types merchant name]
    E --> F{Merchant recognized?}
    F -->|Yes| G[Category auto-filled from merchant map\nUser can override]
    F -->|No| H[Category stays Uncategorized\nUser selects from dropdown]
    G --> I[User enters amount]
    H --> I
    I --> J[Optional: notes field]
    J --> K[Submit]
    K --> L[POST → PRG redirect to Transactions list]
    L --> M[New transaction at top of list\nFlash: Transaction added]
    M --> N([Done: under 10 seconds elapsed])
```

**Key design decisions:**
- Smart defaults eliminate repetitive fields for the common case
- Merchant→category map means frequent merchants auto-categorize after first use
- PRG redirect prevents double-submit on browser refresh

---

### Journey 6: Bill Payment Flow

*One of the most frequent dashboard actions. Must be zero-friction.*

```mermaid
flowchart TD
    A([Dashboard: bill row shows due today or overdue]) --> B[Bills widget: bill highlighted red/amber\nAmount, payee, days overdue visible]
    B --> C{User action}
    C -->|Click 'Mark Paid'| D[POST: log transaction\nAdvance next due date\nPRG redirect]
    C -->|Click bill name| E[Bills full view: bill detail]
    E --> F[Mark Paid button here too]
    F --> D

    D --> G[Dashboard refreshes:\nBill row moves to next cycle\nRecent Transactions shows new entry\nBudget Burn updates if category budget applies]
    G --> H([Bill paid: 1 click from dashboard, transaction auto-logged])

    C -->|Bill is wrong amount| I[Click 'Edit Bill' → bill form]
    I --> J[Correct amount/date → Save\nPRG redirect]
    J --> B
```

**Key design decisions:**
- Mark Paid is a one-click action from the dashboard — no navigation required
- Transaction auto-logged with bill's category and amount — no second form
- PRG ensures redirect lands on a fresh dashboard state, not a stale POST

---

### Journey Patterns

Recurring patterns standardized across all 6 journeys:

**Navigation Patterns:**
- **Context-aware back**: After any action, PRG redirect returns to the most relevant view (dashboard or originating list), not a generic home
- **Sidebar always visible**: No full-screen takeovers. User can orient and escape at any time

**Decision Patterns:**
- **Confirm before large-scope commit**: Import commit, strategy activation → confirm dialog. Single-row actions (mark paid, save transaction) → no dialog
- **Preview before commit**: Paydown planner hover preview, import staging table — user sees outcome before committing

**Feedback Patterns:**
- **Flash messages on success**: Every POST → redirect lands with a confirming flash. Auto-dismissed after 5 seconds
- **Inline error surfacing**: Import errors and parse failures at top of staging screen, not a separate error page
- **Status color consistency**: Green/amber/red means the same thing in every context throughout the app

### Flow Optimization Principles

1. **One click to the critical action**: Mark Paid from dashboard, Add Transaction from nav, Import from nav — primary actions reachable in one click from any view
2. **Smart defaults eliminate repetition**: Date, account, and merchant category pre-filled; user corrects exceptions, not the common case
3. **PRG everywhere**: All state-changing POSTs redirect to a GET. Browser refresh is safe on every screen
4. **Progressive disclosure**: Import staging shows overview first; amortization tables and parse error detail are expandable on demand
5. **Error recovery is non-blocking**: Import parse errors don't halt the flow — they surface alongside valid data, letting the user proceed and handle exceptions manually

---

## Component Strategy

### Design System Components

Primer CSS provides full coverage for these needs — no custom work required:

| Primer Component | Used For |
|---|---|
| `ActionList` | Sidebar navigation (labeled items + icons) |
| `Button` (primary/secondary/danger) | All CTAs, form submits, confirm dialogs |
| `Flash` / `Banner` | PRG redirect success/error messages |
| `FormControl` + `TextInput` + `Select` | Transaction form, settings, bill form |
| `Checkbox` / `Radio` | Staging row selection, category choices |
| `Table` | Transaction list, import staging table, amortization table |
| `Paginate` | Transaction list pagination |
| `Dialog` | Confirm dialogs (strategy activation, import commit) |
| `Blankslate` | Empty/first-run states |
| `Spinner` | Import parse progress, chart loading |
| `Tooltip` | Hover explanations on strategy cards, status icons |
| `Label` | Base for status badges |
| `Header` | App header bar |
| `UnderlineNav` / `TabNav` | Section tabs where needed |
| `ProgressBar` | Base element extended by custom budget/debt bars |
| `Overlay` | Dropdown menus, bulk-apply dialog |

### Custom Components

Seven custom components using the `pf-` prefix to avoid Primer collisions. All use Primer design tokens exclusively — no hardcoded values.

#### `pf-DashboardWidget`

**Purpose:** Consistent shell for every dashboard card. Ensures uniform visual rhythm, header anatomy, and link-out affordance across all 5 widgets.

**Anatomy:**
```
┌─────────────────────────────────────────┐
│ [Icon] Widget Title          View all → │  ← header strip (canvas-subtle bg, border-bottom)
├─────────────────────────────────────────┤
│  [content slot]                         │
└─────────────────────────────────────────┘
```

**States:** `default`, `loading` (Primer Spinner in content slot), `empty` (mini Blankslate with context-specific prompt), `stale` (amber header tint + "Last updated X ago")

**Variants:** `full-width` (spans both grid columns), `half-width` (single column)

**Accessibility:** `<section aria-label="{title}">`, header is `<h2>`, "View all" link has `aria-label="View all {title}"`

---

#### `pf-BudgetBurnBar`

**Purpose:** Per-category budget progress bar with dynamic status theming. Core visual of the Budget Burn widget.

**Anatomy:**
```
Groceries                         $342 / $400
[████████████████████░░░░░░░░]  85%
```

**Thresholds:**

| % Used | Bar Color | Meaning |
|--------|-----------|---------|
| < 70% | `--color-success-emphasis` | On track |
| 70–90% | `--color-attention-emphasis` | Watch |
| > 90% | `--color-danger-emphasis` | Near limit |
| > 100% | `--color-danger-emphasis` + overflow indicator | Over budget |

**Variants:** `compact` (dashboard, labels only), `full` (budget view, amounts + edit link)

**Accessibility:** `role="progressbar"`, `aria-valuenow`, `aria-valuemin="0"`, `aria-valuemax="100"`, `aria-label="{category}: {pct}% of budget used"`

---

#### `pf-StrategyCard`

**Purpose:** Paydown Planner comparison card. Displays one strategy's three hero metrics, supports hover preview, and hosts the Activate action.

**Anatomy:**
```
┌─────────────────────────────────────┐
│ Avalanche Strategy          [Active]│
│                                     │
│      March 2027                     │  ← hero date (32px)
│      Debt-free date                 │
│                                     │
│  $4,820 interest  ·  +$0/mo cost   │
├─────────────────────────────────────┤
│        [Activate This Plan]         │
└─────────────────────────────────────┘
```

**States:** `default`, `hover` (elevated shadow, milestone strip updates), `active` (`--color-success-muted` bg, "Active Plan" badge), `loading` (skeleton shimmer)

**Variants:** `full` (planner page, CTA visible), `summary` (dashboard widget, date only)

**Accessibility:** `role="article"`, hover preview also triggered by `focus`, keyboard `Enter` activates

---

#### `pf-MilestoneStrip`

**Purpose:** Horizontal scrollable timeline of future milestone dates. Passive forward visibility on dashboard; animates on strategy card hover in planner.

**Anatomy:**
```
────●──────────────●──────────────●──────────────●────
  Today        Chase paid       Kohls paid    Debt-free
 May '26        Aug '26          Jan '27       Mar '27
```

**States:** `default` (active strategy milestones), `preview` (hovered strategy milestones, 150ms transition), `empty` ("Activate a paydown plan to see your timeline")

**Accessibility:** `role="list"`, each milestone `role="listitem"`, `aria-label="{account} paid off {date}"`, `aria-current="date"` on today marker

---

#### `pf-StagingRow`

**Purpose:** Extends a standard table `<tr>` for the import staging screen. Adds inline editing, duplicate flagging, and skip state.

**Anatomy:**
```
[☐] 04/15  Amazon          Shopping ▾    $67.43   [⚠ Dup]  [Skip]
           ↳ raw: "AMZN*MP 04152026"
```

**States:** `default`, `editing` (inline inputs visible), `duplicate` (amber left border + ⚠ badge, Keep/Skip buttons), `skipped` (greyed out, strikethrough, Undo link), `error` (red left border + parse error message)

**Behaviors:**
- Click merchant cell → toggle inline edit
- Hover merchant → "Apply to all [merchant]" link appears → bulk category `Overlay`
- Row checkbox → batch actions bar at table top

**Accessibility:** Inline edits use `aria-label="Edit merchant for row {date} {amount}"`, skip action `aria-pressed`

---

#### `pf-StatusBadge`

**Purpose:** Standardized three-tone status label for bills and debt entries.

| Variant | Label | Tokens | Trigger |
|---------|-------|--------|---------|
| `upcoming` | Upcoming | success-fg on success-subtle | Due in 4–30 days |
| `due-soon` | Due Soon | attention-fg on attention-subtle | Due in 1–3 days |
| `due-today` | Due Today | attention-emphasis (filled) | Due today |
| `overdue` | Overdue | danger-fg on danger-subtle | Past due |
| `paid` | Paid | neutral-muted | Current cycle paid |

**Accessibility:** `role="status"`, hidden `<span class="sr-only">` with full context per variant

---

#### `pf-DebtProgressBar`

**Purpose:** Per-card paydown progress with plan deviation indicator. Used in Debt Paydown widget and Paydown Monitor.

**Anatomy:**
```
Chase Sapphire                    $3,240 remaining
[████████████████░░░░░░░░░░░░]  62% paid off  ↑ Plan: on track
```

**States:** `on-track` (success fill), `deviation` (amber fill past plan-line marker, "X% above plan"), `paid-off` (full success fill + celebration state)

**Special:** Vertical plan-line marker at projected balance for current month. Actual > marker triggers deviation state.

**Accessibility:** `role="progressbar"`, `aria-valuenow` = % paid off, `aria-label="{card name}: {pct}% paid off"`

---

### Component Implementation Strategy

**Hierarchy:**
```
Primer CSS (foundation tokens + base components)
  └── pf-* custom components (built exclusively on Primer tokens)
        └── Dashboard compositions (pf-DashboardWidget wrapping domain components)
```

**File structure:**
```
templates/
  components/
    dashboard_widget.html     ← pf-DashboardWidget Jinja2 macro
    budget_burn_bar.html      ← pf-BudgetBurnBar macro
    strategy_card.html        ← pf-StrategyCard macro
    milestone_strip.html      ← pf-MilestoneStrip macro
    staging_row.html          ← pf-StagingRow macro
    status_badge.html         ← pf-StatusBadge macro
    debt_progress_bar.html    ← pf-DebtProgressBar macro
static/
  css/
    components.css            ← all pf-* styles (Primer tokens only, no hex)
  js/
    milestone_strip.js        ← hover preview behavior
    staging_table.js          ← inline edit, bulk select, bulk apply
    strategy_cards.js         ← hover → milestone strip update
```

All `pf-*` components are Jinja2 macros rendered server-side. No framework. Vanilla JS only for interactive behaviors (hover preview, inline editing, bulk select).

### Implementation Roadmap

**Phase 1 — Core (required for Phase 1 feature launch):**

| Component | Required By |
|---|---|
| `pf-DashboardWidget` | All 5 dashboard widgets |
| `pf-BudgetBurnBar` | Budget Burn widget, Budget Management view |
| `pf-StatusBadge` | Bills widget, Bills full view |
| `pf-StrategyCard` | Paydown Planner comparison view |

**Phase 2 — Data richness (required for import launch):**

| Component | Required By |
|---|---|
| `pf-MilestoneStrip` | Dashboard, Paydown Planner hover preview |
| `pf-StagingRow` | Import staging screen |
| `pf-DebtProgressBar` | Debt Paydown widget, Paydown Monitor |

**Phase 3 — Enhancement (analytics layer):**

No new custom components required. Analytics views compose Chart.js charts inside `pf-DashboardWidget` shells and standard Primer tables.

---

## UX Consistency Patterns

Desktop-only application (Chrome/Firefox/Edge per PRD NFR-5). No mobile considerations required.

### Button Hierarchy

Built on Primer `Button` variants. One primary action per view maximum.

| Tier | Primer Class | When to Use | Examples |
|------|-------------|-------------|---------|
| **Primary** | `btn-primary` | Single most important action on a surface | Save transaction, Confirm import, Activate strategy, Mark Paid |
| **Secondary** | `btn` (default) | Supporting, non-destructive alternatives | Cancel, Edit, Add another card, Export CSV |
| **Danger** | `btn-danger` | Irreversible or destructive actions | Delete transaction, Delete bill |
| **Invisible** | `btn-invisible` | Low-emphasis in-context actions | "View all →" widget links, "Undo skip", inline edit toggle |
| **Link** | `btn-link` | Navigation styled as text | "Apply to all matching", breadcrumb navigation |

**Rules:**
- Never more than one `btn-primary` visible at a time on a given form or dialog
- Danger actions require Primer `Dialog` confirmation except reversible soft actions (skip staging row)
- Button order: Primary right, Cancel/secondary left
- Disabled via `aria-disabled="true"` when prerequisites unmet (e.g., Calculate disabled until cards entered)

### Feedback Patterns

**Flash Messages (PRG redirects):**

Rendered via Primer `Flash` at top of main content, above widget grid. Auto-dismiss 5 seconds; dismissible via × button.

| Variant | Primer Class | Used When |
|---------|-------------|-----------|
| Success | `flash-success` | Completed: "Transaction saved", "Import complete (47 transactions)", "Bill marked paid" |
| Warning | `flash-warning` | Partial success: "Import complete — 3 rows had parse errors (see log)" |
| Error | `flash-error` | Failed: "Could not parse file — unsupported issuer" |
| Info | `flash` | Neutral: "Duplicate detected — review required" |

**Inline Validation (forms):**
- Fires on blur (field loses focus), not on keystroke
- Primer `FormControl` with `validation="error"` + `FormControl.Validation` message below field
- Amount field: client-side format check (numeric, max 2 decimal places); server validates range
- Required fields: empty submission triggers error state (no asterisk markers)

**Deviation Alerts (Paydown Monitor):**
- In-app only (no push/email per PRD non-goals)
- Amber `pf-StatusBadge` on Debt Paydown widget header when any card exceeds plan by >5%
- Full detail in Paydown Monitor view — not surfaced in a modal

### Form Patterns

**Standard Layout:**
- Single-column, max-width 480px
- Primer `FormControl` wrapping each input
- Labels always visible above field (no placeholder-as-label)
- Submit button bottom-right; Cancel/back link bottom-left

**Smart Defaults (transaction entry):**
- Date: today (server-supplied)
- Account: last-used (session)
- Category: "Uncategorized" unless merchant matched
- Notes: empty

**Amount Input:**
- `type="text"` with `inputmode="decimal"` (avoids browser spinner on `type="number"`)
- Accepts `67.43`, `67`, `.43` — normalized to 2 decimal places on blur
- Credits as negative: `-25.00`

**Category Select:**
- Primer `Select` with optgroup: system categories → custom categories → "— Manage categories —" link
- Same grouping used in import staging inline select

**Bulk Card Entry (Paydown Planner):**
- Each card is a `<fieldset>`; JS appends new fieldset on "Add another card"
- Remove button per card (danger invisible); disabled when only one card remains

**Confirm Before Destructive Actions:**
- Hard deletes: Primer `Dialog` with "Delete [item]" primary danger button
- Staging row "Skip": soft/reversible — no dialog
- Import "Cancel" (discard staged): dialog: "This will discard all staged transactions. Continue?"

### Navigation Patterns

**Sidebar (primary):**
- Primer `ActionList`, always expanded at 240px fixed
- Active item: `aria-current="page"` + `--color-accent-fg` text + left accent border
- Items: Dashboard · Transactions · Budgets · Bills & Debts · Paydown Planner · Import · Settings
- No sub-navigation — all sections are top-level

**"View all" Links:**
- `pf-DashboardWidget` header → full management view for that widget's domain
- Transaction widget → Transactions list pre-filtered to current month

**Contextual Back (PRG redirect targets):**
- Add/edit transaction → Transactions list
- Mark bill paid → Dashboard
- Import confirm → Dashboard (success flash)
- Activate strategy → Dashboard (success flash)

**Breadcrumbs:**
- Primer `Breadcrumb`, used only on deep sub-views: Paydown Monitor, Budget history, Import history detail
- Not used for top-level navigation

**Multi-step flows (import):**
- Step indicator at top of content area showing: Upload → Stage → Confirm
- Explicit "Back" link within content area — no reliance on browser back

### Modal and Overlay Patterns

**Confirm Dialogs (Primer `Dialog`):**
- Structure: title (what's happening), body (consequence), Primary button right, Cancel left
- Keyboard: `Escape` cancels, `Enter` on focused confirm button confirms
- Never for informational messages — use Flash banners instead

**Bulk-Apply Overlay (import staging):**
- Primer `Overlay` anchored to triggering row
- Content: merchant name (read-only), category `Select`, "Apply to X transactions" primary button, Cancel
- Closes on confirm, cancel, or click-outside

**Amortization Table:**
- Inline `<details>`/`<summary>` accordion per card, not a modal
- `Enter`/`Space` on `<summary>` toggles

### Empty States and Loading States

**Empty States (Primer `Blankslate`):**

| Context | Heading | CTA |
|---------|---------|-----|
| First-run dashboard | "Welcome — let's get started" | "Import Statement" |
| No transactions (filtered) | "No transactions found" | "Clear filters" |
| No active paydown plan | "No active paydown plan" | "Open Paydown Planner" |
| No bills set up | "No bills tracked yet" | "Add a bill" |
| Import history empty | "No imports yet" | "Import Statement" |

**Loading States:**
- Dashboard initial load: Primer `Spinner` centered in each widget content slot
- Import parse: full-width indeterminate `ProgressBar` + "Parsing [filename]…" label
- Strategy calculation: `Spinner` replaces strategy cards area
- Charts: `Spinner` centered in chart container until Chart.js renders

Skeleton loading not used (SSR pages; full HTML delivered on load).

### Search and Filtering Patterns

**Transaction Filters:**
- Filter bar above table (not sidebar)
- Controls: date range, category multi-select, account multi-select, amount range, free-text search
- Apply on form submit (`method="get"`) — not live/onChange
- State stored in URL query params (shareable, browser-back compatible)
- "Clear filters" link when any filter active; badge showing active filter count

**Free-text Search:**
- Searches merchant name + notes fields (server-side `LIKE`)
- Minimum 2 characters; full page reload on submit (no AJAX)

**Sorting:**
- Columns: date (default desc), amount, merchant
- Sort state in URL params (`?sort=amount&dir=desc`)
- Primer column header sort indicator on active column

---

## Responsive Design & Accessibility

### Responsive Strategy

**Desktop-only application.** No mobile or tablet optimization required (PRD NFR-5). Designed for Chrome/Firefox/Edge on desktop monitors, accessed via LAN/localhost.

**Desktop layout behavior:**

| Viewport Width | Behavior |
|---|---|
| ≥ 1280px | Optimal — full 2-column grid with comfortable gutters |
| 1024px–1279px | Functional — grid remains 2-column, widget content adjusts |
| 900px–1023px | Acceptable minimum — sidebar + content remain side-by-side |
| < 900px | Not supported |

The fixed 240px sidebar + fluid main content area handles desktop viewport variation via flexbox without explicit breakpoints. Main content caps at ~1200px max-width on wide monitors to prevent excessive line lengths.

### Breakpoint Strategy

**Single effective breakpoint** at 900px (minimum supported width). Below 900px: banner displayed — "This application is optimized for desktop use." No mobile-first cascade. Desktop-first base styles, no overrides needed.

Dashboard CSS Grid: `grid-template-columns: repeat(2, 1fr)` — no media query collapse needed.

### Accessibility Strategy

**Target: WCAG 2.1 Level AA throughout.**

Primer CSS is designed to WCAG 2.1 AA compliance. Custom `pf-*` components built on Primer tokens inherit the same contrast baseline.

**Color Contrast:**
- All body text: `--color-fg-default` (#1F2328) on white (#FFFFFF) = 16.7:1 ✓
- All three status token pairings (`success`, `attention`, `danger`) pass AA against their subtle backgrounds
- **Critical rule:** Status is never communicated by color alone — every color indicator pairs with a text label or icon

**Keyboard Navigation:**

| Flow | Requirement |
|---|---|
| Sidebar navigation | `Tab` through items, `Enter` to navigate, `aria-current="page"` on active |
| Transaction form | Full tab order; `Tab` → `Enter` to submit |
| Staging table | `Tab` through rows; `Enter` to edit; `Space` on checkboxes |
| Strategy cards | `Tab` through cards; `Enter` to activate; `focus` triggers hover preview |
| Confirm dialogs | Focus trapped; `Escape` cancels; `Tab` cycles buttons |
| Milestone strip | Each milestone is a `<button>` in tab order |
| Bulk-apply overlay | Focus trapped until closed |

**Focus Management:**
- Visible focus ring on all interactive elements (never suppress `outline`)
- After PRG redirect: focus moves to `<main>` or Flash message (`tabindex="-1"` + JS `.focus()`)
- After dialog close: focus returns to triggering element
- After staging row inline edit: focus returns to edit-trigger cell

**Screen Reader Compatibility (NVDA + Chrome, Windows):**

| Requirement | Implementation |
|---|---|
| Page titles | Unique `<title>` per view: "Dashboard — Finance App" |
| Landmark regions | `<header>`, `<nav aria-label="Main navigation">`, `<main>`, `<footer>` |
| Heading hierarchy | `<h1>` page title, `<h2>` widget/section, `<h3>` sub-sections |
| Live regions | Flash: `role="status"` (non-urgent) or `role="alert"` (errors) |
| Table semantics | `<th scope="col">` on all headers; `<caption>` on all data tables |
| Form labels | Explicit `<label for="...">` on every input — never aria-label as sole label |
| Icon-only buttons | `aria-label` required |
| Status badges | `role="status"` + `<span class="sr-only">` full context text |
| Progress bars | `role="progressbar"` with `aria-valuenow/min/max` on all `pf-*` bars |

**Skip Links:** "Skip to main content" as first focusable element in `<body>` (visually hidden until focused).

### Testing Strategy

**Keyboard Testing (every release):**
- Tab through each primary flow without mouse
- Verify: all elements reachable, focus visible, no focus traps outside dialogs
- Key flows: import staging table, confirm dialogs, bulk-apply overlay, strategy card focus preview

**Color Contrast Testing:**
- Browser DevTools accessibility inspector on any new `pf-*` component
- axe-core or Lighthouse audit on each primary view

**Screen Reader Testing (pre-launch, per phase):**
- NVDA + Chrome: dashboard headings/status, transaction form labels/errors, import staging row announcements

**Automated Testing:**
- Lighthouse accessibility score ≥ 90 on dashboard, transactions, import staging
- Run on each phase launch

**Browser Compatibility (per NFR-5):**
- Chrome stable + Firefox stable + Edge stable
- Test matrix: each browser × each primary view per phase launch

### Implementation Guidelines

**Semantic HTML first:**
- Correct element before ARIA: `<button>` not `<div onclick>`, `<nav>` not `<div class="nav">`, real `<table>` not CSS-faked grid
- `<details>`/`<summary>` for accordions (amortization tables)
- `<fieldset>`/`<legend>` for grouped inputs (card entry in Paydown Planner)

**CSS:**
- `rem` for all font sizes (respects user browser font preferences)
- `px` acceptable for borders and fixed UI chrome only
- `prefers-reduced-motion`: disable `pf-MilestoneStrip` animated swap and any transition > 200ms

**JavaScript:**
- Explicitly call `.focus()` after dynamic DOM changes
- All JS-driven interactions degrade gracefully without JS (filter form, pagination work as standard GET forms)
- `aria-expanded` on all toggle controls; `aria-live="polite"` on import parse progress text

**No accessibility debt policy:** WCAG AA violations are bugs, not backlog items. All `pf-*` components ship with full ARIA spec from day one.
