# Design: own-ledger — Personal Finance Dashboard

Generated by /office-hours on 2026-04-02
Branch: none (greenfield, no git repo yet)
Repo: own-ledger
Status: APPROVED
Mode: Builder

## Problem Statement

The user maintains a detailed personal budget spreadsheet (LibreOffice Calc, .ods) they've evolved over years. It tracks 14 sections: income per paycheck, bill definitions with priority/status, a forward-looking monthly ledger with rolling balance, luxury spend allowances, miscellaneous spend, savings goals, vacation planning, and dues to others. The spreadsheet works but is tedious to maintain, chained to a laptop, and makes some sections inconvenient enough that they go unused (savings goals, vacation planning).

The user wants to turn this into a cross-platform app (Windows + Android) for personal use only. Not a startup, not for distribution. A personal tool that preserves their mental model while making it faster and more portable.

## What Makes This Cool

This isn't another budgeting app. It's a forward-looking monthly ledger with rolling balance projection. No existing app does this well. YNAB, Monarch, Actual Budget... they all categorize what already happened. This system projects forward through a month: here's your income, here are your bills sorted by date, here's your running balance as each one hits.

The "promote from misc to line item" pattern is novel. Most apps force upfront categorization. This model: dump it in misc, surface it later via fuzzy search if it becomes recurring. Low friction by default, precision when you need it.

The credit card payment rules are a constraint solver: given net balance, buffer amount, and per-card priority rules, calculate optimal payment amounts. The user currently does this math in their head every month. The app should just do it.

Core design philosophy from the user: "I manage my money to get it out of the way and live life."

## Constraints

- **Platforms:** Windows (primary desktop) + Android (primary mobile)
- **Cost:** $0 forever. No third-party services that could start charging or shut down
- **Data:** SQLite database, single-user
- **Sync:** Robust cross-device sync that's permanently free. Options explored: Dropbox file sync (simple but corruption risk), self-hosted sync server (free-tier cloud or local), CRDT-based SQLite. Decision deferred to implementation.
- **Architecture:** Each month is its own entity. New months clone from previous month but have no live connections. Edits to bills, payouts, etc. only affect the month at hand.
- **Interaction:** Click-to-edit inline for all values. Remove buttons (x) for ephemeral items. Settings screen for paycheck config, CC payment rules, and buffer amount.
- **UI density:** Dashboard-first, dense on desktop. Mobile gets stacked cards with secondary screens max 2 taps away. Speed and control are the primary UX goals.

## Premises

1. The spreadsheet's mental model is the right one to digitize. The 14-section layout evolved over years of actual use and matches how the user thinks about money.
2. Sync needs to be robust AND permanently free. No dependency on services that could change terms or shut down.
3. Dashboard-first, not one-screen-only. Core spend is the default view, dense and information-rich. Secondary screens (bill definitions, savings goals) are fine but max 2 taps away.
4. Manual-first, automate later. V1 is data entry like the spreadsheet. Bank connections, auto-categorization, trend analysis come in V2.
5. Windows + Android are the two platforms that matter.

## Cross-Model Perspective

An independent Claude subagent reviewed the session context cold:

- **Coolest version not yet considered:** A "financial weather forecast" layer. The spreadsheet's sections already form a domain model with priority ordering. Formalize it into a solver that produces a single "safe to spend" number: "You have $340 of discretionary headroom before your next danger zone on April 18th."
- **Key quote:** "I manage my money to get it out of the way and live life." — This is the entire design brief. Every feature should reduce the cognitive load of answering "am I okay right now?"
- **50% tool:** Actual Budget (local-first, SQLite, proven sync). Missing: forward-looking ledger, bill priority system, misc-to-line-item promotion, dense dashboard.
Synthesis: The rolling balance calculator that enables the "safe to spend" number should be built as a pure function in V1's data layer. The "safe to spend" UI surface is deferred to V2. Actual Budget is best used as inspiration, not forked.

## Approaches Considered

### Approach A: Flutter + drift (Native Cross-Platform)
Flutter app for Windows + Android with drift (SQLite ORM). Sync via versioned JSON snapshots or CRDT. Single codebase, single language (Dart). Most mature SQLite integration. Responsive layouts handle desktop density vs mobile simplicity naturally.
- Effort: M (human: ~3-4 weeks / CC: ~2-3 days)
- Risk: Low
- Pros: True native on both platforms, fast startup, single codebase
- Cons: Dart ecosystem smaller than JS/TS, desktop Flutter less battle-tested than mobile

