18 KiB
Laravel upgrade plan: 11 → 12, then 13 (combined)
Combined by Claude Code, 2026-10-15 from four documents in this folder:
- Claude's plan:
LARAVEL_UPGRADE_PLAN_CLAUDE.md - Vibe's plan:
LARAVEL_UPGRADE_PLAN_VIBE.md - Vibe's hybrid:
LARAVEL_UPGRADE_PLAN_HYBRID.md - Claude's first hybrid, merged into this file
Status: proposal, waiting for the user's decisions (section 11). Nothing is approved or started.
Origin of each part: [C] Claude, [V] Vibe (its plan or its hybrid), [C+V] combined. Where a plan assumed something that does not match this codebase, the corrected fact is used (section 2). Section 10 lists what was left out and why; section 12 lists how the two hybrids differed.
1. Approach in short
- [C] Laravel 12 first, Laravel 13 as a second project right after it. Laravel 12 keeps WireChat 0.2; Laravel 13 needs WireChat 0.6, the largest single risk.
- [C] Prepare on Laravel 11 with small, normal deploys (Carbon 3, flatpickr into the app, remove unused packages), so the framework upgrade itself stays small.
- [V] Verify by area of this application: multi-guard profiles, the two permission layers, white-label platforms, payments, chat, search, themes, background processes. Use a test matrix, a reusable verification command and explicit success criteria.
- [V] Measure before and after (page times, search time, queue lag), and decide on rollback with triggers set in advance.
- [C+V] Reuse the proven tooling (
scripts/upgrade.sh: backup, snapshot, rollback, maintenance with a bypass) with a deliberate order: local rehearsals, then a test server if there is one, then production in a quiet window, then other instances. - [V] Tell users in advance, and keep a checklist for before, during and after.
2. Facts that both plans rest on
Checked 2026-10-15 against Composer, Packagist and the code of branch laravel-11.
| Topic | Fact | Note |
|---|---|---|
| Laravel releases | 12.70.0 and 13.36.0 are out | Vibe's plan said "when Laravel 12 is released" |
| PHP | Laravel 12 needs PHP 8.2+, Laravel 13 needs 8.3+; production runs 8.3.35 | Vibe's plan required 8.3 for 12; only 13 needs it |
| Support | 11: no security fixes since March 2026 · 12: security fixes until 2027-02-24 | Verify on laravel.com/docs/master/releases |
| Livewire | 3.8 has releases for Laravel 12 and 13: 4.0 is not needed | Vibe's plan had a Livewire 4 migration |
| Spatie Permission | we run 6.25, which supports 12 and 13 | Vibe's plan had a v5 → v6 migration |
| Reverb | 1.12 supports 12 and 13; there is no Reverb 2 | |
| Scout | 10.x supports 12; 11 is optional | |
| Blockers | flatpickr (no release after Laravel 11), Carbon 3 (28 diffIn*() calls), psr/simple-cache pin, sluggable/taggable new majors, unused Mary and laradumps |
Not in Vibe's plan |
| Laravel 13 only | WireChat 0.6 (we have 0.2 with 45 override and view files), tinker 3, debugbar 4 | |
| Backup and rollback | scripts/upgrade.sh already does verified backups, a vendor/build snapshot, a code and/or database rollback, and maintenance with a bypass; tested in 3 rehearsals and the 2026-10-15 production upgrade |
Vibe's plan wrote new backup and rollback scripts: not needed, reuse |
Package details: references/plans/LARAVEL_UPGRADE_PLAN_CLAUDE.md, section 3.
3. Phases
Phase 0: Preparation, no code changes [C+V]
- [C] Finish the Laravel 11 follow-ups: quiet logs,
mainfast-forwarded, snapshot removed. - [V] Inventory per server: PHP version, Composer version, free disk, services (workers, Reverb, scheduler) and the platform config name. Use one read-only block per server. Laravel 13 needs PHP 8.3 everywhere.
- [V] Baseline measurements on production (for section 5), taken before anything changes.
- [C] Decision: 12 then 13 (recommended), or straight to 13.
- [V] Is there a test or staging server? If not, decide whether to set one up or rely on local rehearsals (which worked for Laravel 11).
Phase 1: Preparation on Laravel 11 (normal deploys, one step at a time) [C]
Each step is a commit on main, the full suite passes, and it ships with the normal deploy.sh.
- Carbon 3.
nesbot/carbon ^3.8.- Review the 28
diffIn*()calls: in Carbon 3 they are signed and fractional, which would give, for example, negative durations in the inactivity mails. - Add tests for durations shown to people.
- Composer clean-up.
- Drop the
psr/simple-cache ^1.0pin. - Remove
robsontenorio/mary(withconfig/mary.php) andlaradumps/laradumps.
- Drop the
- Flatpickr into the app.
- The component (already overridden in
app/Overrides/Asdh), its views and its assets move into the app under the same name, so the 21<x-flatpickr>views stay unchanged. - Remove the package.
- The component (already overridden in
- Updates within the current lines: Collision 8.6+, the
staudenmeirpackages, Livewire 3.8.x. - [V] After each deploy: the verification command (section 4.2) and a short look at the logs.
[V] Commands (each step is its own commit; run the full suite after each):
composer require nesbot/carbon:^3.8 -W # 1, plus the diffIn*() review
composer remove robsontenorio/mary laradumps/laradumps # 2, plus deleting config/mary.php
composer require psr/simple-cache:^3.0 -W # 2, or remove the pin from composer.json
composer remove asdh/laravel-flatpickr # 3, after the component lives in the app
composer update nunomaduro/collision "staudenmeir/*" livewire/livewire -W # 4
(cviebrock/eloquent-sluggable and -taggable cannot move on Laravel 11: their next majors need
Laravel 12, so they belong to Phase 2. The flatpickr component goes to app/View/Components/, the
Laravel convention.)
[V] Phase 1 is complete when:
- Carbon 3, the clean-up and flatpickr are on production through normal deploys
- the full suite and
upgrade:verifypass - three days without new error types in the production log
Phase 2: Laravel 12 (branch laravel-12) [C+V]
- [C] Composer:
laravel/framework ^12.0,cviebrock/eloquent-sluggable ^12.0,cviebrock/eloquent-taggable ^12.0, thencomposer update -W.- Write down every major version that changed in the lock file.
- [C] Upgrade guide checked against the code: Carbon applies. SVG images, the
localdisk root, UUIDv7,mergeIfMissing,Concurrency,Schema::getTables()and the token repository are not used or not affected. Diff the skeleton config 11 → 12. - [C] Overrides: diff the vendor originals old → new for every override and published view (Jetstream
HasProfilePhoto, Matchish, WireChat views, WireUI) and port the changes. - [C+V] Tests:
- full suite
- the verification command (4.2)
- the manual test matrix (4.3), with all 4 themes
- [C] Rehearsals with
upgrade.sh:- a clone of
mainwith a copy of the database (success path, rollback before and after going live) - the fictitious Uuro platform
- a routine deploy afterwards
- a clone of
- [V] Test server (if decided in Phase 0): a real run there, the matrix, and fixes on
laravel-12. - [C] Docs:
references/UPGRADE_LARAVEL_11_TO_12.md, versions inAGENTS.md. - [C+V] Rollout: see section 6, per instance with
--target origin/laravel-12; fast-forwardmainwhen all are done.
[V] Commands:
git switch -c laravel-12 main # only after the user agrees: a whole-tree git command
composer require laravel/framework:^12.0 cviebrock/eloquent-sluggable:^12.0 cviebrock/eloquent-taggable:^12.0 -W
git diff composer.lock | grep -E '"version"' | head # write down every major that moved
[V] Phase 2 is complete when:
- Laravel 12 runs on every instance
upgrade:verifyand the high-priority matrix rows pass on each- three days without new error types per instance
Phase 3: Laravel 13 (separate project, right after Laravel 12 is live) [C]
- PHP 8.3 on every instance (from the Phase 0 inventory).
- WireChat 0.2 → 0.6:
- read the changelogs
- re-port the 45 override and view files, the
_finishUploadoverride, private attachments and the listeners across profile switches - extend the chat tests first
- can be prepared on Laravel 12 (WireChat 0.6 supports
^11|^12|^13)
- tinker 3, debugbar 4, sluggable/taggable 13; Symfony 7.4/8, Monolog 3.10.
- The official 12 → 13 upgrade guide, checked against the code item by item.
- Same method as Phase 2. Finish before 2027-02-24.
[V] Commands:
composer require laravel/framework:^13.0 namu/wirechat:^0.6 laravel/tinker:^3.0 \
cviebrock/eloquent-sluggable:^13.0 cviebrock/eloquent-taggable:^13.0 -W
composer require --dev barryvdh/laravel-debugbar:^4.0 -W
[V] Phase 3 is complete when:
- Laravel 13 runs on every instance, PHP 8.3+ everywhere
- chat works as before: messages, images, private attachments, profile switches
upgrade:verify, the full suite and the matrix pass- three days without new error types per instance
4. Verification
4.1 Automated
[C] The full suite: 656 tests, green on Laravel 11. It runs on every phase step and before every rollout.
4.2 Verification command [V idea, C implementation]
[V] A read-only check per area, runnable locally and on every server after each step. Instead of three
tinker scripts, [C] one artisan command, php artisan upgrade:verify, with correct checks and a
non-zero exit code on failure:
| Area | Check |
|---|---|
| Framework | Laravel and PHP version, config:merge --all --dry-run clean |
| Profiles and guards | the 4 guards configured; one User, Organization, Bank and Admin load; the getActiveProfile() helpers exist |
| Permissions | for every AdminRole and OrganizationRole: each scoped Spatie role with that suffix has exactly the same permission set (sorted arrays compared, not the return value of sort()); missing scoped roles listed |
| White-label | config('app.timebank_config') set and its file exists; platform_name() is not empty and not Example Timebank; theme_color('brand') set; local_pages and android_app present |
| Payments | the app's MySQL user may not UPDATE or DELETE transactions (reuse scripts/test-transaction-immutability.sh) |
| Search | a Scout search returns results for each searchable model |
| Background | queue sizes per queue; Reverb answers on its port |
Written in Phase 1 (on Laravel 11), so the baseline is known before the framework changes.
Done 2026-10-15 (branch prep-l12-verify): php artisan upgrade:verify [--only=area,...]. Two checks
turned out to need care: organization_roles is created by database:update, not by a migration (a warning
when missing), and roles without a scope prefix are base roles and are skipped.
4.3 Manual test matrix [V, trimmed to this application]
| Area | Cases | Priority |
|---|---|---|
| Profiles | register, log in and out, switch User → Organization → Bank → Admin and back, session survives the switch | high |
| Permissions | admin pages per role, organization and bank manager rights, edit a role and see it take effect | high |
| Payments | a payment, balance and history updated, a call paused at zero credits | high |
| Chat | message between profiles, image upload (6 MB), message after a profile switch | high |
| Search | results in en and nl, a category browse | high |
| White-label | all 4 themes; a platform with its own config (the Uuro rehearsal) | high |
a mailing preview, a test mail per type (email:send-test), inactivity mail texts (Carbon) |
medium | |
| an account statement, a post PDF | medium | |
| Dates | every date picker (flatpickr) | medium |
| Background | queue workers process jobs, Reverb presence, scheduler tasks | medium |
5. Measurements and success criteria [V, adapted]
Baseline (Phase 0) and after each rollout (simple, production-safe, no load test on production):
| Metric | How |
|---|---|
| Response time of the home page, a profile page, search, the payment page | curl -o /dev/null -s -w '%{time_total}', 5 runs each, median |
| Search time | timed Scout query in tinker |
| Queue lag | Queue::size() per queue, and the age of the oldest job |
| Errors | .ERROR count per day in laravel-<date>.log |
Done when:
- the full suite and
upgrade:verifypass; - the manual matrix's high-priority rows pass;
- no new error types in the log for 3 days;
- response times are within 25% of the baseline.
6. Rollout and rollback [C+V]
- Order [V]: local rehearsals → test server (if any) → production → other instances, one at a time.
- Window [V, adapted]:
- production at a quiet moment the admin is awake for (for example an evening, 20:00–22:00 local time), not 02:00
- 5 to 15 minutes of maintenance, by the measure of the Laravel 11 upgrade
- Tool [C]:
scripts/upgrade.shfrom the target branch, run in tmux, with a dry run first. - Rollback triggers, decided in advance [V]:
- before going live, roll back (the database restore is allowed) when:
deploy.shfails and the cause is not fixed within 30 minutes- any high-priority row of the matrix fails
upgrade:verifyfails
- after going live, roll back the code only when:
- logging in, payments or chat break and a fix is not available within an hour
- errors repeat for many users
- before going live, roll back (the database restore is allowed) when:
- Code-only by default after going live [C]: a database restore deletes payments and messages written since the backup.
7. Communication [V, sized for this platform]
- 24 hours before: a short message to users, a post or a banner: date, time, expected few minutes of downtime.
- During: the maintenance page; the admin tests through the bypass URL.
- After: a short "done" note only if something visible changed.
- Between the agents [C]:
.agents/WORKLOG.mdentries per phase step; one agent per step; the user dispatches.
8. Checklists [V, corrected]
Before (per instance):
- Phase 0 inventory done; PHP and disk sufficient
upgrade:verifypasses on the current version- baseline measured
- users informed
- the MySQL root password at hand (no
DB_DEPLOY_*in.env)
During:
- tmux, dry run, real run
- maintenance on
- config merge
y/yes - restart Reverb, PHP-FPM and workers
- verification and matrix through the bypass
- go or roll back by the triggers
After:
upgrade:verifyand measurements- workers
active - logs for 3 days
- fast-forward
mainwhen all instances are done - remove the snapshot
- lessons learned added to the upgrade guide
9. Effort (working time, rough) [C, with Vibe's verification work added]
| Phase | Effort |
|---|---|
| 0 | 1 to 2 hours (inventory, baseline, decisions) |
| 1 | 2 to 3 days (Carbon review, flatpickr, upgrade:verify), plus normal deploys |
| 2 | 2 to 3 days, including rehearsals, plus a rollout evening per instance |
| 3 | 3 to 5 days, mainly WireChat 0.6 |
9a. Timeline [V idea, C dates]
Vibe's hybrid put Laravel 13 live on 2026-10-26. That is too early: Phase 0 depends on the Laravel 11 follow-ups (quiet logs until 2026-10-18), and each production change needs a few quiet days before the next one. A realistic schedule, to be confirmed by the user:
| Phase | Work | Calendar window | Milestone |
|---|---|---|---|
| 0 | 1 to 2 hours | from 2026-10-19 | inventory, baseline, decisions |
| 1 | 2 to 3 days | late October to early November | Carbon 3, flatpickr, clean-up on production |
| 2 | 2 to 3 days | November | Laravel 12 on every instance |
| 3 | 3 to 5 days | December to January | Laravel 13 on every instance |
| deadline | before 2027-02-24 | end of Laravel 12 security fixes |
10. What was left out, and why
| From | Left out | Why |
|---|---|---|
| Vibe | Livewire 4, Spatie v5 → v6, Reverb 2, Scout 11 migrations | not needed (section 2) |
| Vibe | new backup and rollback scripts | upgrade.sh already does it, tested |
| Vibe | composer.json PHP requirement to ^8.3 for Laravel 12 |
only Laravel 13 needs it; do it in Phase 3 |
| Vibe | load test with 100 concurrent users on production | puts load on the live site; response-time samples are enough here |
| Vibe | a 02:00 production window and a status page | the admin must be awake to test and decide; the platform is small |
| Vibe's hybrid | running Vibe's three original verification scripts | they read a non-existent config key (platform_name) and compare sort() return values, so permissions always look in sync; replaced by upgrade:verify (4.2) |
| Vibe's hybrid | decisions marked as made, "Approved Strategy" | the user has not decided yet (section 11) |
| Vibe's hybrid | production at 02:00–04:00, Laravel 13 by 2026-10-26 | see section 6 and the timeline in 9a |
| Claude | — | everything kept |
11. Open questions for the user
- Which instances exist besides timebank.cc, and on which PHP version?
- Is there a test or staging server, or should one be set up?
- 12 then 13 (recommended), or straight to 13?
- A preferred rollout window for production?
- Should
upgrade:verifybecome a permanent command, also useful after normal deploys?
12. How the two hybrid plans differed
Both hybrids, written at the same time without seeing each other, chose the same structure: Claude's phases (preparation on 11, then 12, then 13) with Vibe's verification and test matrix. The differences, and what this plan uses:
| Topic | Vibe's hybrid | Claude's hybrid | This plan |
|---|---|---|---|
| Verification | Vibe's 3 tinker scripts | one upgrade:verify command |
upgrade:verify (correct checks) |
| Measurements, rollback triggers, communication | not included | included (from Vibe's own plan) | included |
| Completion criteria per phase | yes | overall only | per phase (from Vibe's hybrid) |
| Commands per phase | yes | no | yes, corrected |
| Timeline | calendar dates, Laravel 13 by 2026-10-26 | effort only | effort plus realistic windows |
| Decisions | marked as decided | open | open |
| Production window | 02:00–04:00 | an evening the admin is awake | an evening |