## Summary
- Add four view modes for net worth and account balance charts:
- **Stacked Accounts**: existing stacked bar chart showing balances by
account
- **Line**: net worth line chart with linear/log scale toggle
- **Change**: bar chart showing MoM%, YoY%, Rolling 12M changes
- **Waterfall**: bridge chart showing Start -> Change -> End transition
- Add Vitest for frontend unit testing with 52 tests covering all
calculation functions
- Proper sign handling for liabilities (credit cards and loans subtract
from net worth)
- Log scale disabled with warning when net worth includes zero or
negative values
- Shared components and logic between dashboard and account detail
charts
## New Files
- `vitest.config.ts` - Vitest configuration
- `resources/js/lib/chart-calculations.ts` - Pure computation functions
- `resources/js/lib/chart-calculations.test.ts` - 52 unit tests
- `resources/js/hooks/use-chart-views.ts` - Shared chart view state hook
- `resources/js/components/charts/` - Reusable chart components
## Test plan
- [x] Dashboard net worth chart shows view toggle and all four views
work
- [ ] Account detail page chart shows view toggle and three views work
(no stacked)
- [x] Line chart scale toggle works (log disabled with warning when
applicable)
- [x] Change chart series toggle (MoM, YoY, 12M EUR, 12M %) works
- [x] Waterfall chart month selector works
- [x] All 52 unit tests pass (`bun run test`)
- [x] Build succeeds (`bun run build`)
- [x] Dark mode styling correct
- Add onKeyDown handler to AmountInput component to format value when Enter is pressed
- Ensure amount is properly parsed before form submission regardless of submission method
- Add browser test to verify amount formatting on Enter key press
## Summary
This PR implements a global privacy mode feature that allows users to
hide all monetary amounts throughout the application.
## Changes
- **Created PrivacyModeContext**: New context provider with localStorage
persistence for privacy mode state
- **Created AmountDisplay component**: Centralized component for
displaying monetary amounts with blur effect when privacy mode is
enabled
- **Updated app logo logic**: Logo now shows birdhouse icon when privacy
mode is enabled or encryption key is not set
- **Added privacy mode toggle**: New toggle in user menu to
enable/disable privacy mode
- **Replaced inline amount formatting**: All monetary amounts now use
the AmountDisplay component
- **Updated trend indicators**: AmountTrendIndicator and
PercentageTrendIndicator now use AmountDisplay
- **Added animated transitions**: Smooth transitions when toggling
privacy mode
## Features
- Privacy mode state persists across sessions (localStorage)
- Amounts display as `$•••.••` with blur effect when privacy mode is
enabled
- Animated transitions (300ms) when toggling privacy mode
- App logo changes to birdhouse when privacy mode is on
- Toggle accessible from user menu at the top
## Testing
- Tested privacy mode toggle functionality
- Verified amounts are hidden/shown correctly across all pages
- Confirmed logo animation works properly
- Verified localStorage persistence
## Screenshots
Privacy mode can be toggled from the user menu, and all amounts
throughout the application will be hidden with a blur effect.
Adds a 'Sync now' button below the pending operations list in the
dialog, allowing users to manually trigger a sync directly from the
dialog.
## Changes
- Added new props to `PendingOperationsDialog`: `onSyncNow`,
`syncStatus`, and `isOnline`
- Added a button at the bottom of the dialog that triggers the sync
process
- Button shows loading state with spinning icon while syncing
- Button is disabled when offline or when sync is already in progress
## Summary
Add a labeling system for transactions that differs from categories in
that labels are ephemeral and created on-the-fly when assigned (no
pre-creation required). A transaction can have multiple labels
(many-to-many relationship), and labels can be used for filtering in the
transaction table and as actions in automation rules.
<img width="1372" height="484" alt="image"
src="https://github.com/user-attachments/assets/fd342b11-dafb-44ed-b818-578e1ea856e6"
/>
<img width="1373" height="613" alt="image"
src="https://github.com/user-attachments/assets/aa5cfb8b-50b7-4101-a872-54904924f234"
/>
<img width="1035" height="594" alt="image"
src="https://github.com/user-attachments/assets/ffa946b2-b01f-496b-8cb8-ab55ab5b79ed"
/>
## Changes
### Backend (Laravel)
- Add `labels` table with user_id, name, color, and soft deletes
- Add `label_transaction` pivot table for transaction-label many-to-many
- Add `automation_rule_labels` pivot table for automation rule-label
many-to-many
- Create Label model with relationships to User, Transaction, and
AutomationRule
- Add LabelController for CRUD operations (returns JSON for on-the-fly
creation)
- Add LabelSyncController for frontend sync
- Add LabelPolicy for authorization
- Update TransactionController to handle bulk label updates
- Update AutomationRuleController to handle label actions
- Add validation for label_ids in transactions and automation rules
### Frontend (React/TypeScript)
- Add Label type and color utilities
- Add labels table to IndexedDB (Dexie version 6)
- Create label-sync service with findOrCreate functionality
- Create LabelCombobox component with multi-select and create-on-type
- Add labels column to transactions table
- Add labels filter to transaction filters
- Add bulk label assignment in bulk actions bar
- Add labels action in create automation rule dialog
- Update rule engine to include labels in evaluation results
## Testing
- 11 feature tests for label CRUD
- 3 tests for label sync
- All 241 feature tests pass
## Screenshots
N/A - UI changes can be reviewed in browser
Allow users to select individual transactions for import instead of
automatically importing all non-duplicate transactions. Adds checkboxes
to the preview table with select-all functionality and updates the
import logic to only process selected transactions.
<img width="1076" height="592" alt="image"
src="https://github.com/user-attachments/assets/06dab7d0-87f1-4c69-97a4-fa98b33062d7"
/>
## Summary
Implements a complete user onboarding flow that guides new users through
setting up their account after registration.
## Features
- **Step-by-step wizard UI** with progress indicator and smooth
animations
- **E2E Encryption setup** with password strength indicator and
explanation of how encryption works
- **Account creation** supporting all account types (checking, savings,
credit card, investment, pension)
- **Category customization** with explanation of category types
(expenses, income, transfer)
- **Smart rules explanation** covering why there is no AI
auto-categorization (privacy & E2EE)
- **Transaction/balance import** based on account type
- **Sync integration** to ensure data consistency with backend
## Flow Diagram
```mermaid
flowchart TD
A[User Registration] --> B[Welcome]
B --> E[Encryption Explained]
E --> C{Has encryption key?}
C -->|No|F[Encryption Setup]
C -->|Yes|G{Has Existing Accounts?}
G -->|Yes| H[Show Existing Accounts]
G -->|No| I[Create First Account]
H --> J[Continue]
I --> K{Account Type?}
K -->|Checking/Savings/Credit Card| L[Import Transactions]
K -->|Investment/Pension| M[Import Balances]
J --> N[Category Types Explanation]
L --> N
M --> N
N --> O[Customize Categories]
O --> P[Smart Rules Explanation]
P --> Q[More Accounts?]
Q -->|Add More| I
Q -->|Finish| R[Complete]
R --> S[Redirect to Dashboard]
S --> T{Subscribed?}
T -->|No| U[Subscribe Page]
T -->|Yes| V[Dashboard]
```
## Technical Changes
### Backend
- Added `onboarded_at` field to users table with migration
- Created `EnsureOnboardingComplete` middleware for redirect logic
- Created `OnboardingController` with index and complete actions
- Custom `RegisterResponse` to redirect new users to onboarding
- Updated `AccountController::store` to return JSON for fetch requests
### Frontend
- `OnboardingLayout` - fullscreen layout with step progress
- `useOnboardingState` hook - manages step navigation and state
- 12 step components for each onboarding screen
- Backend sync after account creation and imports
### Tests
- Feature tests for onboarding middleware
- Updated existing tests to use `onboarded()` factory state
The logo icon now switches between BirdIcon (when encryption key is set) and Birdhouse (when not set) with smooth transitions. Also includes import reordering in the register page.
- Add user-session-storage utility to track and manage user IDs in localStorage
- Update SyncProvider to detect user changes and clear IndexedDB/localStorage when switching users
- Pass initial user from app.tsx to SyncProvider for proper initialization
- Reload page after clearing data to ensure clean state for new user
- Reorganize header layout with better mobile spacing and button grouping
- Make Prev/Skip button labels always visible (remove sm:inline)
- Add text-nowrap to "Back to Transactions" link to prevent wrapping
- Adjust main content alignment: justify-start on mobile, justify-center on sm
- Remove progress bar footer
- Fix indentation and formatting for conditional rendering
- Remove explicit button sizes from transaction actions menu
- Improve action buttons layout with flex containers and responsive classes
- Add hideColumnTextOnMobile prop to DataTableViewOptions for better mobile control
- Fix Tailwind class ordering and indentation in categorize page
- Update transaction filters actions container for better mobile layout
## Summary
Add a new quick categorization page that allows users to categorize
transactions without categories one by one, in a game-like interface.
https://github.com/user-attachments/assets/14fd7386-e87e-4a75-ae94-e487e3a48112
## Features
- **Quick Categorization Page**: A clean, focused interface for
categorizing transactions
- White screen with centered transaction card
- Categories sorted by usage frequency (stored in localStorage)
- Smooth animations when categorizing (card exits, success feedback,
next card enters)
- Progress indicator showing remaining transactions
- **Keyboard Shortcuts**:
- `Ctrl+R`: Open automation rules dialog
- `Ctrl+N`: Skip current transaction
- `Escape`: Go back to transactions page
- Arrow keys + Enter: Navigate and select categories
- **Automation Rules Integration**:
- Rules button in header to manage automation rules
- When closing the rules dialog, all remaining transactions are checked
against rules
- Matching transactions are automatically categorized
- **UI/UX**:
- Command component for category search and selection
- Encrypted text component for account names
- Responsive amount display with positive/negative styling
- Skip button moved to header for cleaner card design
## Access
New "Categorize" button added to the transactions page toolbar (left of
"Add Transaction" button) showing the count of uncategorized
transactions.
## Summary
Centralizes all pricing/subscription plan configuration in a single PHP
config file, making it easy to modify prices, features, and promotional
codes from one place.
## Changes
### Config (`config/subscriptions.php`)
- Added `plans` array with support for multiple billing cycles (monthly,
yearly, lifetime)
- Each plan includes: name, price, original_price (for discounts),
stripe_price_id, billing_period, and features
- Added `default_plan` setting to control which plan is highlighted
- Added `promo` config with enabled flag, code, description, and badge
### Frontend
- Created TypeScript types for `Plan`, `PromoConfig`, and
`PricingConfig`
- Updated `welcome.tsx` to display all plans in a responsive grid layout
- Updated `paywall.tsx` to show all plans with "Most Popular" and "Best
Value" badges
- Promo code section is now conditionally rendered based on
`promo.enabled`
### Backend
- Updated `HandleInertiaRequests.php` to share pricing config globally
- Removed redundant props from routes
### Tests
- Added test to verify pricing config structure is passed correctly
## How to Use
To modify pricing, edit `config/subscriptions.php`:
- Change `default_plan` to highlight a different plan
- Set `promo.enabled` to `false` (or `PROMO_ENABLED=false` in `.env`) to
hide promo codes
- Add/remove/modify plans as needed
- Add window check to getInitialColumnVisibility in transaction-list.tsx
- Add window check to getInitialColumnVisibility in transactions/index.tsx
- Add SSR guards to balance-import-config-storage.ts functions
- Add SSR guards to import-config-storage.ts functions
- Add SSR guards to use-appearance.tsx functions
The EncryptionKeyProvider was missing from the SSR entry point,
causing 'useEncryptionKey must be used within an EncryptionKeyProvider'
error when server-side rendering pages that use EncryptedText.
The previous implementation applied rounded corners based on static
position in the dataKeys array. When first/last segments had small
or zero values, the rounding wasn't visible.
Now uses a custom StackedBarShape component that dynamically determines
which segments are actually visible and applies border radius only to
the truly first/last visible segments in each bar stack.