### Approach B: Tauri + SvelteKit (Web-tech Desktop + PWA Mobile)
Tauri app for Windows (Rust backend, web frontend), SvelteKit PWA for Android. Closest to Simple Bank's webapp feel. cr-sqlite gives CRDT sync for free.
- Effort: M-L (human: ~4-5 weeks / CC: ~3-4 days)
- Risk: Medium
- Pros: Fastest UI iteration, tiny binary, huge web ecosystem
- Cons: Two shells (Tauri + PWA), PWA limitations on Android

### Approach C: React Native + Expo (JS Everywhere)
Expo app targeting Android + Windows. Largest community but react-native-windows is less mature.
- Effort: M (human: ~3-4 weeks / CC: ~2-3 days)
- Risk: Medium-High
- Cons: Windows support is the weak link

## Recommended Approach

**Approach A: Flutter + drift.** Best single-codebase experience for the exact platform targets (Windows + Android), most mature SQLite integration, lowest risk. Flutter's responsive layout system handles the desktop-dense / mobile-stacked split naturally.

**Risk note:** Flutter desktop (Windows) is mature but less battle-tested than mobile for dense data tables and inline text editing. Mitigate by testing inline editing and table scroll performance on Windows in the first week. Overall risk: Low-Medium.

## Data Model

### Entities and Relationships

```
MonthPlan (one per month, self-contained)
├── id: UUID
├── year: int
├── month: int (1-12)
├── opening_balance: decimal  (auto-carried from previous month's remaining, editable)
├── buffer_amount: decimal    (default from Settings, overridable per month)
├── created_at: timestamp
└── cloned_from: UUID?        (null for first-ever month)

PaycheckDefinition (per month, 1-N per MonthPlan)
├── id: UUID
├── month_plan_id: FK → MonthPlan
├── sequence: int             (#1, #2, etc.)
├── date: date
├── amount: decimal
└── sort_order: int

PayPeriodItem (essentials within a pay period: gas, groceries, other)
├── id: UUID
├── paycheck_id: FK → PaycheckDefinition
├── description: text
├── comment: text?
├── amount: decimal
├── date: date
├── is_paid: bool
└── sort_order: int

BillDefinition (global, cloned into each month — edits in a month are local)
├── id: UUID
├── month_plan_id: FK → MonthPlan
├── name: text                (e.g., "Rent")
├── subtitle: text?           (e.g., "Duke Energy")
├── due_day: int              (1-31)
├── default_amount: decimal
├── amount_override: decimal? (ephemeral per-month override, null = use default)
├── priority: enum(green, yellow, red)
├── is_auto_pay: bool         (AUTO → no checkbox in ledger, no manual action needed)
├── is_freeform: bool         (freeform → amount computed by payment rules engine)
├── outstanding_balance: decimal?  (for loans/credit cards, null for non-debt)
├── payment_rule_id: FK → PaymentRule?
└── sort_order: int

PaymentRule (Settings screen, referenced by freeform BillDefinitions)
├── id: UUID
├── name: text                (e.g., "Chase priority paydown")
├── priority_rank: int        (lower = higher priority for remainder allocation)
├── min_payment: decimal
├── fixed_addon: decimal      (e.g., +$200 for priority card)
├── cap: decimal?             (max total payment, null = uncapped)
└── receives_remainder: bool  (gets leftover after buffer)

Payout (ephemeral per-month items, before or after bills)
├── id: UUID
├── month_plan_id: FK → MonthPlan
├── position: enum(priority, notable)  (before bills or after bills)
├── description: text
├── comment: text?
├── amount: decimal
├── date: date
├── is_paid: bool
└── sort_order: int

MiscItem (per month, some clone via recurring flag)
├── id: UUID
├── month_plan_id: FK → MonthPlan
├── description: text
├── comment: text?
├── amount: decimal
├── date: date?
├── is_recurring: bool        (if true, clones to next month on month creation)
├── is_enabled: bool          (toggle: disabled items excluded from total)
├── promoted_to_bill_id: UUID?  (if promoted, FK → BillDefinition created from this item)
└── sort_order: int

LuxuryConfig (per month)
├── id: UUID
├── month_plan_id: FK → MonthPlan
├── eating_out_budget: decimal
└── (misc_other is derived: remaining - eating_out_budget)

Settings (singleton, app-level)
├── default_buffer: decimal   (e.g., $300)
├── paycheck_count: int       (default for new months)
└── sync_config: json?        (deferred)
```

