diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index a35c0cf..f818496 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,23 +6,26 @@ ### Features -- New theme preset options available to choose from in App Settings -- Added undo and redo support across planning workflows, plus an audit history overlay so you can review and restore prior changes. -- Added plan-wide search actions that can jump directly to settings, sections, and modals, including common add/edit/delete/pause tasks. -- Added automated reallocation support when remaining spending is below target, with safe-source rules and a clear summary of what changed. -- Added app-wide Lucide icon support with account icon selection, replacing legacy emoji-based iconography. +- No major features for this release ### Improvements -- Improved gross-to-net clarity with explicit pre-tax and post-tax deduction visibility in key breakdown views. -- Expanded tax modeling so tax lines can be configured using either percentage rates or fixed amounts. -- Expanded view mode flexibility with more cadence options, paycheck-cadence defaults, and better selector guidance. -- Overhauled Appearance and Accessibility settings with curated light/dark preset pairs, manual dark-mode overrides, and a dedicated high-contrast mode. -- Added app-level display scaling controls (zoom/font size) in Settings and View, including keyboard shortcuts for zoom in, zoom out, and reset. +- Added the tab icon next to each view title so the active workspace has clearer visual context. +- Simplified retirement setup by removing employer match handling from retirement elections to reduce complexity, as it's not an amount taken out of your paycheck generally +- Improved Key Metrics view accuracy + - Remaining-for-spending totals now match between Key Metrics and Pay Breakdown in yearly mode. + - The Bills metric was reworked as Recurring Expenses and now reflects the total of custom allocations, bills, deductions, and loan payments. +- Improved account deletion behavior so linked items are handled more intelligently across all supported item types. +- Reduced icon width to look cleaner, and replaced a few icons to make more sense in context +- Reduced built application size by around 100MB ### Bug Fixes -- Fixed cross-mode rounding and persistence behavior so values remain stable after editing, saving, and reopening. -- Fixed several search interaction issues, including navigation/scroll behavior and action responsiveness for pause/resume controls. \ No newline at end of file +- Fixed deduction amount rounding/display inconsistencies (for example 9.30 no longer drifting to 9.31). +- Fixed edit-form amount formatting so trailing zeros are preserved more consistently when editing existing bill and deduction values. +- Added tighter decimal precision handling in amount entry fields to match what the UI can reliably display. +- Fixed history overlay behavior for legacy entries so edits no longer appear as misleading empty-to-value changes. +- Fixed a history overlay deletion bug where deleting an Initial tracked state row could delete the wrong item in stacked history. +- Fixed tax settings history to actually break out the line item changes done \ No newline at end of file diff --git a/app_updates/v0.4.0-icon-migration-plan.md b/app_updates/v0.4.0-icon-migration-plan.md deleted file mode 100644 index e2e15d0..0000000 --- a/app_updates/v0.4.0-icon-migration-plan.md +++ /dev/null @@ -1,351 +0,0 @@ -# v0.4.0 Icon Migration Plan — Emoji → Lucide React - -## License - -**Lucide React** is published under the **ISC License** (a permissive open-source license functionally equivalent to MIT). -- No attribution required in the app UI -- No copyleft — can be used in commercial and closed-source products -- Copyright notice is satisfied by the license file present in `node_modules/lucide-react` (included in any Electron build) -- Zero legal risk for distribution on Mac App Store, Windows, or Linux - ---- - -## Approach - -The migration is split into three tiers by complexity: - -| Tier | Scope | Complexity | -|------|-------|------------| -| **1 — App chrome** | Buttons, headings, modal headers, option cards, empty states, tab icons, metric card icons | Straightforward swap | -| **2 — Search module icons** | `categoryIcon` fields in all `searchModules/*.ts` — currently typed as `string` | Requires a type change from `string` to a Lucide icon type; then per-module substitution | -| **3 — Account icon field** | User-entered emoji in the AccountsEditor icon text input; defaults in `accountDefaults.ts` | User data — recommend deferring or a separate purpose-built picker | - ---- - -## License Requirement Checklist - -- [x] ISC license verified from `npm info lucide-react` and GitHub source -- [x] No UI attribution required -- [x] Commercial distribution permitted -- [x] Electron packaging: license file will be present in `node_modules/` inside the app bundle — satisfies the "include notice in copies" clause automatically - ---- - -## Tier 1 — App Chrome (Button Labels, Headings, Empty States, Cards) - -### A. Icon-Only Buttons (`✕` and `⚙`) - -The `✕` character is used as the entire visible content of six distinct icon-only buttons. -The bare `⚙` (no variation selector) is used in the view mode settings button. - -| File | Current | Proposed Lucide Icon | Notes | -|------|---------|----------------------|-------| -| `components/_shared/layout/Modal/Modal.tsx` | `✕` (close modal) | `X` | `aria-label="Close modal"` already present | -| `components/views/WelcomeScreen/WelcomeScreen.tsx` | `✕` (dismiss error banner) | `X` | | -| `components/views/WelcomeScreen/WelcomeScreen.tsx` | `✕` (remove recent file) | `X` | `aria-label` present | -| `components/tabViews/PayBreakdown/PayBreakdown.tsx` | `✕` (remove allocation category) | `X` | | -| `components/tabViews/LoansManager/LoansManager.tsx` | `✕` (remove payment line) | `X` | | -| `components/PlanSearchOverlay/PlanSearchOverlay.tsx` | `✕` (clear search) | `X` | | -| `components/_shared/layout/ViewModeSelector/ViewModeSelector.tsx` | `⚙` (view mode settings) | `Settings` | aria-label present | - ---- - -### B. PlanDashboard Header Buttons - -| Current | Proposed Lucide Icon | Rationale | -|---------|----------------------|-----------| -| `🏦 Accounts` | `Building2` + "Accounts" | Bank/institution shape matches the tab | -| `📋 Copy Plan` | `Copy` + "Copy Plan" | Direct semantic match | -| `💾 Save` | `Save` + "Save" | Standard | -| `🔐 Encryption Key Setup` | `LockKeyhole` + text | Keyhole variant = key-based lock | -| `🔐 Disable Encryption` | `LockOpen` + text | Open lock for "disable" state | -| `🔐 Manage/Enable Encryption` | `LockKeyhole` + text | Consistent with Setup | - ---- - -### C. PlanDashboard Status Pills and Banners - -| Current | Proposed Lucide Icon | Notes | -|---------|----------------------|-------| -| `🔒 Encrypted` (status pill) | `Lock` (icon) + "Encrypted" | 16px, inline before text | -| `📄 Unencrypted` (status pill) | `LockOpen` (icon) + "Unencrypted" | | -| `📺 View-Only: …` (mode banner) | `Eye` (icon) + "View-Only: …" | `Tv2` is too playful; `Eye` is cleaner for read-only context | - ---- - -### D. Toast Messages - -Toast emojis are currently baked into message strings (`'✏️ Plan updated'`). The right long-term fix is: -- Strip emojis from all toast message strings -- Use the existing Toast `variant` or `type` prop to render a Lucide icon inside the Toast component itself - -| Current emoji in toast | Recommended Toast variant / icon | -|------------------------|----------------------------------| -| `✏️` (rename/update success) | success variant → `Check` or `Pencil` | -| `📋` (tab order updated) | success variant → `Check` | -| `⚠️` (warning/error) | warning/error variant → `AlertTriangle` | -| `🔒` (encryption enabled) | success variant → `Lock` | -| `📄` (encryption disabled) | info variant → `LockOpen` | - -**Note:** This requires a small refactor of the Toast component to render an icon based on variant rather than embedding it in the string — flagged as part of this milestone's implementation. - ---- - -### E. Pay Breakdown Page - -| Current | Proposed Lucide Icon | Notes | -|---------|----------------------|-------| -| `⚙️ Pay Settings` button | `Settings` + "Pay Settings" | | -| `ℹ️` (inline info indicator) | `Info` | `size={14}` to match inline weight | -| `🧾` (Paycheck Deductions section header) | `ReceiptText` | | - ---- - -### F. WelcomeScreen Header Buttons - -| Current | Proposed Lucide Icon | Notes | -|---------|----------------------|-------| -| `✨ Try Demo` | `Sparkles` + "Try Demo" | Lucide has `Sparkles` | -| `⚙️ Settings` | `Settings` + "Settings" | | -| `📂` (Open Existing Plan button icon) | `FolderOpen` | Icon-only span inside the button | - ---- - -### G. Settings Modal — Theme Toggle - -| Current | Proposed Lucide Icon | -|---------|----------------------| -| `☀️ Light` | `Sun` + "Light" | -| `🌙 Dark` | `Moon` + "Dark" | -| `💻 System` | `Monitor` + "System" | -| `✓ Backed up` / `Back Up First` | `Check` (conditionally shown) + text | - ---- - -### H. Encryption Config Panel (Option Cards) - -| Use | Current | Proposed Lucide Icon | Notes | -|-----|---------|----------------------|-------| -| Enable Encryption card | `🔒` | `ShieldCheck` | Shield with check = security enabled | -| Change Key card | `🔑` | `KeyRound` | Rounded key icon | -| Disable Encryption card | `📄` | `ShieldOff` | Shield-off = security disabled | -| Setup — Enable | `🔒` | `ShieldCheck` | | -| Setup — No Encryption | `📄` | `ShieldOff` | | -| `✓ Copied!` feedback text | `✓` | `Check` (icon, inline) | Small `size={12}` | - ---- - -### I. Encryption Setup Page and Setup Wizard - -| File | Current | Proposed | -|------|---------|----------| -| `EncryptionSetup.tsx` page heading | `🔐 Encryption Key Setup` / `🔐 Security Setup` | `ShieldCheck` icon before heading text (icon outside `

