Files
timebank-cc-public/references/plans/LARAVEL_UPGRADE_PLAN.md
T

18 KiB
Raw Blame History

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

  1. [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.
  2. [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.
  3. [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.
  4. [V] Measure before and after (page times, search time, queue lag), and decide on rollback with triggers set in advance.
  5. [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.
  6. [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]

  1. [C] Finish the Laravel 11 follow-ups: quiet logs, main fast-forwarded, snapshot removed.
  2. [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.
  3. [V] Baseline measurements on production (for section 5), taken before anything changes.
  4. [C] Decision: 12 then 13 (recommended), or straight to 13.
  5. [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.

  1. 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.
  2. Composer clean-up.
    • Drop the psr/simple-cache ^1.0 pin.
    • Remove robsontenorio/mary (with config/mary.php) and laradumps/laradumps.
  3. 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.
  4. Updates within the current lines: Collision 8.6+, the staudenmeir packages, Livewire 3.8.x.
  5. [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:verify pass
  • three days without new error types in the production log

Phase 2: Laravel 12 (branch laravel-12) [C+V]

  1. [C] Composer:
    • laravel/framework ^12.0, cviebrock/eloquent-sluggable ^12.0, cviebrock/eloquent-taggable ^12.0, then composer update -W.
    • Write down every major version that changed in the lock file.
  2. [C] Upgrade guide checked against the code: Carbon applies. SVG images, the local disk root, UUIDv7, mergeIfMissing, Concurrency, Schema::getTables() and the token repository are not used or not affected. Diff the skeleton config 11 → 12.
  3. [C] Overrides: diff the vendor originals old → new for every override and published view (Jetstream HasProfilePhoto, Matchish, WireChat views, WireUI) and port the changes.
  4. [C+V] Tests:
    • full suite
    • the verification command (4.2)
    • the manual test matrix (4.3), with all 4 themes
  5. [C] Rehearsals with upgrade.sh:
    • a clone of main with a copy of the database (success path, rollback before and after going live)
    • the fictitious Uuro platform
    • a routine deploy afterwards
  6. [V] Test server (if decided in Phase 0): a real run there, the matrix, and fixes on laravel-12.
  7. [C] Docs: references/UPGRADE_LARAVEL_11_TO_12.md, versions in AGENTS.md.
  8. [C+V] Rollout: see section 6, per instance with --target origin/laravel-12; fast-forward main when 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:verify and 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]

  1. PHP 8.3 on every instance (from the Phase 0 inventory).
  2. WireChat 0.2 → 0.6:
    • read the changelogs
    • re-port the 45 override and view files, the _finishUpload override, 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)
  3. tinker 3, debugbar 4, sluggable/taggable 13; Symfony 7.4/8, Monolog 3.10.
  4. The official 12 → 13 upgrade guide, checked against the code item by item.
  5. 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
Mail a mailing preview, a test mail per type (email:send-test), inactivity mail texts (Carbon) medium
PDF 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:verify pass;
  • 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.sh from 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.sh fails and the cause is not fixed within 30 minutes
      • any high-priority row of the matrix fails
      • upgrade:verify fails
    • 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
  • 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.md entries 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:verify passes 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:verify and measurements
  • workers active
  • logs for 3 days
  • fast-forward main when 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

  1. Which instances exist besides timebank.cc, and on which PHP version?
  2. Is there a test or staging server, or should one be set up?
  3. 12 then 13 (recommended), or straight to 13?
  4. A preferred rollout window for production?
  5. Should upgrade:verify become 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