### Month Clone Rules

When "+" creates a new month from the current month:

| Field / Entity | Behavior |
|---|---|
| opening_balance | Set to previous month's computed Remaining value |
| BillDefinitions | Cloned with amount_override reset to null |
| PaycheckDefinitions | Cloned (same count, dates adjusted to new month, same amounts) |
| PayPeriodItems | Cloned as template (Gas/Transit, Groceries, Other) with amounts zeroed |
| Payouts (priority + notable) | NOT cloned (ephemeral, per-month only) |
| MiscItems where is_recurring=true | Cloned with is_enabled=true |
| MiscItems where is_recurring=false | NOT cloned |
| LuxuryConfig | Cloned from previous month |
| buffer_amount | Inherited from Settings.default_buffer |

First-ever month: user manually enters opening_balance. All other entities start empty; user adds bills, paychecks, etc. through the UI.

### Key Definitions

**Net Balance (aka Remaining):** Computed top-to-bottom through the ledger as a rolling balance:

```
balance = opening_balance
balance += sum(paycheck amounts)
balance -= sum(all pay_period_items)  ← all items are always included in projection; is_paid is display-only
balance -= sum(priority_payouts)
balance -= sum(non-freeform bill amounts, using amount_override where set)
balance -= sum(freeform bill amounts from CC payment algorithm)  ← computed BEFORE this step
balance -= sum(notable_payouts)
Net Balance = balance
```

**Computation order:** Freeform CC payment amounts must be computed first (using `balance_before_cc = balance after all non-freeform bills`), then subtracted from the rolling balance. The CC algorithm's input (`net_balance_before_cc`) is the rolling balance at the point just before freeform bills are applied.

Net Balance is the same number displayed in three places: top bar, bottom of Core Spend ("Remaining"), and Luxury Allowance header ("$ left"). It represents the budget available for discretionary spending after all obligations.

**Buffer:** A user-configured floor (default $300). The buffer indicator compares Net Balance to the buffer amount. If Net Balance >= buffer: "OK (+$difference)". If Net Balance < buffer: "OVER BY $difference". The buffer is informational only — it does not block spending. It does interact with the CC payment rules: remainder allocation only happens from `Net Balance - buffer` (never dips into the buffer).

**AUTO:** A boolean on BillDefinition. When true: the ledger row shows "AUTO" text instead of a checkbox. The bill is still computed in the rolling balance. The user cannot check/uncheck it (no manual action needed). Set via a toggle in the bills config panel.

**Freeform:** A boolean on BillDefinition. When true: the bill's ledger amount is computed by the PaymentRule engine rather than using default_amount. The bills config panel shows a "freeform" tag. The amount is read-only in the ledger (computed, not manually entered).

**Ephemeral items:** Payouts (priority and notable) are always ephemeral — they exist only in their month and are never cloned. Aux payouts are always treated as ephemeral per user specification.

**Recurring misc items:** MiscItems with is_recurring=true clone to the next month when a new month is created. Non-recurring items do not clone.

**Misc-to-bill promotion:** When a user identifies a recurring misc charge that should become a tracked bill (via fuzzy search results or manual action), the app: (1) creates a new BillDefinition with the misc item's description and amount; `due_day` is inferred from `MiscItem.date` (day of month) if set, otherwise the user is prompted to enter it via inline edit on the new bill row, (2) sets `promoted_to_bill_id` on the original MiscItem (soft-link, item is retained for history), (3) marks the MiscItem as `is_enabled=false` in the current month. The new bill appears in subsequent months via the normal clone process. The original misc item remains visible (dimmed, disabled) for audit trail.

**LuxuryConfig.misc_other derivation:** `misc_other = Net Balance - eating_out_budget`. This is the remaining discretionary *budget ceiling* after the eating out allocation is reserved. The Misc Spend card shows actual spend against this ceiling. The Luxury Allowance card's "Spent" value is `eating_out_actual + sum(enabled MiscItems)`. The "$ left" value is `Net Balance - Spent`. The dashboard description "misc other (derived from misc spend total)" refers to the budget allocation line item within the Luxury card, not the actual spend total.

**Savings goals placeholder (V2):** The V1 data model does not include savings entities. When implementing V2, add `SavingsGoal` and `SavingsContribution` entities linked to MonthPlan. The V1 schema should reserve no tables for this — add them in V2's migration.

