Files

361 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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):
```bash
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:**
```bash
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:**
```bash
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 |