`) | -| `SetupWizard.tsx` section heading | `🔐 Security Setup` | Same — `ShieldCheck` icon beside heading | -| `SetupWizard.tsx` Complete Setup button | `Complete Setup ✓` | `Complete Setup` + trailing `Check` icon | - ---- - -### J. BillsManager Empty States and Section Header - -| Current | Proposed Lucide Icon | Notes | -|---------|----------------------|-------| -| `🏦` empty-state (no accounts) | `Building2` | 48px, styled as empty-state illustration | -| `📋` empty-state (no bills) | `ClipboardList` | 48px | -| `🧾` Paycheck Deductions section header | `ReceiptText` | 16–18px inline | - ---- - -### K. LoansManager Empty States - -| Current | Proposed Lucide Icon | -|---------|----------------------| -| `🏦` empty-state | `Building2` | -| `💸` empty-state | `Banknote` | - ---- - -### L. SavingsManager Empty States - -| Current | Proposed Lucide Icon | Notes | -|---------|----------------------|-------| -| `💰` empty-state | `PiggyBank` | For Savings contributions section | -| `🏦` empty-state | `ChartNoAxesCombined` | For Retirement section | - ---- - -### M. PlanSearchOverlay — Search Input Icon - -| Current | Proposed Lucide Icon | Notes | -|---------|----------------------|-------| -| `🔍` (decorative input prefix) | `Search` | `aria-hidden="true"` stays | - ---- - -### N. PlanTabs — Manage Tabs Button Icon - -| Current | Proposed Lucide Icon | -|---------|----------------------| -| `✏️` tab icon inside "Manage Tabs" button | `LayoutList` or `SlidersHorizontal` | `LayoutList` = manage/reorder list of tabs | - ---- - -### O. TabManagementModal Header - -| Current | Proposed Lucide Icon | -|---------|----------------------| -| `📋 Manage Tabs` (modal header prop) | Icon moved into modal header slot — `LayoutList`, text stays "Manage Tabs" | - ---- - -### P. Alert Component - -| Current | Proposed | -|---------|----------| -| `✓` (success status badge) | `Check` icon | `AlertTriangle` for warning, `Info` for info, `X` for error — consistent set | - ---- - -### Q. About Modal — Feature Icons - -| Feature | Current | Proposed Lucide Icon | -|---------|---------|----------------------| -| Smart Budgeting | `💰` | `PiggyBank` | -| Analytics | `📊` | `BarChart2` | -| Security | `🔒` | `ShieldCheck` | -| Multi-currency | `🌍` | `Globe` | -| Accounts | `🏦` | `Building2` | -| Pay periods | `📅` | `CalendarClock` | - ---- - -## Tier 1B — Navigation Tab Icons (`utils/tabManagement.ts`) - -Tab icons are currently emoji strings in `TabConfig`. These render in the tab strip. -Lucide icons are React components, not strings — so `TabConfig.icon` will need to change from `string` to `React.ReactNode` or `LucideIcon`. This is a small type change with a predictable cascade. - -| Tab | Current | Proposed Lucide Icon | -|-----|---------|----------------------| -| Key Metrics | `📊` | `ChartPie` | -| Pay Breakdown | `💵` | `Banknote` | -| Bills | `📋` | `ClipboardList` | -| Savings | `💰` | `PiggyBank` | -| Loans | `🏦` | `Landmark` | -| Taxes | `🏛️` | `Scale` | - -> `Landmark` for Loans (bank/lender) vs `Scale` for Taxes keeps the two visually distinct even though both had `🏛️`. - ---- - -## Tier 1C — Key Metrics Cards (`KeyMetrics.tsx`) - -`MetricCard` takes an `icon` string prop. Same type-change needed as tabs — change to `React.ReactNode`. - -| Metric | Current | Proposed Lucide Icon | -|--------|---------|----------------------| -| Total Income | `💰` | `BanknoteArrowDown` | -| Total Taxes | `🏛️` | `Scale` | -| Total Take Home Pay | `✅` | `HandCoins` | -| Total Bills | `📋` | `ClipboardList` | -| Savings Rate | `🏦` | `PiggyBank` | -| Remaining for Spending | `💵` | `Wallet` | - ---- - -## Tier 2 — Search Module `categoryIcon` Fields - -**Architecture consideration:** All search modules export a `categoryIcon: string` field that renders the emoji inline in search results. Lucide icons are React components — to replace these, `categoryIcon` needs to change from `string` to `LucideIcon` (Lucide's exported component type), and the search result renderer needs to render it as ``. - -This is a well-contained change but touches 9 files + the search result renderer. - -### Proposed mappings by module - -| Module | Current | Proposed Lucide Icon | -|--------|---------|----------------------| -| **accountsSearchModule** | `🏛️` | `Building2` | -| **billsSearchModule** — bills | `🧾` | `ReceiptText` | -| **billsSearchModule** — health | `🏥` | `HeartPulse` | -| **keyMetricsSearchModule** — trending up | `📈` | `TrendingUp` | -| **keyMetricsSearchModule** — taxes | `🏛️` | `Scale` | -| **keyMetricsSearchModule** — bills | `📋` | `ClipboardList` | -| **keyMetricsSearchModule** — savings rate | `🏦` | `PiggyBank` | -| **keyMetricsSearchModule** — take home | `✅` | `HandCoins` | -| **keyMetricsSearchModule** — remaining | `💵` | `Wallet` | -| **keyMetricsSearchModule** — analytics | `📊` | `ChartPie` | -| **loansSearchModule** | `💳` | `CreditCard` | -| **payBreakdownSearchModule** — pay | `💸` | `Banknote` | -| **payBreakdownSearchModule** — taxes | `🏛️` | `Scale` | -| **payBreakdownSearchModule** — calculator | `🧮` | `Calculator` | -| **payBreakdownSearchModule** — trending down | `📉` | `TrendingDown` | -| **payBreakdownSearchModule** — pinned | `📌` | `Pin` | -| **payBreakdownSearchModule** — take home | `✅` | `HandCoins` | -| **payBreakdownSearchModule** — remaining | `💵` | `Wallet` | -| **payBreakdownSearchModule** — receipts | `🧾` | `ReceiptText` | -| **paySettingsSearchModule** — salary | `💰` | `BanknoteArrowUp` | -| **paySettingsSearchModule** — schedule | `📅` | `CalendarClock` | -| **paySettingsSearchModule** — hours | `⏱️` | `Clock` | -| **preTaxDeductionsSearchModule** | `📉` | `TrendingDown` | -| **quickActionsSearchModule** — all add actions | `➕` (×5) | `Plus` | -| **quickActionsSearchModule** — settings | `⚙️` | `Settings` | -| **savingsSearchModule** — savings | `🏦` | `PiggyBank` | -| **savingsSearchModule** — vacation/goal | `🏖️` | `Umbrella` | -| **settingsSearchModule** — palette | `🎨` | `Palette` | -| **settingsSearchModule** — paintbrush | `🖌️` | `Paintbrush` | -| **settingsSearchModule** — light theme | `☀️` | `Sun` | -| **settingsSearchModule** — dark theme | `🌙` | `Moon` | -| **settingsSearchModule** — system theme | `💻` | `Monitor` | -| **settingsSearchModule** — font | `🔤` | `Type` | -| **settingsSearchModule** — grid/density | `🔲` | `LayoutGrid` | -| **settingsSearchModule** — accessibility | `♿` | `PersonStanding` | -| **settingsSearchModule** — glossary | `📖` | `LibraryBig` | -| **settingsSearchModule** — feedback | `💬` | `MessageSquareReply` | -| **settingsSearchModule** — analytics | `📊` | `ChartPie` | -| **settingsSearchModule** — date/view (×3) | `📅` | `CalendarClock` | -| **settingsSearchModule** — save/backup | `💾` | `Save` | -| **settingsSearchModule** — reset | `🔄` | `RefreshCw` | -| **taxesSearchModule** (×3) | `🏛️` | `Scale` | - ---- - -## Tier 3 — Account Icon Field (Defer / Separate Task) - -The account icon in `AccountsEditor` is a **user-editable text input** where the user types an emoji character themselves. The defaults in `accountDefaults.ts` (`💳`, `💰`, `📈`, `💵`) are just pre-filled starting values — they're user data, not UI chrome. - -**Recommendation:** Do not migrate this in the icon library pass. Replacing it with a Lucide icon picker would require: -- Changing the `Account.icon` data type from `string` (user emoji) to something structured -- Building or integrating an icon picker component -- A migration path for existing plan files that have emoji strings stored - -This is a separate UX task (e.g., v0.4.1 "Account Icon Picker"). For now, the `💰` placeholder text in the input can remain as a hint, and `accountDefaults.ts` defaults can stay as-is. - ---- - -## Test File Impact - -Four test files assert against emoji strings that will change as part of this migration. These need updates alongside the implementation — not separately. - -| Test file | Affected assertions | -|-----------|---------------------| -| `utils/accountDefaults.test.ts` | `.toBe('💳')`, `.toBe('💰')`, `.toBe('📈')`, `.toBe('💵')` — **keep as-is** if account tier is deferred | -| `utils/tabManagement.test.ts` | Tab `icon` field assertions — update to Lucide component references | -| `utils/planSearch.test.ts` | Account fixture `icon: '🏦'` — keep as-is (user data field) | -| `utils/searchRegistry.test.ts` | `categoryIcon` fixture values `'📦'`, `'🎯'` — update when Tier 2 lands | -| `test/accessibility.test.tsx` | Tests that render `✕` buttons — update to expect rendered `X` icon output | - ---- - -## Suggested Implementation Order - -1. **Install** `lucide-react` via npm -2. **Tier 1 — App chrome** (all buttons, headings, empty states, option cards, about modal): no type changes needed, pure JSX swaps -3. **Alert component** icon variants (replaces `✓` with `Check`, adds `AlertTriangle`, `Info`, `X`) -4. **Toast message strings** — strip emojis, move icon rendering into the Toast component variant system -5. **Tab icons** — change `TabConfig.icon: string` → `React.ReactNode`, update tab strip renderer, update `tabManagement.ts` -6. **MetricCard icon prop** — same type change, update `KeyMetrics.tsx` -7. **Tier 2 — Search modules** — update `categoryIcon` type in search registry, update search result renderer, update all 9 modules + their tests -8. **Tier 3 — Account icon field** — defer to v0.4.1 - ---- - -## Nothing to Defer (Risk: Zero) - -The ISC license requires zero action. No NOTICE file, no UI attribution badge, no license screen addition. The license file that ships inside the Electron app bundle inside `node_modules/lucide-react/LICENSE` fully satisfies the requirement. diff --git a/app_updates/v0.4.0-list.md b/app_updates/v0.4.0-list.md deleted file mode 100644 index 6ed79d3..0000000 --- a/app_updates/v0.4.0-list.md +++ /dev/null @@ -1,144 +0,0 @@ -# v0.4.0 Work Plan - -Status keys: -- `[ ]` Planned -- `[-]` In Progress -- `[x]` Done - -Release notes discipline: -- [ ] Add a user-facing RELEASE_NOTES bullet when each parent item reaches Done. -- [ ] Keep release-note wording focused on outcomes, not implementation details. - -## 1. Gross-to-Net Breakdown Clarity -Parent status: `[x] Done` - -- [x] Add explicit Pre-Tax Deductions line items in Gross-to-Net views. -- [x] Add explicit Post-Tax Deductions line items in Gross-to-Net views. -- [x] Validate labels/tooltips so users can clearly tell what is included. -- [x] Add/update tests for displayed values and category separation. - -Done definition: -- [x] A user can identify pre-tax vs post-tax deductions at a glance, with values matching calculations. - -## 2. Automated Reallocation to Target Remaining -Parent status: `[x] Done` - -- [x] Add an action when Remaining is below target (for "All that remains for spending"). -- [x] Define and enforce "safe" reallocation sources: - - [x] Savings contributions - - [x] Investment contributions - - [x] Retirement elections - - [x] Bills/Deductions marked as discretionary -- [x] Allow user to review and revise automated re-allocations as they see fit. -- [x] Add discretionary tagging on Bills/Deductions create/edit flows. -- [x] For items that are able to be enabled/disabled, prefer that over removing it completely -- [x] Implement allocation priority/order rules and caps. -- [x] Show a post-action summary of every changed amount. -- [x] Add the "discretionary" definition to the glossary so users know what it means. -- [x] Add tests for success path, no-op path, and guardrails. - -Done definition: -- [x] Reallocation reaches or improves target safely and always provides a clear change summary. - -## 3. Taxation Updates and Accuracy -Parent status: `[x] Done` - -- [x] Allow fixed-amount tax entry in addition to percentage tax lines. - - [x] Each should update based on how the other was updated -- [x] Support separate treatment for distinct tax lines (example: SUI vs Federal). -- [x] Add tests covering mixed percentage + fixed tax configurations. - -Done definition: -- [x] Users can model taxes to match pay-stub reality with clear line-item behavior. - -## 4. Theme and Accessibility Expansion -Parent status: `[x] Done` - -- See `v0.4.0-theme-accessibility-plan.md` for reference of complete implementation -- Progress tracking note: update `v0.4.0-theme-accessibility-plan.md` after each delivered increment. -- Active scope: `Phase 1 foundation only` until explicitly advanced. -- [x] Design and build a theme engine that will enable fluid and simple color scheme changes -- [x] Add more curated light/dark theme pair presets. - -- [x] Allow manual dark-mode overrides after auto-generation if user prefers. -- [x] Add high-contrast mode -- [x] Add font-size/zoom controls in app settings and View menu. -- [x] Add keyboard shortcuts for zoom: Cmd/Ctrl `+`, Cmd/Ctrl `-`, Cmd/Ctrl `0`. - - [x] Make sure to add to the keyboard shortcuts modal - -Done definition: -- [x] Users can apply preset/custom themes and accessibility scaling with stable contrast/readability. - -## 5. Cross-Mode Rounding and Persistence Stability -Parent status: `[x] Done` - -- [x] Confirm and document conversion source-of-truth strategy. -- [x] Decide whether yearly values are canonical storage (or another canonical model). - - Canonical storage remains domain-specific rather than forcing everything into yearly values. - - Manual account allocation categories use normalized per-paycheck storage because they are compared directly against paycheck net pay and reallocation math. - - Bills, loans, savings, and other frequency-based items keep their existing native/monthly storage models. -- [x] Ensure edits in paycheck/monthly/yearly remain stable after save/reopen. -- [x] Add targeted tests for mode conversion + save/blur regression cases. - -Additions after completion: -- [x] There's still rounding issues due to calculations. I think we need to find a stable source of truth, and use that to do calculations off of. I propose using the user's estimated annual pay since that is the largest unit and easiest to then split out smaller. They will be putting this number in when going through setup, and if they choose hourly as their wage it's easy to calculate that out to yearly, then divide down as well. - -Done definition: -- [x] Re-entering the app does not introduce drift or unexpected value jumps across modes. - -## 6. View Mode Selector Enhancements -Parent status: `[x] Done` - -- [x] Expand selector options from smallest to largest unit: - - [x] Weekly - - [x] Bi-weekly - - [x] Semi-monthly - - [x] Monthly - - [x] Quarterly - - [x] Yearly -- [x] Default selector to the user paycheck cadence view mode. -- [x] Add subtle helper text indicating which option matches paycheck cadence. -- [x] Keep paycheck-cadence option visually identifiable even when not selected. -- [x] Allow user to edit what options they want to always see in the view mode selector, so that it's not overly long (unless they want it to be). -- [x] Add tests for initial/default mode and cadence indicator behavior. - -Done definition: -- [x] Users can easily switch units and always find the paycheck-cadence mode quickly. - -## 7. Undo/Redo Functionality -- Detailed implementation plan: `app_updates/v0.4.0-undo-redo-audit-plan.md` -- [x] Analysis first on complexity of implementing an undo/redo feature -- [x] When users make a change, allow them to undo it to put the Plan back to the prior state. -- [x] Also allow a redo, if they change their mind and still want that. -- [x] Add the options to Edit in menubar, and also as the usual keyboard shortcuts for undo and redo, depending on the OS -- [x] Add the details of the shortcuts to the keyboard shortcuts modal -- [x] Add an audit log for several key editable areas of the app so users can roll back if needed, and see what changes were made throughout the year - -## 8. Plan-wide Search Functionality -- Most of this is completed, but some pending items: -- [x] If a pay setting is queried, it shows in the search but clicking it does not open the pay settings modal and navigate to that setting yet -- [x] Doesn't seem to be any search available for anything on the Key Metrics or Pay Breakdown views at all yet -- [x] The search for specific components is working, but the scroll to view is still not working quite properly -- [x] Enable buttons throughout the app to be searchable and jumped to, with their Modals opened as well. For example, search `Add bill` and it appear in search to jump to and start adding a new bill immediately. -- [x] Allow user to take actions on certain things from the Search, such as - - [x] For SectionItemCard items, easily pause, edit, or delete -- [x] Possible to rework Search to be more extensible going forward? So it's very simple to add new components, settings, modals, etc. to the Search actions, without too much additional logic added every time. -- [x] Few more fixes needed for search: - - When clicking Pause/Resume there's a brief delay where button is stuck at old width and wording is wrong - -## 9. Add and Implement icon library -Parent status: `[x] Done` -- [x] Check and see what icon library would be best for the project - - https://react-icons.github.io/react-icons/ - - https://iconoir.com -- [x] Implement icons across the app in spots that make sense, replacing emoji counterparts - -Done definition: -- [x] No more emojis being used in the app - - -## Final v0.4.0 Exit Checklist -- [x] All parent items above are marked `[x] Done` or explicitly deferred. -- [x] RELEASE_NOTES updated with completed v0.4.0 user-facing items. -- [x] Lint, typecheck, tests, and build pass for merged changes. \ No newline at end of file diff --git a/app_updates/v0.4.0-theme-accessibility-plan.md b/app_updates/v0.4.0-theme-accessibility-plan.md deleted file mode 100644 index 80ffd88..0000000 --- a/app_updates/v0.4.0-theme-accessibility-plan.md +++ /dev/null @@ -1,308 +0,0 @@ -# v0.4.0 Theme and Accessibility Plan - -Status keys: -- `[ ]` Planned -- `[-]` In Progress -- `[x]` Done - -Release notes discipline: -- [ ] Add a user-facing RELEASE_NOTES.md bullet when each parent item reaches Done. -- [ ] Keep release-note wording focused on outcomes, not implementation details. - -Current execution scope: -- `Phase 4 active`. - -Phase order (rearranged for implementation): -- `Phase 1 (Foundation)`: Sections 1, 4, and foundation parts of 6 + 7. -- `Phase 2 (Presets)`: Section 2 + corresponding validation in 7. -- `Phase 3 (Custom Themes)`: Section 3 + corresponding validation in 7. -- `Phase 4 (Zoom and Final Hardening)`: Sections 5 + 8 + final rollout checks in 7. - ---- - -## 1. Appearance Architecture Foundation (Phase 1) -Parent status: `[x] Done` - -- [x] Extend app-level settings to support appearance and accessibility preferences. - - Keep these preferences in app settings, not plan files. - - Include a clear model for theme mode, preset selection, custom appearance seed values, high contrast, and font scaling. -- [x] Refactor theme application so the app resolves appearance from a single source of truth. - - Avoid scattered `localStorage` theme reads outside the shared settings/theme flow. -- [x] Define the semantic token contract for themeable UI. - - Background, surface, text, border, accent, status, focus, overlay, and elevation tokens should be centrally owned. - - All semantic tokens defined in `src/index.css` with light/dark/high-contrast overrides; consumed throughout UI via `var(--token-name)`. -- [x] Audit existing raw or one-off styling patterns that would block stable theming. - - Completed 4 passes of comprehensive tokenization: - - Pass 1: Button, PillToggle, Toast, AccountsEditor focus ring, TabManagementModal — replaced raw rgba shadows/backgrounds with semantic tokens. - - Pass 2: Added metric color tokens (posttax, bills, shortfall) with light/dark variants; replaced KeyMetrics hardcoded hex values and fileStorage encryption dialog inline colors with token resolution. - - Pass 3: Tab/manager surface cleanup — TabPositionHandle, AccountsDeleteModal, LoansManager, PayBreakdown — unified to semantic tokens. - - Pass 4: Account palette centralization — consolidated duplicate inline color/icon maps into `src/constants/accountPalette.ts`; refactored all callsites (accountDefaults.ts, demoDataGenerator.ts, fileStorage.ts) to import and use shared helpers. - - Final sweep confirmed: 0 raw rgba/hex in active component files; all colors properly tokenized in index.css. -- [x] Add/update tests for settings persistence and theme resolution behavior. - - Updated accountDefaults.test.ts to assert against ACCOUNT_TYPE_COLORS constant; all 169 tests passing. - -Done definition: -- [x] Theme and accessibility preferences are persisted safely, applied consistently, and resolved from one predictable settings model. - ---- - -## 2. Curated Theme Presets (Phase 2) -Parent status: `[x] Done` - -- [x] Add curated preset theme families with paired light and dark variants. - - Start with a small, opinionated set that feels polished rather than exhaustive. -- [x] Separate theme mode from theme family. - - Users choose Light, Dark, or System independently from the selected preset family. -- [x] Ensure all major surfaces and shared controls render correctly across presets. - - Include buttons, tabs, cards, modals, alerts, form fields, badges, and focus states. - - Verify primary/secondary/destructive button contrast in both themes (including Edit/Delete on dark surfaces). - - Verify tab label contrast on dark backgrounds (including selected/active state colors). - - Completed representative preset QA sweep across modal, alert, tab-like selector, and destructive button surfaces in both light and dark modes for all curated presets via `presetSurfaceQa.test.tsx`. - - Tightened readable accent text usage by switching shared text-bearing surfaces (PlanTabs active label, TabManagementModal badges/actions, ViewModeSelector active state and cadence pill, InfoBox headings) from `--accent-primary` to `--text-accent`. - - Fixed default light theme readable accent token by making `--text-accent` and `--link-color` use a darker accessible indigo (`#5568d3`) instead of the decorative accent fill value (`#667eea`). -- [x] Add a lightweight preview pattern in Settings so users can understand the preset before leaving the modal. -- [x] Add/update tests where necessary. - - Added persisted `appearancePreset` setting with normalization/import-export support and document-level `data-theme-preset` application in `ThemeContext`. - - Introduced curated preset families: `Default`, `Spreadsheet Core`, `Ocean`, `Forest`, `Sunset`, and `Pretty in Pink`, each with paired light/dark accent, link, and header treatments. - - Added Settings preset preview cards so users can compare families before leaving the modal. - - Added `SettingsModal.test.tsx` to verify preset selection persistence and restored preview state across reopen. - - Added `presetSurfaceQa.test.tsx` to smoke-test representative preset surfaces across light/dark families. - - Extended appearance normalization tests, app-settings persistence tests, and contrast tests to cover preset families and their accessibility-sensitive accent/text combinations. - -Done definition: -- [x] Users can switch among a curated set of polished preset themes and get consistent results in both light and dark modes. - ---- - - - - - ---- - -## 4. Accessibility Controls (Phase 1) -Parent status: `[x] Done` - -- [x] Add a high-contrast mode. - - Treat this as a real token layer, not just a minor dark-mode tweak. - - Include stronger separators/dividers and clearer differentiation for adjacent surfaces and line items. -- [x] Add font-size or UI scale controls in Settings. - - Apply scaling through shared/root sizing variables rather than ad hoc per-component overrides. - - As is best, want to make sure any measurement that makes sense to be is in rem units, so that things scale naturally based on the user's preferred font size -- [x] Audit shared components for scaling behavior. - - Buttons, inputs, pills, headers, cards, modal content, and table-like layouts should remain usable. -- [x] Identify and clean up hard-coded font sizes or spacing that break accessibility scaling. -- [x] Add non-color affordances for interactive help/info patterns. - - Do not rely on dotted underline alone for discoverability, especially on smaller/mobile layouts. - - Add a consistent info icon trigger (`i` or `?`) where glossary/help content is available. -- [x] Ensure non-text contrast and component-state contrast meet WCAG targets. - - Validate low-contrast combinations called out in UX review (light gray on white, dark gray on black, purple text on black). - - Validate contrast for borders, row separators, control outlines, and disabled/inactive states. -- [x] Consider reduced-motion support if the app's motion patterns justify it during implementation. - -Done definition: -- [x] Users can increase readability and contrast without the app becoming visually broken or inconsistent. - -- Added small `ⓘ` (circled-i) superscript icon inside each `GlossaryTerm` button as a persistent non-color affordance. The icon is accent-colored, subtly dimmed at rest, and fully visible on hover/focus — making glossary terms discoverable without relying on the dotted underline alone. -- Strengthened `GlossaryTerm` focus-visible outline from a semi-transparent `color-mix(...50%...)` value to a solid `var(--accent-primary)` 2px outline, which meets WCAG 2.4.11 Focus Appearance. -- Fixed `--text-secondary` in light theme: `#6b7280` (Tailwind gray-500, 4.36:1 on white — fails WCAG AA for normal text) → `#4b5563` (Tailwind gray-600, ~6.7:1 ✓ passes AA). -- Added `--text-accent` semantic token to separate accent-for-text from accent-for-backgrounds: - - Light mode: `var(--accent-primary)` (#667eea, unchanged) - - Dark mode: `#c084fc` (accent-secondary, 6.06:1 on dark bg ✓ vs. the old #a855f7 which was 4.06:1 — borderline fail for normal-sized text). -- Residual contrast gap noted (not fixed in this release): light-theme `--border-color` (#e5e7eb) on white is ~1.2:1 — WCAG 1.4.11 non-text contrast for form control outlines requires 3:1. Full compliance would require a border of approximately Tailwind gray-500 (#6b7280), which is too dark for the current design aesthetic. High-contrast mode addresses this with `--border-color: #9ca3af`. `App.css` `body { font-size: 1rem }` so inherited text scales with the root (ThemeContext-controlled) font-size rather than always being locked to a hardcoded px value. This makes all text-level components (ViewModeSelector, labels, card content, etc.) scale correctly with Zoom/font-scale settings. -- Converted `Toggle` switch and knob dimensions to `rem` units so the switch scales proportionally at large font sizes. -- Converted `AccountsEditor` icon button dimensions to `rem`. -- Converted `DateInput` calendar trigger button width to `rem`. -- Fixed `ExportModal` checkbox label and error banner `font-size` from `14px` → `0.875rem`. -- Added global `@media (prefers-reduced-motion: reduce)` rule in `index.css` to suppress animations and transitions for users who opt in at the OS level. -- Implemented persisted accessibility settings in app settings model (`highContrastMode`, `fontScale`). -- Implemented global appearance application via `ThemeContext` using `data-theme`, `data-contrast`, and root font-size. -- Implemented Settings controls for High Contrast and UI Font Scale. -- Added initial high-contrast token overrides for both light and dark themes. -- Added shared appearance normalization utilities and wired them into settings read/save/import paths. -- Added targeted tests for appearance normalization utility and app settings persistence/import normalization. -- Converted left/right sidebar tab widths to scale-friendly sizing (`rem` + fit-content constraints) so labels remain readable at larger font scales. -- Added a shared styled Select control with a custom caret indicator and adopted it in key accessibility-sensitive flows (Settings, Setup Wizard currency, Feedback category, and Plan year edit modal). -- Renamed the shared Select control to `Dropdown` and migrated all direct JSX ` { + const factor = 10 ** decimals; + return Math.round((value + Number.EPSILON) * factor) / factor; +}; + +const sanitizeDecimalInput = (rawValue: string, maxDecimals: number): string | null => { + const value = rawValue.replace(/,/g, ''); + + if (value === '') return ''; + if (!/^\d*\.?\d*$/.test(value)) return null; + + const [wholePart, decimalPart] = value.split('.'); + if (decimalPart == null) return wholePart; + + return `${wholePart}.${decimalPart.slice(0, maxDecimals)}`; +}; + +const formatAmountForInput = (amount: number, minDecimals: number, maxDecimals: number): string => { + if (!Number.isFinite(amount)) return ''; + + const rounded = roundToScale(amount, maxDecimals); + let formatted = rounded.toFixed(maxDecimals).replace(/0+$/, '').replace(/\.$/, ''); + + if (!formatted.includes('.') && minDecimals > 0) { + formatted = `${formatted}.${'0'.repeat(minDecimals)}`; + } else if (formatted.includes('.')) { + const fractionLength = formatted.split('.')[1].length; + if (fractionLength < minDecimals) { + formatted = `${formatted}${'0'.repeat(minDecimals - fractionLength)}`; + } + } + + return formatted; +}; + const BillsManager: React.FC = ({ scrollToAccountId, searchActionRequestKey, @@ -126,7 +163,7 @@ const BillsManager: React.FC = ({ const bill = budgetData.bills.find((item) => item.id === searchActionTargetId); if (bill) { setBillName(bill.name); - setBillAmount(bill.amount.toString()); + setBillAmount(formatAmountForInput(bill.amount, 2, MAX_AMOUNT_DECIMALS)); setBillFrequency(bill.frequency); setBillAccountId(bill.accountId); setBillNotes(bill.notes || ''); @@ -163,7 +200,7 @@ const BillsManager: React.FC = ({ const benefit = budgetData.benefits.find((item) => item.id === searchActionTargetId); if (benefit) { setBenefitName(benefit.name); - setBenefitAmount(benefit.amount.toString()); + setBenefitAmount(formatAmountForInput(benefit.amount, benefit.isPercentage ? 0 : 2, MAX_AMOUNT_DECIMALS)); setBenefitIsPercentage(benefit.isPercentage || false); setBenefitSource(benefit.deductionSource || 'paycheck'); setBenefitSourceAccountId(benefit.sourceAccountId || ''); @@ -243,7 +280,7 @@ const BillsManager: React.FC = ({ if (benefit.isPercentage) { return roundUpToCent((grossPayPerPaycheck * benefit.amount) / 100); } - return roundUpToCent(benefit.amount); + return roundToCent(benefit.amount); }; const getBenefitMonthly = (benefit: Benefit): number => { @@ -313,7 +350,7 @@ const BillsManager: React.FC = ({ const handleEditBill = (bill: Bill) => { setBillName(bill.name); - setBillAmount(bill.amount.toString()); + setBillAmount(formatAmountForInput(bill.amount, 2, MAX_AMOUNT_DECIMALS)); setBillFrequency(bill.frequency); setBillAccountId(bill.accountId); setBillNotes(bill.notes || ''); @@ -325,12 +362,13 @@ const BillsManager: React.FC = ({ const handleSaveBill = () => { const trimmedBillName = billName.trim(); const parsedBillAmount = parseFloat(billAmount); + const normalizedBillAmount = roundToScale(parsedBillAmount, MAX_AMOUNT_DECIMALS); const errors: BillFieldErrors = {}; if (!trimmedBillName) { errors.name = 'Bill name is required.'; } - if (!Number.isFinite(parsedBillAmount) || parsedBillAmount <= 0) { + if (!Number.isFinite(parsedBillAmount) || normalizedBillAmount <= 0) { errors.amount = 'Please enter a valid amount greater than zero.'; } if (!billAccountId) { @@ -344,7 +382,7 @@ const BillsManager: React.FC = ({ const billData = { name: trimmedBillName, - amount: parsedBillAmount, + amount: normalizedBillAmount, frequency: billFrequency, accountId: billAccountId, enabled: editingBill ? editingBill.enabled !== false : true, @@ -389,7 +427,7 @@ const BillsManager: React.FC = ({ const handleEditBenefit = (benefit: Benefit) => { setBenefitName(benefit.name); - setBenefitAmount(benefit.amount.toString()); + setBenefitAmount(formatAmountForInput(benefit.amount, benefit.isPercentage ? 0 : 2, MAX_AMOUNT_DECIMALS)); setBenefitIsPercentage(benefit.isPercentage || false); setBenefitSource(benefit.deductionSource || 'paycheck'); setBenefitSourceAccountId(benefit.sourceAccountId || ''); @@ -402,13 +440,14 @@ const BillsManager: React.FC = ({ const handleSaveBenefit = () => { const name = benefitName.trim(); const parsedAmount = parseFloat(benefitAmount); + const normalizedAmount = roundToScale(parsedAmount, MAX_AMOUNT_DECIMALS); const isAccountSource = benefitSource === 'account'; const errors: BenefitFieldErrors = {}; if (!name) { errors.name = 'Deduction name is required.'; } - if (!Number.isFinite(parsedAmount) || parsedAmount < 0) { + if (!Number.isFinite(parsedAmount) || normalizedAmount < 0) { errors.amount = 'Please enter a valid deduction amount.'; } if (isAccountSource && !benefitSourceAccountId) { @@ -422,7 +461,7 @@ const BillsManager: React.FC = ({ const payload = { name, - amount: parsedAmount, + amount: normalizedAmount, enabled: editingBenefit ? editingBenefit.enabled !== false : true, discretionary: benefitIsDiscretionary, isTaxable: isAccountSource ? true : benefitIsTaxable, @@ -469,6 +508,7 @@ const BillsManager: React.FC = ({