**"Aux payouts"** is a legacy term from the spreadsheet. In the app data model, these are represented as `Payout` entities with `position=priority` (before bills) or `position=notable` (after bills). The term "aux" should not appear in the app UI.

**Paycheck cloning:** When cloning PaycheckDefinitions to a new month, the day-of-month from the source paycheck's date is preserved. If the new month has fewer days (e.g., source is Jan 31, target is Feb), clamp to the last day of the target month. `Settings.paycheck_count` is a convenience default for the "+" new month action — the user can freely add or remove PaycheckDefinitions per month after creation.

**Amount override scope:** The `amount_override` field on BillDefinition is ephemeral to its month. It is set via click-to-edit on the charge amount in the ledger row. On month clone, `amount_override` resets to null (reverts to `default_amount`). This is defined in Key Definitions under "Freeform" but applies equally to non-freeform bills: any bill's ledger amount can be overridden for a single month.

### CC Payment Rules Algorithm (V1: 2-card case)

Inputs: `net_balance_before_cc` (balance after all non-freeform bills), `buffer`, list of PaymentRules sorted by `priority_rank` ASC.

Returns: a map of `rule_id → computed_amount` (pure function, no side effects).

The caller writes the computed amounts to each freeform BillDefinition's `amount_override` for the current month.

```
function compute_cc_payments(net_balance_before_cc, buffer, rules) → Map<rule_id, amount>:

  available = net_balance_before_cc
  result = {}

  # Step 1: Check if we can cover all minimum payments
  total_minimums = sum(rule.min_payment for rule in rules)
  if available < total_minimums:
      # Shortfall: pay minimums in priority order until funds run out
      for rule in rules (by priority_rank ASC):
          payment = min(rule.min_payment, available)
          result[rule.id] = payment
          available -= payment
      return result  # caller should show warning banner: "Insufficient funds for minimum payments"

  # Step 2: Assign base payments (min + fixed addon, capped)
  for rule in rules (by priority_rank ASC):
      base = rule.min_payment + rule.fixed_addon
      if rule.cap is not null:
          base = min(base, rule.cap)
      base = min(base, available)  # never exceed what's left
      result[rule.id] = base
      available -= base

  # Step 3: Allocate remainder (available minus buffer) to receives_remainder card
  remainder = max(0, available - buffer)
  for rule in rules where rule.receives_remainder:
      result[rule.id] += remainder
      available -= remainder
      break  # only one card receives remainder

  return result
```

