## Summary
- keep newly created labels available in the combobox without reload
- propagate created labels through transaction forms and bulk actions
- add browser coverage for creating a label from transaction label
dropdown
## Tests
- php artisan test --compact tests/Browser/TransactionsTest.php
--filter='newly created labels'
- vendor/bin/pint --dirty --format agent
- npm run build
Note: `npm run types` still fails on pre-existing TypeScript errors
outside this change.
## Summary
- hide connected-account plan warning and price after the user chooses
connected setup once
- seed onboarding state from existing connected accounts
- add hook unit coverage and browser coverage for warning/price
visibility
## Tests
- bun run test -- resources/js/hooks/use-onboarding-state.test.tsx
- php artisan test --compact tests/Browser/OnboardingFlowTest.php
--filter="hides the connected plan warning"
- npm run types -- --pretty false 2>&1 | rg
"resources/js/(components/onboarding/step-create-account|hooks/use-onboarding-state|pages/onboarding/index)"
(no onboarding errors; repo has unrelated existing TS errors)
## Summary
- Keep non-onboarded users on the accounts step after bank authorization
errors
- Add feature and browser coverage for the callback error path
## Tests
- vendor/bin/pint --dirty --format agent
- php artisan test --compact tests/Browser/OnboardingFlowTest.php
--filter='returns to the accounts step'
- php artisan test --compact
tests/Feature/OpenBanking/AuthorizationControllerTest.php
--filter='callback with error'
## Summary
Mirror the real-estate historical-balance pattern for loan accounts.
When a loan is created with a current balance, linearly interpolate
monthly `account_balances` rows between the loan start date
(`original_amount`) and today (current balance).
## Approach
Since we don't know how the user actually paid down the loan, use simple
linear regression between two known points:
- `(start_date, original_amount)` from `loan_details`
- `(today, current_balance)` from the latest `account_balances` row
Rows are placed on the start date, the 1st of each intermediate month,
and today.
## Changes
- **`LoanBalanceGeneratorService`** — linear interpolation + upsert on
`(account_id, balance_date)` so existing rows aren't duplicated.
- **`GenerateHistoricalLoanBalancesJob`** — `ShouldQueue`, 3 tries, 10s
backoff. Takes account, original amount, start date, current balance,
from, to.
- **`Settings/AccountController::store`** (loan branch) — after creating
`loanDetail`, if a starting balance was provided: generate the last 12
months synchronously, dispatch older window to the queue when
`start_date` predates it.
- **`LoanDetailController::update`** — when the detail is first created
for an existing account, use the most recent `AccountBalance` as the
current value and apply the same sync/async split.
## Tests
- Service: interpolation, single-balance edge, future start_date, upsert
dedupe, monthly 1st placement, from/to range.
- Job: implements `ShouldQueue`, respects from/to range.
```
Tests: 8 passed (62 assertions)
```
No dependency or directory-structure changes.
## Summary
- soft-delete users by adding `deleted_at` to `users`
- rename deleted user emails with a timestamp prefix so original email
can be reused
- block email sends and banking follow-up work for deleted users while
preserving data
## Testing
- php artisan test --compact tests/Feature/DeleteUserCommandTest.php
tests/Feature/Settings/ProfileUpdateTest.php
tests/Feature/Auth/RegistrationTest.php
tests/Feature/Auth/AuthenticationTest.php
tests/Feature/Jobs/Drip/SendWelcomeEmailJobTest.php
tests/Feature/Console/SendUpdateEmailCommandTest.php
- vendor/bin/pint --dirty --format agent
## Summary
- remove the `real-estate` Pennant flag definition, request gating,
local auto-activation middleware, and frontend shared flag plumbing
- make real-estate account creation and onboarding availability the
default path for all users
- update feature and browser tests to cover default availability instead
of flag enable/disable behavior
## Testing
- vendor/bin/pint --dirty --format agent
- php artisan test --compact tests/Feature/RealEstateTest.php
- php artisan test --compact
tests/Feature/RealEstateAvailabilityTest.php
- php artisan test --compact tests/Browser/RealEstateAccountTest.php
- php artisan test --compact tests/Browser/OnboardingFlowTest.php
- php artisan test --compact --filter=\"can create a real estate account
linked to an existing loan\" tests/Browser/BankAccountsTest.php
## Notes
- `npm run build` completed the client build, but the Sentry sourcemap
upload step failed with an SSL/TLS error from the configured plugin
- deploy follow-up: run `php artisan pennant:purge real-estate` to
remove stale stored flag values
## Summary
- remove the Pennant-based `open-banking` flag and middleware gating so
open banking is always available for authenticated users
- simplify landing, onboarding, settings, and subscription flows to use
always-on open-banking behavior and remove stale frontend/shared flag
plumbing
- update open-banking tests and purge stored `open-banking` Pennant rows
## Testing
- `php artisan test --compact
tests/Feature/OpenBanking/InstitutionControllerTest.php`
- `php artisan test --compact
tests/Feature/OpenBanking/BinanceControllerTest.php`
- `php artisan test --compact
tests/Feature/OpenBanking/IndexaCapitalControllerTest.php`
- `php artisan test --compact
tests/Feature/OpenBanking/BitpandaControllerTest.php`
- `php artisan test --compact
tests/Feature/OpenBanking/AuthorizationControllerTest.php`
- `php artisan test --compact
tests/Feature/OpenBanking/ConnectionControllerTest.php`
- `php artisan test --compact
tests/Feature/OpenBanking/AccountMappingTest.php`
- `php artisan test --compact
tests/Feature/OpenBanking/OpenBankingFeatureFlagTest.php`
- `php artisan test --compact
tests/Feature/OpenBanking/SyncRetryAndLoggingTest.php`
- `php artisan test --compact tests/Feature/SubscriptionTest.php`
- `php artisan test --compact
tests/Feature/WelcomeBanksOrderingTest.php`
- `vendor/bin/pint --dirty --format agent`
## Notes
- `php artisan pennant:purge open-banking` was run to remove stale
stored values
- `php artisan test --compact tests/Browser/OnboardingFlowTest.php`
still has one unrelated real-estate onboarding browser failure (`it
creates a real estate account during onboarding when feature is
enabled`)
## Summary
- **Auto-calculates Annual Revaluation %** (CAGR) on the frontend when
purchase price, purchase date, and current market value are all provided
— pre-fills the revaluation field while still allowing manual override
- **Generates historical monthly balance records** via linear
interpolation from purchase date to today when creating a real estate
account with complete purchase data
- New `RealEstateBalanceGeneratorService` handles balance generation
with dates on the 1st of each month (matching the existing
`ApplyRealEstateRevaluationCommand` convention)
## Changes
### Backend
- **`app/Services/RealEstateBalanceGeneratorService.php`** (new) —
Linear interpolation service that builds balance dates (purchase date,
1st of each intermediate month, today) and creates `AccountBalance`
records using `updateOrCreate`
- **`app/Http/Controllers/Settings/AccountController.php`** — Calls the
service after real estate detail creation when all three inputs are
present
### Frontend
- **`resources/js/components/accounts/account-form.tsx`** — Added CAGR
auto-calculation via `useEffect` with a `useRef` flag
(`isRevaluationManuallySet`) to avoid overwriting manual user input
### Tests
- **`tests/Feature/Services/RealEstateBalanceGeneratorServiceTest.php`**
(new) — 7 unit tests covering interpolation, edge cases (same-day
purchase, flat values, single month spans)
- **`tests/Feature/RealEstateTest.php`** — 6 new feature tests covering
historical balance generation through the controller (with/without
purchase data, same-day, flat values)
All 38 tests in `RealEstateTest.php` and all 7 service tests pass.
## Summary
- add browser coverage for manual credit card, investment, retirement,
and other account creation flows
- replace placeholder transaction search coverage with real filter
assertions and add edit/delete transaction browser tests
- keep the new coverage focused on the existing account and transaction
browser test files
## Testing
- vendor/bin/pint --dirty --format agent
- php artisan test --compact tests/Browser/BankAccountsTest.php
tests/Browser/TransactionsTest.php
## Summary
- default the onboarding account setup choice to the connected Standard
option when open banking is available
- change the Standard onboarding card and connected-bank step accents
from blue to emerald for consistent plan styling
- update the onboarding browser test comment to reflect the new default
selection
## Verification
- `php artisan test --compact tests/Feature/SubscriptionTest.php`
- `php artisan test --compact tests/Browser/OnboardingFlowTest.php` *(3
unrelated existing failures in browser assertions)*
## Summary
- update the create account modal copy to explicitly mention loans and
property accounts
- clarify the manual option so users understand it also covers manually
created real-estate accounts
- update the browser test assertion to match the revised dialog copy
## Summary
- add real estate to the onboarding account type explainer and update
investment copy to mention crypto and cold wallets
- align onboarding manual account creation with the real-estate feature
flag and real-estate payload requirements
- cover the onboarding copy and real-estate creation flow with browser
tests
## Testing
- php artisan test --compact tests/Browser/OnboardingFlowTest.php
## Summary
- replace the budget rename test's fixed post-submit wait with stable UI
signals tied to the save lifecycle
- wait for the edit dialog to close, the budget show page to settle, and
the updated budget name to appear in the page content and title
- verify the change with `php artisan test --compact
tests/Browser/BudgetCrudTest.php`
## Summary
- Makes Income Sources and Expense Categories rows on the Cashflow page
clickable, navigating to the Transactions page pre-filtered by category
and date range
- Follows the exact same pattern as the dashboard's Top Spending
Categories card (`top-categories-card.tsx`)
- Fixes progress bar hover visibility by adding the `group` Tailwind
class to the link wrapper
## Changes
- **`resources/js/components/cashflow/breakdown-card.tsx`**: Added
optional `period` prop; each category row renders as an Inertia `<Link>`
with `category_ids`, `date_from`, and `date_to` query params when period
is provided; falls back to plain `div` otherwise
- **`resources/js/pages/cashflow/index.tsx`**: Passes `period` to both
`BreakdownCard` instances (income and expense)
- **`tests/Browser/CashflowCategoryNavigationTest.php`**: Browser tests
verifying clicking a category navigates to `/transactions` with the
correct query params
## Summary
- Reverts the CSV special-case added in `55f35c6` — `router.reload({
only: ['transactions'] })` now always fires in the syncing step so
CSV-imported transactions correctly appear in the categorize step.
- Fixes the `click('Skip')` selector in `OnboardingFlowTest` to use
`button:has-text("Skip")` instead of `'Skip'`, bypassing the Pest
browser plugin's exact-text fallback which timed out because the
button's full text content is `"Skip Ctrl+N"` (due to the `<Kbd>` child
element).
- Updates the onboarding flow browser test to exercise the full
categorize flow (skipping all 5 CSV transactions) instead of asserting
the empty state.
This commit was pushed after PR #201 was merged and did not make it into
main.
## Summary
- Adds an intro screen to the categorize transactions onboarding step
that shows before the categorizer UI, explaining that at least 5
transactions must be categorized to continue
- Displays four benefit cards (see where you spend, build better
budgets, spot savings, automate over time) to motivate the user
- Adds missing Spanish translation for \"Back to accounts\" and all new
strings introduced by the intro screen
## Summary
- Open Banking users who complete onboarding **without** connecting a
bank are no longer blocked at `/subscribe` — they can continue for free
via a new \"Continue for free\" button on the paywall
- Users who **did** connect a bank during onboarding see the standard
paywall with no free option (bank sync is a paid/Standard feature)
- Renamed the paid plan badge from **Pro** → **Standard** in the
onboarding UI
## Changes
### Backend
- `EnsureUserIsSubscribed` middleware: grants free access when
`open-banking` feature is active and the user has no
`banking_connections`
- `SubscriptionController::index()`: passes a `canUseFreePlan` boolean
prop to the paywall page
### Frontend
- `paywall.tsx`: accepts `canUseFreePlan` prop and renders a "Continue
for free" button that navigates to the dashboard
- `step-create-account.tsx`: badge and info text updated from "Pro" →
"Standard"
### Tests
- Two new browser tests in `OnboardingFlowTest.php`:
- Manual account flow → `/subscribe` shows "Continue for free"
- BBVA connected bank flow (EnableBanking sandbox) → `/subscribe` does
NOT show "Continue for free"
## Summary
- The \"Connected\" account card in `StepCreateAccount` was rendered
unconditionally during onboarding, allowing users without the
`open-banking` Pennant flag to see and interact with the bank connection
option
- Now follows the same pattern as `CreateAccountDialog`, which already
correctly checks `features['open-banking']` before showing the Connected
option
- The grid also collapses to a single column when only the Manual option
is available
## Changes
- **`resources/js/components/onboarding/step-create-account.tsx`** —
reads `features['open-banking']` from shared Inertia props and
conditionally renders the Connected card, the connected inline flow, and
the "Pro feature" hint message
- **`tests/Feature/OpenBanking/OpenBankingFeatureFlagTest.php`** — two
new tests asserting the flag value is correctly shared to the frontend
on the onboarding page (both enabled and disabled states)
## 🚪 Why?
### Problem
The onboarding flow had no integration with connected (bank-linked)
accounts. Users who connected a bank via OAuth were redirected to a
separate account-mapping page, breaking the onboarding flow. After
returning, there was no way to resume at the correct step. Additionally,
connected accounts were incorrectly shown in the manual import steps
(import-transactions / import-balances), which don't apply to them.
## 🔑 What?
### Changes
- **Inline bank connect wizard**: Added `ConnectAccountInline` component
that embeds the full 3-step bank OAuth flow within the onboarding
`create-account` step, replacing the redirect to a separate page.
- **Manual / Connected selection UI**: `StepCreateAccount` now presents
a choice between manual and connected account setup before proceeding.
- **Auto-account creation on OAuth callback**:
`AccountMappingController` and `AuthorizationController` auto-create
accounts for non-onboarded users and redirect back to
`/onboarding?step=create-account` instead of showing the mapping UI.
- **`?step=` deep-linking**: The onboarding page reads a `?step=` query
param on mount and starts at that step, so returning from bank OAuth
lands at the right place.
- **Skip import steps for connected accounts**: `handleAccountCreated`
detects `connected: true` and skips `import-transactions` /
`import-balances`, going directly to `category-types` (first account) or
`more-accounts` (subsequent accounts).
- **Fix phantom account in more-accounts**: Connected accounts are no
longer added to `createdAccounts` state (they already appear via
`existingAccounts`), preventing a duplicate nameless entry in the final
account list.
- **Feature flags enabled in non-production**: `open-banking` and
`account-mapping` Pennant flags now resolve to `! app()->isProduction()`
instead of always `false`.
- **Open-banking routes accessible during onboarding**: Removed
`onboarded`/`subscribed` middleware from open-banking routes;
`EnsureOnboardingComplete` middleware explicitly allows `open-banking.*`
routes through.
## ✅ Verification
### Tests
- Updated `tests/Browser/OnboardingFlowTest.php` to reflect new UI
(manual/connected selection).
- Existing feature tests for open-banking controllers pass (pre-existing
failures in Binance/Bitpanda/IndexaCapital/AuthorizationController tests
are from a prior commit unrelated to this work).
### Manual Verification
- Connected account flow tested end-to-end: bank OAuth → auto-account
creation → redirect to `create-account` step → Continue → skip import
steps → `category-types`.
- 7-account BBVA user correctly shows all accounts in `more-accounts`
via `filteredExistingAccounts` with no phantom entry.
- Feature flags confirmed active in local environment via tinker.
## Why
### Problem
On iOS (and narrow mobile screens), the transactions toolbar overflows
the viewport — the Columns button is pushed off-screen because the
actions row does not wrap.
Additionally, the \"Add Transaction\" button label was too long for
small screens, contributing to the overflow.
## What
### Changes
<img width="388" height="374" alt="image"
src="https://github.com/user-attachments/assets/7962f21d-70b7-427b-b01b-a855b687452d"
/>
## Why
### Problem
Users viewing a budget had no way to look back at historical periods —
the show page always displayed the current period with no navigation.
The cashflow page's period selector also used a different visual style
(loose buttons with gaps) compared to the new grouped button pattern
established for budgets.
## What
### Changes
- **Budget period navigation**: new `BudgetPeriodNavigation` component
renders a `ButtonGroup` with `[<] [date range] [>]` buttons. Clicking
the label returns to the current period; arrows navigate between
existing periods.
- **Backend**: `BudgetController::show` accepts an optional
`?period=<uuid>` query param to serve a specific period. Future periods
are blocked at the query level on both direct access and `nextPeriod`
resolution.
- **Dropdown**: removed the split `ButtonGroup` from the budget header;
"Edit budget" and "Delete budget" now live together in a single `˅`
dropdown.
- **Cashflow period selector**: updated `PeriodNavigation` to use
`ButtonGroup` + `size="icon"` buttons to match the same visual style.
- **Tests**: 4 new feature tests covering period navigation,
future-period blocking, cross-budget access (404), and `nextPeriod`
boundary behaviour.
## Verification
<img width="921" height="683" alt="image"
src="https://github.com/user-attachments/assets/ceb5f70b-a15a-4a36-ae49-5d84054a62f9"
/>
## Why
**Problem:** Bulk re-evaluation of transactions against smart rules was
broken because the code required an encryption key that no longer exists
in the app. Additionally, the rule builder UI had unnecessary complexity
(priority field, labels/tags, date/category/notes conditions) that added
cognitive load without value. Currency amounts were also always
formatted with US conventions regardless of the user's locale.
## What
**Automation rules – re-evaluation fix:**
- Remove encryption key requirement from `evaluateRules`,
`evaluateRulesForTransactions`, `evaluateRulesForNewTransaction`, and
`prepareTransactionData` (accept `CryptoKey | null`)
- Drop `isKeySet` guards from single-transaction and bulk re-evaluate
handlers in `transaction-list.tsx` and `transaction-actions-menu.tsx`
- Simplify `use-re-evaluate-all-transactions.tsx` to pass `null` as key
directly
**Automation rules – UI cleanup:**
- Hide priority field from create/edit dialogs (still sends `priority:
0` / `rule.priority` to satisfy backend validation)
- Remove labels/tags from create/edit dialogs, actions column, and table
display
- Remove `date`, `category`, and `notes` from rule condition field
options in `rule-builder-utils.ts`
- Add `opacity-30` to disabled X button when only one condition/group
remains
**Amount localization:**
- Replace hardcoded `'en-US'` with `useLocale()` in `AmountDisplay` so
symbol position and number format follow the user's locale (e.g.
`89.705,00 €` in Spanish)
## Verification
- 38/38 `AutomationRuleTest` + `AutomationRuleEvaluationTest` tests pass
- Re-evaluation works without an encryption key set
- Spanish locale users see `89.705,00 €` instead of `€89,705.00`
## Why
The `/accounts` page was making two extra client-side API calls via the
`useDashboardData()` hook:
- `/api/dashboard/net-worth-evolution` (~14.5s in dev)
- `/api/dashboard/top-categories` (~119ms, completely unused on this
page)
The backend logic itself is fast (~34ms), but the full middleware stack
cost of extra HTTP roundtrips in the dev environment was adding
significant overhead.
## What
### Backend (`AccountController.php`)
- Added `ExchangeRateService` constructor injection
- Added `Inertia::defer()` prop `accountMetrics` to the `index()` action
- Added `getAccountMetrics()`, `formatMonth()`, `convertBalance()`
private methods
- Uses `BalanceLookup::forAccounts()` for efficient batch loading (2 SQL
queries regardless of account count)
### Frontend (`Accounts/Index.tsx`)
- Removed `useDashboardData()` hook import and usage
- Added `accountMetrics` as an optional deferred prop (undefined until
resolved)
- Loading state derived from `!accountMetrics`, which naturally drives
the existing `loading` prop on `AccountListCard` (skeleton UI already
existed)
- Added `handleBalanceUpdated` callback using `router.reload({ only:
['accountMetrics'] })` for targeted refresh
## Verification
### Tests
- 3 new Pest tests covering deferred prop behavior:
- `accounts index defers account metrics` — verifies metrics are missing
from initial response, present after `loadDeferredProps()`, with correct
balance values
- `accounts index deferred metrics includes invested amount for
investment accounts` — verifies invested amount for investment account
types
- `accounts index deferred metrics returns null invested amount for
non-investment accounts` — verifies non-investment accounts get null
invested amount
- All 15 tests in `AccountControllerTest.php` pass
## Summary
- Removed all `setupEncryptionKey()` / `visitWithEncryptionKey()` calls
from browser tests — encryption key setup in localStorage is no longer
needed since new users don't have encryption
- Removed `encryption_salt` from `UserFactory::onboarded()` state and
`OnboardingFlowTest` user creation
- Removed `name_iv` from `Account::factory()` calls in
`BankAccountsTest`
- Deleted `DemoEncryptionService` and its unit test — demo command now
stores plaintext account names and transaction descriptions
- Removed `demoEncryptionKey` Inertia shared prop and `encryption_key`
from demo config
- Removed encryption helper methods from `TestCase.php` and global
`setupEncryptionKey()` from `Pest.php`
## Test plan
- [x] Run `php artisan test --exclude-testsuite=Browser` — all
non-browser tests pass
- [x] Run `php artisan test --testsuite=Browser` — browser tests pass
without encryption key setup
- [x] Run `php artisan demo:reset` — demo account created with plaintext
data
- [x] Verify existing encryption migration tests still pass
(`EncryptionTest`, `DecryptTransactionsTest`,
`PlaintextTransactionsTest`)
## Summary
- Add `GET /api/transactions?encrypted=true` endpoint for paginated
listing of encrypted transactions
- Add `PATCH /api/transactions/bulk` endpoint for batch-updating
decrypted transaction data (max 50 per request, bypasses model events)
- Add `useDecryptTransactions` hook that mirrors the existing account
name decryption flow: fetches encrypted transactions page-by-page,
decrypts `description`/`notes` client-side via Web Crypto API, sends
plaintext back and clears IVs
- Wire the hook into `AppSidebarLayout` so decryption runs automatically
when the user unlocks their encryption key
- Once all transactions (and accounts) are decrypted, the encryption key
button in the header disappears automatically
## Test plan
- [x] 11 Pest tests covering encrypted/plaintext filtering, pagination,
user scoping, bulk update, authorization, validation, nullable notes,
guest access, and no-model-events behavior
- [x] Manual: create encrypted transactions, unlock encryption key,
verify transactions get decrypted and the key button disappears
## Summary
- Removes the `plaintext-transactions` Pennant feature flag — plaintext
is now the default for all users
- Removes encryption guards and `isPlaintext` conditionals from
transaction create/edit/import flows, keeping only the plaintext code
paths
- Makes `EncryptionKeyButton` conditional — only shown when user has
legacy encrypted accounts or transactions
- Removes encryption onboarding steps (`step-encryption-explained`,
`step-encryption-setup`) and related state/props
- Updates landing page to replace E2E encryption marketing with
privacy-first messaging ("Your Data, Your Rules" section)
- Updates privacy policy to replace E2E encryption claims with accurate
security language (encryption at rest, TLS in transit, no third-party
sharing)
- Cleans up tests to remove feature flag assertions and
`Feature::activate()` calls
**22 files changed, 145 insertions, 730 deletions**
## Test plan
- [x] Create a transaction without encryption key unlocked — should
succeed
- [x] EncryptionKeyButton should not appear in header for users with no
encrypted data
- [x] EncryptionKeyButton should appear for users with legacy encrypted
transactions/accounts
- [x] Landing page has no E2E encryption references, shows new privacy
section
- [x] Onboarding flow has no encryption setup steps
- [x] Privacy policy reflects accurate security language
- [x] Frontend builds successfully (`bun run build`)
- [x] All linting passes (`bun run lint`, `vendor/bin/pint --dirty`)
## Summary
- Remove the `budgets` Pennant feature flag — budgets is now enabled for
all users
- Delete `EnsureBudgetsFeature` middleware and its route guard
- Remove `budgets` from shared Inertia features and the `Features`
TypeScript interface
- Remove all `Feature::for($user)->activate('budgets')` calls from tests
- Delete `BudgetFeatureFlagTest` and feature-disabled test cases from
`BudgetsFeatureNavigationTest`
## Summary
- Enables `MustVerifyEmail` on the `User` model so new users must verify
their email before accessing protected routes
- Unverified users are redirected to `/email/verify` when attempting to
access routes behind the `verified` middleware (subscribe, onboarding,
dashboard)
- A verification email is automatically sent on registration via Fortify
## Screenshot

