whisper-money/config
Víctor Falcón a190d37307
feat(deps): upgrade Inertia.js from v2 to v3 (#769)
Upgrades Inertia.js from v2 to v3 on both sides — the major deliberately
left out of #764.

| Package | From | To |
| --- | --- | --- |
| `inertiajs/inertia-laravel` | 2.0.24 | **3.3.1** |
| `@inertiajs/react` | 2.3.17 | **3.6.1** |
| `axios` | transitive | **direct dep** (1.19.0) |

## Why axios becomes a direct dependency

The app imports `axios` in 18 runtime files, but it was never declared —
it arrived transitively through `@inertiajs/core` v2. v3 drops axios in
favour of its own XHR client, so without this the whole app would fail
to resolve the import.

Nothing relied on Inertia configuring axios: v2 never touched
`axios.defaults`, and there are no interceptors. `^1.19.0` also
satisfies core's optional peer range (`^1.15.2`).

## Breaking changes swept

Every v2→v3 breaking change from the [official upgrade
guide](https://inertiajs.com/upgrade-guide) was checked against the
codebase:

| Change | Call sites |
| --- | --- |
| `<title inertia>` → `<title data-inertia>` | fixed (`app.blade.php`) |
| `testing.page_paths/page_extensions` → `pages.paths/extensions` |
fixed (`config/inertia.php`) |
| `ComponentResolver` no longer accepts `Promise<{default}>` | fixed
(`app.tsx`, `ssr.tsx`) |
| `router.on('invalid'/'exception')` → `httpException`/`networkError` |
none — only `'navigate'` is used |
| `router.cancel()` → `cancelAll()` | none |
| `Inertia::lazy()` → `optional()` | none |
| `future` options block | none |
| `hideProgress`/`revealProgress` | none |
| arrow-fn `.layout =` → `[Layout]` | none — no page sets `.layout` |
| deprecated testing traits (`Has`/`Matching`/`Debugging`) | none |
| `qs` / `lodash-es` imports | none |

React 19.2.4, Laravel 13 and PHP 8.4 already satisfy v3's raised floors.

## DevTools kept opt-in

v3 adds a DevTools recorder that writes every request's page props to
`storage/inertia-devtools/` and **defaults to on in local**. On this app
that produced 38 dumps, the largest ~1 MB, holding whole transaction and
balance payloads — redaction only covers credential-shaped keys. It is
gitignored by the package, so it never reaches the repo, but for a
privacy-first product it shouldn't be the default. Gated behind
`INERTIA_DEVTOOLS_ENABLED` (set it to `true` in your own `.env` when you
need it).

## Deliberate behaviour change: `<Deferred>` on partial reloads

v3 no longer resets `<Deferred>` to its `fallback` during a partial
reload — existing content stays visible while fresh data loads. This
affects the dashboard (`onBalanceUpdated`) and account detail
(`handleTransactionCreated`).

**Kept as-is, on purpose.** Two reasons:

1. `dashboard.tsx:131-133` already carried a workaround for exactly this
v2 behaviour — it requests an unrelated cheap prop purely so the
deferred prop isn't refetched into a skeleton. The team already treated
the skeleton flash as a problem and v3 fixes it upstream, so restoring
it via the new `reloading` slot prop would undo an improvement. That
workaround is now redundant and can be simplified in a follow-up.
2. The "user can't tell it worked and re-submits" risk doesn't apply:
`edit-transaction-dialog.tsx:390` fires `toast.success('Transaction
created successfully')` *before* the reload, so the confirmation is
explicit and independent of the list.

## Verification

Full CI set, locally:

- `bun run build` — green
- `bun run test` — **353/353**
- `bun run types` — zero new errors (advisory step; pre-existing backlog
unchanged)
- `bun run format`, `bun run lint`, `bun run dry` — green
- `vendor/bin/pint --test`, `vendor/bin/phpstan analyse` — green
- `php artisan crap --base=origin/main` — 0 methods above complexity 10
- `pest --exclude-testsuite=Browser,Performance` — **2086/2088**
- `pest --testsuite=Browser` — **132/133**

Both test failures are pre-existing and reproduce identically on `main`
with Inertia v2 — I ran each one there to confirm:

- `DashboardTest::dashboard top categories roll child spending up into
the parent` — 409, the known local Inertia asset-version artifact
- `BudgetsFeatureNavigationTest::user cannot access another users
budget` — `Unknown column 'category_id'` on `budgets`, a factory/schema
mismatch

## Browser QA

Real browser sweep against the production build over dashboard,
transactions, accounts, budgets, settings and billing: **0 console
errors, 0 uncaught exceptions, 0 failed requests**. Page titles apply,
deferred props resolve, sidebar `<Link>` navigation stays client-side
with no document reloads, back/forward restores from Inertia history,
and a profile form submit persisted correctly.

## SSR: measured, left alone

v3 routes SSR through the Vite dev server (`/__inertia_ssr`) whenever
Vite is running hot, and we don't use the `@inertiajs/vite` plugin — so
in theory local dev makes a doomed POST per render. Measured it rather
than guessed: with `bun run dev` up, page loads are **16 ms** and the
failed attempt is a connection-refused costing ~0.6 ms, then it falls
back to client rendering. Dev mode renders correctly (React mounts, 0
console errors). Not worth a config knob.

Production is untouched — `Vite::isRunningHot()` is false there, so it
uses the real SSR server from `docker/supervisor/supervisord.conf`
exactly as on v2.

## Notes

- Wayfinder needed nothing: #764 already brought it to v0.1.21 (latest),
and `@laravel/vite-plugin-wayfinder` is on 0.1.7 (latest).
- Pre-existing bug both reviews surfaced, left for its own PR:
`auth/register.tsx:40` passes an `async` handler to `<Form onBefore>`,
which Inertia never awaits (same in v2 and v3), so
`transactionSyncService.clearAll()` is fire-and-forget on registration.
- `config/inertia.php` is hand-patched rather than republished. The v3
stub is ~190 lines; the unset keys resolve to package defaults via
`mergeConfigFrom`. Adopting the whole stub is a separate, reviewable
change.
- Remaining "Inertia v2" strings live inside Boost-generated blocks
(`CLAUDE.md`'s guidelines block and the vendored `SKILL.md` copies).
Hand-edits there get overwritten, so they should come from a Boost
regeneration. The three hand-written references are updated here.

## Demo


https://github.com/user-attachments/assets/3f0e7f6d-621a-4c3a-a2a0-68473ec59f10


<!-- PLACEHOLDER: drag the QA video in here -->
2026-08-11 14:33:31 +02:00
..
ai_categorization.php feat(ai): make the AI provider configurable (any laravel/ai provider, incl. local Ollama) (#718) 2026-07-22 09:01:51 +02:00
ai_reports.php feat(stats): post the Discord stats reports in Spanish, opened by an AI summary (#752) 2026-08-10 10:13:40 +02:00
ai_suggestions.php feat(ai): make the AI provider configurable (any laravel/ai provider, incl. local Ollama) (#718) 2026-07-22 09:01:51 +02:00
app.php feat(demo): gate demo account access behind a config flag (#580) 2026-06-22 11:01:27 +00:00
auth.php fix(auth): make build deterministic when REGISTRATION_ENABLED=false (#720) 2026-07-22 07:04:20 +00:00
cache.php chore: upgrade Laravel 12 to 13 (#242) 2026-03-25 12:56:33 +00:00
cashier.php feat(pricing): dynamic Stripe pricing with locale-aware formatting (#204) 2026-03-05 11:41:59 +00:00
currencies.php feat(currencies): add the Danish Krone (DKK) (#754) 2026-08-10 12:08:56 +02:00
database.php feat(console): add agent:db command for querying local and prod DB (#522) 2026-06-12 18:35:14 +02:00
filesystems.php Set up a fresh Laravel app 2025-11-07 12:01:36 +00:00
fortify.php fix(auth): make build deterministic when REGISTRATION_ENABLED=false (#720) 2026-07-22 07:04:20 +00:00
inertia.php feat(deps): upgrade Inertia.js from v2 to v3 (#769) 2026-08-11 14:33:31 +02:00
logging.php fix(logging): keep laravel.log writable across container UIDs (#451) 2026-05-29 15:10:50 +02:00
mail.php feat(mail): use AWS SES for email delivery (#422) 2026-05-25 10:59:57 +02:00
mcp.php fix(mcp): serve OAuth authorize on a dedicated host so the PWA can't capture it (#709) 2026-07-21 13:51:03 +02:00
passport.php feat(mcp): add OAuth 2.1 for Claude Desktop & ChatGPT connectors (Phase 3) (#691) 2026-07-17 19:10:48 +02:00
pennant.php Add Cashflow Analytics Feature (#49) 2026-01-05 13:06:50 +01:00
queue.php fix(queue): raise retry_after above the longest job timeout (PHP-LARAVEL-2D) (#645) 2026-07-05 09:31:23 +00:00
sanctum.php feat(mcp): read-only MCP server for Pro accounts (#689) 2026-07-17 16:54:15 +02:00
sentry.php fix(sentry): only report errors in production (#467) 2026-06-01 12:41:31 +02:00
services.php feat(mcp): serve the ChatGPT app directory domain challenge (#749) 2026-08-10 07:46:49 +00:00
session.php Set up a fresh Laravel app 2025-11-07 12:01:36 +00:00
subscriptions.php feat(subscriptions): trial/pricing A/B/C experiment (#600) 2026-06-27 18:00:15 +02:00