**Concrete example (user's 2-card setup):**
- Chase: min=$53, fixed_addon=$200, cap=null, priority_rank=1, receives_remainder=true
- Discover: min=$75, fixed_addon=$0, cap=$575 (min+$500), priority_rank=2, receives_remainder=false
- net_balance_before_cc=$700.37, buffer=$300

Step 2: Chase base = min($53+$200, $700.37) = $253. available=$447.37. Discover base = min($75+$0, min($575, $447.37)) = $75. available=$372.37.
Step 3: remainder = max(0, $372.37 - $300) = $72.37. Chase gets +$72.37 = $325.37 total. available=$300.00.

Result: Chase=$325.37, Discover=$75.00. Buffer intact at $300.

### Timeline View

When toggled to Timeline, the Core Spend ledger:
- Removes all section dividers
- Displays every item (payouts, pay period items, bills, notable payouts) in a single flat list sorted by date ASC, then by sort_order within same date
- Each row shows: Date, Checkbox/AUTO, Description (with subtitle/comment), Charge, Running Balance
- Pay Period block visual encapsulation is removed; paycheck and its items appear as regular rows
- The Remaining row still appears at the bottom

### Misc Spend Fuzzy Search

- Search input: text field at the bottom of the Misc Spend card (always visible)
- Searches across: item description and comment fields, across ALL months
- Match algorithm: substring match with typo tolerance (Levenshtein distance ≤ 2)
- Result display: dropdown list below the search input showing matching items with their month label (e.g., "Spotify — Mar 2026, $11.99"). Selecting a result navigates to that month or offers to "promote" (add as recurring item in current month)

## Sync Strategy (V1 Provisional)

V1 sync: **versioned JSON snapshots over a user-owned shared folder.** Not raw SQLite file sync (corruption risk when file is open).

**Important constraint:** The "no third-party services that could start charging" rule means Dropbox API is not suitable (API terms can change, OAuth tokens can be revoked). Instead, sync uses plain filesystem operations on any user-chosen shared folder — this works with Dropbox desktop sync, Google Drive, Syncthing, or a network share. The app never calls a third-party API; it only reads/writes files to a local path that the user configures.

Mechanism:
- User configures a sync folder path in Settings (e.g., `C:\Users\decid\Dropbox\own-ledger\` on Windows, or the equivalent on Android via SAF/Storage Access Framework)
- On every write, export current month data as a JSON snapshot to `{sync_folder}/months/{year}-{month}.json` with a `last_modified` timestamp in the JSON
- On app open, scan the sync folder for snapshots. For each month, compare remote `last_modified` to local. If remote is newer, import it (last-write-wins per month, since months are independent entities)
- Conflict detection: if both local and remote have `last_modified` timestamps that are newer than the last sync timestamp, show a conflict dialog: display both timestamps and the key differences (Net Balance, number of items changed). User picks which version to keep. The losing version is archived to `{sync_folder}/conflicts/` with a timestamp suffix.
- No time-bound SLA in V1. Sync latency depends on the underlying folder sync service.

**Android note:** Android does not expose Dropbox/GDrive folders as filesystem paths. Use Storage Access Framework (SAF) to let the user grant the app access to a specific folder in their cloud storage provider. This works with any provider that implements Android's document provider interface (Dropbox, Google Drive, OneDrive all do). The app writes JSON files via SAF URIs, not filesystem paths. This is $0, uses no third-party SDK, and works with whatever sync service the user already has.

CRDT (cr-sqlite) is a V2 upgrade path if conflict-free merging becomes needed.

## Desktop Dashboard Layout (v6 wireframe)

Wireframe HTML files at: `C:\Users\decid\AppData\Local\Temp\gstack-sketch-own-ledger-v6.html`

### Top Bar
- **Net Balance** (largest element, green): The post-everything number. Same as Luxury Allowance "$ left."
- **$300 Buffer indicator:** "OK (+$47.32)" or "OVER BY $X"
- **Outstanding debt summary:** Credit total + Loan total, positioned beside Net Balance
- **Large centered month name** ("April") with "2026" subtext
- **Month grid:** 4-across clickable squares (Jan, Feb, Mar, Apr active) + "+" button to clone current month. Side buttons: ellipsis (menu), up/down arrows (scroll history). Nav buttons use amber/green coloring for visibility.

### Left Column: Core Spend
- **Grouped/Timeline toggle:** Grouped shows sections, Timeline shows flat chronological view
- **Section dividers:** Green horizontal lines with centered label text. Sections with ephemeral items have "+ Add" buttons at the far right (outside the centering).
- **Opening Balance:** Carry-forward from previous month
- **Priority Payouts:** Ephemeral items with checkboxes and x remove buttons. Added/removed per month.
- **Pay Period block:** Visually encapsulated in a green-bordered rounded card. Header: "PAY PERIOD #N — {date}" with total. Contains: Paycheck (income), Gas/Transit, Groceries, Other (catchall for misc pre-plan spend). Repeatable for multi-paycheck months (header shows #1, #2, etc. — omits # if only one).
- **Bills:** Sorted by due date. AUTO bills show "AUTO" text instead of checkbox. Non-AUTO bills have checkboxes. Bill subtitles show provider name (dimmed). Per-month amount overrides: click-to-edit the charge amount in the ledger row. The override is stored on the month-specific BillDefinition (amount_override field), not on the global bill definition. On month clone, amount_override resets to null (reverts to default).
- **Notable Payouts:** Ephemeral items with checkboxes and x remove buttons.
- **Remaining:** Final balance, matches Net Balance at top.

### Right Column
- **Bills config panel:** Priority dot (green/yellow/red), Bill name + subtitle, Due date, Amount (editable), Balance column (outstanding debt for loans/credit cards, dash for non-debt). "freeform" tag on accounts with variable payment rules. Separate screen on mobile.
- **Luxury Allowance:** Progress bar, "$ left" matches Net Balance. Sub-items: eating out budget, misc other (derived from misc spend total).
- **Misc Spend:** Toggleable items (enable/disable affects total without removing). "recurring" tag for items that clone to next month. Comments as inline dimmed italic text. x remove buttons. Add row with item name + amount. Fuzzy search across all months.

### Interaction Model
- Click any editable value to show inline editing (caret appears)
- x buttons to remove ephemeral items (payouts, misc items)
- Checkboxes for manual payment tracking (dark/muted styling when unchecked, green accent when checked)
- Settings screen (separate page) for: paycheck count/dates/amounts, CC payment rules, buffer amount

### Design Requirements
- Dark theme (navy background, green/amber accents)
- Consistent 34px row heights across all ledger rows
- 60px date column with no-wrap
- Monospace font for all monetary values
- Unchecked checkboxes must use dark/muted styling (not browser-default bright white)

## Open Questions

1. **Data migration:** How to import historical data from the .ods spreadsheet? Manual entry for first month, or build a one-time parser?
2. **Payment rules generalization:** V1 implements the 2-card case with the algorithm specified above. Should it generalize to N freeform accounts in V2? The PaymentRule entity already supports N rules, but the UI needs design work for managing many rules.

## Success Criteria

- Can enter a full month's data (income, bills, misc spend) in under 5 minutes
- Can check "am I okay?" on phone in under 3 seconds (app open → net balance visible)
- Data syncs between Windows and Android via Dropbox folder sync (latency depends on Dropbox, typically 5-30 seconds for small JSON files; no app-level SLA in V1)
- Zero recurring costs
- User stops opening the spreadsheet

## V1 MVP Scope

All 14 spreadsheet sections mapped:

| # | Section | Scope | Notes |
|---|---------|-------|-------|
| 1 | Income per paycheck | **V1** | PaycheckDefinition entity |
| 1.1 | Credit card balances | **V1** | outstanding_balance on BillDefinition |
| 1.2 | Loan balances | **V1** | outstanding_balance on BillDefinition |
| 2 | Bills definitions | **V1** | BillDefinition entity + bills config panel |
| 3 | Core spend ledger | **V1** | Main dashboard, rolling balance |
| 4 | Luxury spend | **V1** | Luxury Allowance card |
| 5 | Miscellaneous spend | **V1** | Misc Spend card with toggles |
| 6 | Savings goals & progress | **V2** | Data model placeholder in V1 |
| 7 | Amount to save | **V2** | Coupled with savings goals |
| 8 | Short term purchase queue | **V2** | |
| 9 | Long term goals list | **V2** | |
| 10-12 | Vacation planning | **V2** | |
| 13 | Pay period date ranges | **V1** | PaycheckDefinition.date covers this |
| 14 | Dues to other people | **V1** | Payout entity (priority/notable) |

Also deferred to V2:
- Bank connections and auto-import
- Trend analysis and spending insights
- "Safe to spend" forecast UI (rolling balance calculator built as pure function in V1 data layer)
- "Financial weather forecast" (danger zones, next date of concern) — explicitly out of scope for V1 and V2; revisit after V2 ships

## Next Steps

1. **Initialize the project:** `git init`, Flutter project scaffold, drift database setup
2. **Define the data model:** Months, bills, ledger entries, misc items, paycheck definitions, CC payment rules. Each month is a self-contained entity that clones from previous.
3. **Build the rolling balance calculator:** Pure function, test against real spreadsheet numbers.
4. **Build the desktop dashboard:** Core Spend view with all sections from the wireframe.
5. **Build the bills config panel:** Secondary screen on mobile, right column on desktop.
6. **Build the settings screen:** Paycheck config, CC rules, buffer amount.
7. **Implement sync:** Start with simplest viable approach, iterate.
8. **Android layout:** Stacked cards, bills on separate screen.

## What I noticed about how you think

- You said "I manage my money to get it out of the way and live life." That's not how most people who maintain 14-section spreadsheets talk. You've built a system that lets you be responsible without it being a hobby... and now you want the app to be even faster at getting out of the way.
- Your feedback on the wireframes was unusually precise. You didn't say "it feels off" — you pointed at specific rows, named the inconsistency, and proposed fixes. "Inconsistent wrapping behavior on dates" and "the X button by the priority payout is smaller than all the X buttons in the MISC SPEND section" are the kind of observations that separate people who care about craft from people who just want features.
- You pushed back on my AUTO checkbox interpretation with clarity: "I wasn't trying to say that AUTO bills should have checkboxes. I was pointing out that some non-AUTO bills were missing checkboxes." You corrected the misunderstanding without ambiguity. That's good communication.
- The credit card payment rules you described are a constraint solver. You've been doing optimization math in your head every month and didn't frame it as complex... just as "how I pay my bills." That's domain expertise hiding in plain sight.