## Video
https://github.com/whisper-money/whisper-money/raw/mail-verification/storage/videos/ac0945c527f014b9cd657f18c911f496.webm
## Test plan
- [x] New test: registration sends a `VerifyEmail` notification
- [x] New test: newly registered users have `email_verified_at` as null
- [x] New test: unverified users are redirected to `verification.notice`
from protected routes (subscribe, onboarding, dashboard)
- [x] New test: verified users are not redirected to verification notice
- [x] Existing email verification and notification tests still pass (31
auth tests, 52 settings tests)
- [x] Browser walkthrough: registration → redirected to `/email/verify`
page with "Resend verification email" button
## Summary
- Fixes a race condition where the automerge workflow merged PR #94
despite `browser-tests` failing
- Adds SHA verification to ensure the CI run matches the PR's latest
commit (prevents stale merges from earlier pushes)
- Adds a failed-checks guard that inspects all PR check statuses before
merging
## What happened
PR #94 had two CI runs: an earlier one that passed and a later one where
`browser-tests` failed. The automerge workflow triggered on the first
successful run and merged the PR before the second run completed,
because it only checked `workflow_run.conclusion == 'success'` without
verifying it was for the latest commit.
## Test plan
- [x] Push a PR with the Automerge label, verify it merges when all
checks pass
- [x] Push a follow-up commit that breaks browser-tests — verify the
earlier success doesn't trigger a merge
- [x] Verify PRs without the Automerge label are unaffected
## Overview
We're excited to introduce budgeting capabilities to Whisper Money! This
feature helps you take control of your finances by setting spending
limits and tracking your progress over time.
## Screenshots
<img width="1316" height="793" alt="image"
src="https://github.com/user-attachments/assets/ac394d36-cded-4ea4-9883-120785e260f1"
/>
<img width="1315" height="907" alt="image"
src="https://github.com/user-attachments/assets/7c682474-5aa7-4388-b626-29b56f5ebbef"
/>
<img width="1315" height="992" alt="image"
src="https://github.com/user-attachments/assets/21eace45-23c6-472d-9aa0-0feb6db3fba4"
/>
## What's New
### Create Flexible Budgets
- Set budgets for specific categories or labels
- Choose from monthly, weekly, bi-weekly, or custom periods
- Set your own budget start date for better alignment with your pay
schedule
### Track Your Spending
- Visual spending charts show how much you've spent vs. your budget
- See at a glance which budgets are on track and which need attention
- View all transactions that count toward each budget
### Smart Budget Management
- **Carry Over**: Unused budget amounts automatically roll into the next
period
- **Reset**: Unused amounts return to your available money pool
- Edit or delete budgets anytime as your needs change
### Easy Access
- New Budgets section in the main navigation
- Quick overview cards showing budget status
- Detailed budget pages with spending history and transaction lists
## How It Works
1. Create a budget by selecting a category or label and setting your
spending limit
2. Your transactions are automatically matched to relevant budgets
3. Track your progress with visual charts and spending summaries
4. Adjust your budgets as needed to stay on track with your financial
goals
This feature is now available behind a feature flag and can be enabled
for users who want to start budgeting their expenses.
This PR simplifies the IndexedDB synchronization mechanism by removing
individual sync controllers and services, and instead using Inertia.js
shared props to provide data globally across the application.
## Benefits
1. **Simplified Architecture**: Removed complex sync logic for
static/semi-static data (accounts, categories, banks, labels, automation
rules)
2. **Better Performance**: Data is now shared via Inertia props,
eliminating unnecessary API calls and IndexedDB operations
3. **Reduced Complexity**: Significantly reduced codebase size (~2000
lines removed)
4. **Better UX**: Data is immediately available on page load without
waiting for sync operations
5. **Maintainability**: Fewer moving parts means easier to maintain and
debug
## Migration Notes
- Transaction syncing still uses IndexedDB for offline support
- All other data (accounts, categories, banks, labels, automation rules)
is now fetched via Inertia shared props
- Components automatically receive updated data on navigation without
manual sync operations
- 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
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
* Add category type field support
- Remove default value from type column in migration
- Add type validation to StoreCategoryRequest and UpdateCategoryRequest
- Include type field in CategoryController index response
- Assign proper types to all default categories (income/expense/transfer)
- Update CategoryFactory to include type field
- Add type field to TypeScript Category interface
- Add type select field to create and edit category dialogs
- Update all category tests to include type field
- Add new tests for type field validation
All tests passing (27 category-related tests)
* Add CategoryColor enum and update validation rules
* Remove unused AccountType enum class
* Add category type column to settings page
* Remove obsolete import screenshots, add new category edit screenshot
* feat(ui): add transfer type description alerts
* feat(tests): Add .gitignore and update CategoriesTest.php for Browser tests
* fix: update transfer category UI and test
* chore: update .gitignore to include Screenshots directory
* Change category type from income to expense
- Add null safety checks in rule evaluation
- Improve error messages in sync context with user-friendly formatting
- Add browser test for automation rule application during import
- Update test CSV data to better test rule matching
- Add detailed error logging for failed transaction imports
- Allow description field to map to multiple columns
- Update ColumnMapping type to accept string or string[] for description
- Join multiple description columns with newlines
- Add validation for multi-column descriptions
- Add test asset for multi-description CSV files