Files
Obsidian-Vault/Work/Projects/Laravel-v12-Migration/Logs/upgrade-log-contentmakers.md
Vincent Verbruggen 1999dbbc11 Add upgrade logs for Laravel 11 to 12 migration across multiple projects
- Created detailed upgrade log for deversspecialist with phases including Composer updates, Filament upgrades, Tailwind migration, Pest updates, and final verification.
- Added upgrade log for goos project, documenting version changes, encountered issues, and resolutions during the migration process.
- Documented Composer dependency upgrades for qlshandling, highlighting significant changes and necessary code adjustments.
- Introduced a template for future upgrade logs to standardize documentation for Laravel migrations.
2026-02-20 10:43:24 +01:00

539 lines
30 KiB
Markdown

# Update Log
## Run Metadata
- Date: 2026-02-10
- Started: 2026-02-10 12:36:36 CET
- Branch: `upgrade/laravel12-filament5-full`
- Goal: Laravel 12 + full ecosystem upgrades (Composer and npm), including Filament v3 -> v4 -> v5, while skipping
ESLint major upgrades.
## Preflight Snapshot
### Environment
- PHP: `8.4.17`
- Composer: `2.8.5`
- Node: `v20.19.6`
- npm: `11.6.4`
### Composer Outdated (Direct)
- Laravel major target available: `laravel/framework 11.46.1 -> 12.50.0`.
- Filament major target available: `filament/filament 3.3.45 -> 5.2.0`.
- Inertia Laravel major target available: `1.3.3 -> 2.0.19`.
- Ziggy major target available: `1.8.2 -> 2.6.0`.
- Multiple ecosystem majors available (Filament plugins, DOMPDF, Mollie, Pest stack, Symfony components, Resend).
### Laravel 12 Blocker Check (`composer why-not laravel/framework ^12 -t`)
- Hard blockers detected:
- `codezero/laravel-localized-routes:^4` (supports only Illuminate 10/11).
- `codezero/laravel-localizer:^3` (supports only Illuminate 10/11).
- `codezero/laravel-uri-translator:^2` (transitive, supports only Illuminate 10/11).
- `resend/resend-laravel:^0.14` (supports only Illuminate 10/11).
- `laravel-lang` current line (`lang:^12`, `common:^3`) anchored to publisher line that supports only Illuminate <
=11.
### npm Outdated Snapshot
- Major targets available:
- `@inertiajs/vue3 1.3.0 -> 2.3.13`
- `tailwindcss 3.4.18 -> 4.1.18`
- `laravel-vite-plugin 1.3.0 -> 2.1.0`
- `vite 6.4.1 -> 7.3.1`
- `@vitejs/plugin-vue 5.2.4 -> 6.0.4`
- `pinia 2.3.1 -> 3.0.4`
- `swiper 10.3.1 -> 12.1.0`
- `vue-i18n 10.0.8 -> 11.2.8`
- ESLint majors are available but intentionally skipped in this run.
### Baseline Verification
- `php artisan test --compact`: `14 passed`, no failures.
- Warnings: PHPUnit doc-comment metadata deprecations (will break in PHPUnit 12 timeframe).
- `npm run build`: build succeeds.
- Warning: `baseline-browser-mapping` database is older than two months.
## Package Blockers
- `codezero/laravel-localizer` removed per decision (no Laravel 12 support).
- `codezero/laravel-localized-routes` replaced by `opgginc/codezero-laravel-localized-routes` (Laravel 12 support).
- `resend/resend-laravel` must move to `^1.0`.
- `laravel-lang/lang` and `laravel-lang/common` must move to newer major lines compatible with Laravel 12.
## Exceptions & Weirdness
1. **Stale package/service manifests after Composer package changes**
- Symptom: runtime errors for removed/moved service providers (for example debugbar, old localizer, filament-upgrade
provider references).
- Failing commands: `php artisan ...` commands after `composer update --no-scripts`.
- Root cause: stale `bootstrap/cache/packages.php` and `bootstrap/cache/services.php`.
- Exact fix: delete both files and re-run `composer dump-autoload` / package discovery.
- Preventive rule: clear both manifest files after major package removals/replacements.
2. **Localized route transition edge case (`Attribute [localized] does not exist`)**
- Symptom: route boot failure during transition from abandoned CodeZero packages to OPGG fork.
- Root cause: cached package manifests + package transition timing.
- Exact fix: same manifest clear as above, then rerun discovery.
3. **Ziggy v2 client dependency gap**
- Symptom: Vite build failed with `Rollup failed to resolve import "qs-esm"` from
`vendor/tightenco/ziggy/src/js/Route.js`.
- Failing command: `npm run build`.
- Root cause: Ziggy v2 JS source now imports `qs-esm`, not present in project npm deps.
- Exact fix: `npm install qs-esm`.
- Preventive rule: after Ziggy major upgrades, run a full build immediately and verify vendor JS imports.
4. **PHPUnit 12 test discovery change reduced executed test count (14 -> 3)**
- Symptom: only methods named `test_*` executed; legacy `/** @test */` methods were skipped.
- Failing command: `php artisan test --compact` (unexpected low count).
- Root cause: metadata annotation behavior changed in PHPUnit 12 line.
- Exact fix: migrate `/** @test */` methods to `#[Test]` attributes in affected class-based tests.
- Preventive rule: include a test-count sanity check after PHPUnit major upgrades.
5. **Hidden regression surfaced after restoring full test discovery**
- Symptom: `Call to undefined function get_classes_in_namespace()` in sitemap generation test.
- Failing command: `php artisan test --compact`.
- Root cause: helper function no longer provided by dependencies after package upgrades.
- Exact fix: added local `get_classes_in_namespace()` implementation to `app/helpers.php`.
- Preventive rule: once discovery is fixed, rerun full suite and treat newly exposed failures as real upgrade
regressions.
6. **Pint interaction with PHPUnit setup visibility**
- Symptom: `setUp()` visibility conflict (`must be public`) after formatting pass.
- Failing command: `php artisan test --compact`.
- Root cause: formatter rule normalized setup method visibility in a way that conflicted with this TestCase chain.
- Exact fix: remove no-op `setUp()` overrides from affected tests.
- Preventive rule: rerun tests after Pint in upgrade branches, not just before Pint.
7. **npm package location warning due duplicate entries**
- Symptom: npm warned about removing duplicated packages from `dependencies` in favor of `devDependencies`.
- Root cause: same frontend build packages declared in both sections.
- Exact fix: npm normalized entries during install (kept in `devDependencies`).
- Preventive rule: avoid duplicating build-only packages across both dependency sections.
8. **Build warning persisted (non-blocking)**
- Symptom: `[baseline-browser-mapping] ... over two months old` and generated empty chunks in Vite output.
- Impact: build still successful.
- Action: log-only.
9. **Filament v5 codemod scope gap caused delayed runtime failures**
- Symptom: project appeared upgraded, but frontend routes later crashed with missing Filament classes in `cms/*` and
`commerce/*`.
- Root cause: initial codemod pass was executed for `app/` and `modules/` only, leaving other custom source roots
untouched.
- Exact fix: run a second migration pass over all custom roots and then verify class existence across repository
imports.
- Preventive rule: always enumerate all custom source roots before running Filament codemods (`app`, `modules`,
`cms`, `commerce`, `support`, etc.).
10. **`vendor/bin/filament-v5` can print Rector cache deletion errors but still exit successfully**
- Symptom: commands like `vendor/bin/filament-v5 cms` and `vendor/bin/filament-v5 commerce` reported
`Unable to delete ... rector_cached_files ... No such file or directory`, yet finished with success messaging.
- Impact: misleading signal that migration completed for target directory.
- Exact fix: do not trust command success text alone; run a post-codemod class-import existence scan and targeted
runtime smoke checks.
- Preventive rule: treat codemod output warnings as potential partial-failure even with zero exit code.
11. **Filament v5 namespace migration gaps (schemas vs forms)**
- Symptom: `Class "Filament\\Forms\\Components\\Fieldset" not found` on runtime page render.
- Root cause: layout/util classes moved to `Filament\\Schemas\\...` but legacy imports remained in
`cms/Filament/Form/*`.
- Exact fix: migrate imports:
- `Filament\\Forms\\Components\\Fieldset` -> `Filament\\Schemas\\Components\\Fieldset`
- `Filament\\Forms\\Components\\Grid` -> `Filament\\Schemas\\Components\\Grid`
- `Filament\\Forms\\Components\\Component` -> `Filament\\Schemas\\Components\\Component`
- `Filament\\Forms\\Get|Set` -> `Filament\\Schemas\\Components\\Utilities\\Get|Set`
- `Filament\\Forms\\Components\\Tabs\\Tab` -> `Filament\\Schemas\\Components\\Tabs\\Tab`
- `Filament\\Forms\\Components\\Section` -> `Filament\\Schemas\\Components\\Section`
- Preventive rule: after each major Filament upgrade, scan for old
`Filament\\Forms\\Components\\(Fieldset|Grid|Section|Tabs\\Tab)` and `Filament\\Forms\\Get|Set`.
12. **Filament page actions namespace changed**
- Symptom: commerce resource page classes still imported `Filament\\Pages\\Actions`.
- Root cause: missed codemod transformations in specific folders.
- Exact fix: replace with `use Filament\\Actions;` and keep `Actions\\*Action::make()` calls.
- Preventive rule: grep for `use Filament\\Pages\\Actions;` after v5 migration and replace all matches.
13. **Resource/relation manager form signature drift**
- Symptom: files still used `Filament\\Forms\\Form` type and `form(Form $form): Form` signatures.
- Root cause: partial codemod application in commerce resources.
- Exact fix: update to Filament v5 schema signature:
- Resources: `public static function form(Schema $schema): Schema`
- Relation managers: `public function form(Schema $schema): Schema`
- Preventive rule: grep for `use Filament\\Forms\\Form;` and remove all remaining occurrences.
14. **`TextInput\\Mask` builder class no longer available in current Filament line**
- Symptom: legacy closures typed as `Forms\\Components\\TextInput\\Mask` became invalid and future runtime failures were
likely.
- Root cause: legacy mask builder API from older Filament line.
- Exact fix: replaced with v5-safe numeric/step/prefix configuration instead of removed mask-builder typehints.
- Preventive rule: grep for `TextInput\\Mask` and rewrite masks using current v5 text input API.
15. **Running the official Filament upgrade utility can generate a large public-asset diff**
- Symptom: `php artisan filament:upgrade --no-interaction` republished many files under `public/js/filament/*`,
`public/css/filament/*`, and `public/fonts/filament/*`, then cleared config/route/view caches.
- Impact: noisy git diff and potential overwrite risk for locally customized published Filament assets.
- Exact fix: run this command intentionally once per upgrade pass, then review/commit asset churn separately and rerun
verification (`pint`, tests, build).
- Preventive rule: announce this side effect before execution and avoid running it repeatedly without need.
16. **`route:list` can throw due controller-constructor abort guards**
- Symptom: `php artisan route:list --path=admin` failed with
`Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException` originating from
`App\\Http\\Controllers\\Public\\VacancyController::__construct()` line 16.
- Root cause: constructor contains `abort_unless(..., 404)` and the route-list command instantiates controllers during
route metadata collection.
- Exact fix: use `php artisan about` + tests/build for upgrade smoke checks when this occurs. Long-term refactor: move
hard feature gates out of constructors into middleware/action-level guards.
- Preventive rule: avoid hard `abort_*` calls in controller constructors.
17. **npm dependency graph got stuck during ESLint major migration**
- Symptom: repeated `npm install` attempts failed with `ERESOLVE could not resolve` while moving `eslint-plugin-vue` to
`^10` and `vue-eslint-parser` to `^10`.
- Root cause: stale lock/tree state from earlier duplicated dependency declarations caused resolver deadlock.
- Exact fix: regenerate the npm install tree cleanly (fresh `node_modules` and lock regeneration), then install the
target dependency set.
- Preventive rule: when npm repeatedly fails with the same peer conflict after package.json corrections, do a clean
dependency graph rebuild instead of retry loops.
18. **`eslint-plugin-vue` v10 required ESLint flat config migration**
- Symptom: linting failed with legacy config errors (`plugin:vue/vue3-recommended` not found / invalid config shape with
`default` key).
- Root cause: newer Vue ESLint plugin line expects modern flat-config flow; legacy `.eslintrc` extends become
incompatible.
- Exact fix: add `eslint.config.cjs` and migrate rules/plugins there; keep eslint on v9.
- Preventive rule: treat ESLint plugin major upgrades as config-schema migrations, not package-only updates.
19. **Tailwind v4 upgrader refuses dirty git state by default**
- Symptom: `npx @tailwindcss/upgrade` aborted with “Git directory is not clean”.
- Exact fix: run with `--force` on controlled upgrade branches.
20. **Tailwind upgrader scanned backup directories and failed config linking**
- Symptom: upgrader reported it could not determine config for CSS files in `node_modules.bak` and
`resources/css/app.css`.
- Root cause: backup dependency folder was inside repository tree and stylesheet had no explicit config link.
- Exact fix: move backup folder outside repo and run upgrader with `-c tailwind.config.js`.
21. **Tailwind upgrader failed to load custom config due legacy helper signature**
- Symptom: `Cannot destructure property 'negative' of 'undefined'`.
- Root cause: custom `inset: (theme, { negative }) => ...` pattern from older Tailwind config API.
- Exact fix: replace with explicit negative-spacing generation helper that only depends on `theme('spacing')`.
22. **Tailwind template migration failed on `@apply` of custom class**
- Symptom: `Cannot apply unknown utility class 'pl-container'`.
- Root cause: `@apply` referenced custom class aliases instead of raw utilities in `resources/css/utility.css`.
- Exact fix: expand `.container` rule to raw utility set (`pl-8 lg:pl-32 pr-8 lg:pr-32`) and rerun upgrader.
23. **Tailwind v4 PostCSS plugin package split**
- Symptom: Vite build failed with “trying to use `tailwindcss` directly as a PostCSS plugin”.
- Root cause: Tailwind v4 moved PostCSS integration to `@tailwindcss/postcss`.
- Exact fix: install `@tailwindcss/postcss` and update `postcss.config.js` plugin key.
24. **`eslint-plugin-tailwindcss` is not Tailwind v4 compatible**
- Symptom: linting failed with `ERR_PACKAGE_PATH_NOT_EXPORTED` for `tailwindcss/resolveConfig`.
- Root cause: current plugin line peers against Tailwind `^3.4.0` only.
- Exact fix: remove `eslint-plugin-tailwindcss` integration from active lint config during Tailwind v4 migration.
25. **Clean npm install exposed missing direct dependency (`lodash`)**
- Symptom: Vite build failed resolving `lodash` imported by `resources/js/utilities.js`.
- Root cause: package was previously present transitively in old lock state but not declared directly.
- Exact fix: add `lodash` as direct dependency.
- Preventive rule: after lock regeneration, treat unresolved imports as missing direct dependency declarations and fix
explicitly.
26. **Filament schema traversal can access uninitialized component container**
- Symptom: runtime crash
`Typed property Filament\\Schemas\\Components\\Component::$container must not be accessed before initialization` while
resolving block schema metadata.
- Root cause: direct child-component traversal (`getChildComponents()`/`getName()`) can touch component internals before
Livewire/Filament container initialization.
- Exact fix: in `cms/Filament/Blocks/Block.php`, resolve protected `childComponents` via reflection, treat closure child
schemas as empty in non-Livewire context, and guard `getName()` with `Throwable` fallback to recursive traversal.
- Preventive rule: when extracting schema metadata outside a mounted form lifecycle, avoid direct `getChildComponents()`
assumptions and handle closure-based schema definitions safely.
## Command Transcript (Key Commands)
1. `COMPOSER_CACHE_DIR=/tmp/composer-cache composer outdated --direct --format=json`
2. `composer why-not laravel/framework ^12 -t`
3. Multiple Composer major updates (Laravel/Filament ecosystem, plugin replacements, package removals)
4. `npm outdated --json`
5. `npm update`
6.
`npm install @inertiajs/vue3@^2 @vitejs/plugin-vue@^6 laravel-vite-plugin@^2 vite@^7 @vueuse/core@^14 swiper@^12 vue-i18n@^11 pinia@^3 imagetools-core@^9 vite-imagetools@^9 @formkit/auto-animate@^0.9 @gtm-support/vue-gtm@^3`
7. `npm install uuid@^13`
8. `npm install qs-esm`
9. `php artisan test --compact`
10. `vendor/bin/pint --dirty`
11. `npm run build`
12. `vendor/bin/filament-v5 cms`
13. `vendor/bin/filament-v5 commerce`
14. `vendor/bin/filament-v5 support`
15. Repository-wide Filament import existence scan against Composer autoload
16. `php artisan tinker --execute="Cms\\Filament\\Form\\Fieldset\\ImageForBlock::make('image'); echo 'ok';"`
17. `php artisan filament:upgrade --no-interaction`
18. `vendor/bin/pint --dirty`
19. `php artisan test --compact`
20. `npm run build`
21. `composer outdated --direct --format=json`
22. `npm outdated --json`
23. `php artisan route:list --path=admin` (fails due constructor abort; see exception #16)
24. `php artisan about`
25.
`npm pkg delete dependencies.@typescript-eslint/eslint-plugin dependencies.eslint-plugin-unused-imports dependencies.eslint-plugin-vue`
26. npm dependency graph regeneration for ESLint package upgrades
27. Added flat ESLint config (`eslint.config.cjs`) and migrated rules from `.eslintrc.json`
28. `npx @tailwindcss/upgrade --force -c tailwind.config.js`
29. `npm install -D @tailwindcss/postcss`
30. `npm install lodash`
31. Verification reruns: `npx eslint ...`, `npm run build`, `php artisan test --compact`,
`composer outdated --direct --format=json`, `npm outdated --json`
32. `php artisan test tests/Feature/Cms/Blocks/BlockSchemaTest.php --compact`
33. Added regression coverage in `tests/Feature/Cms/Blocks/BlockSchemaTest.php`
## npm Final State
### Upgraded majors completed in this pass
- `@inertiajs/vue3` -> `^2.3.13`
- `@vitejs/plugin-vue` -> `^6.0.4`
- `laravel-vite-plugin` -> `^2.1.0`
- `vite` -> `^7.3.1`
- `@vueuse/core` -> `^14.2.1`
- `pinia` -> `^3.0.4`
- `swiper` -> `^12.1.0`
- `vue-i18n` -> `^11.2.8`
- `imagetools-core` -> `^9.1.0`
- `vite-imagetools` -> `^9.0.2`
- `@formkit/auto-animate` -> `^0.9.0`
- `@gtm-support/vue-gtm` -> `^3.1.0`
- `uuid` -> `^13.0.0`
- `qs-esm` added for Ziggy v2 compatibility
- `tailwindcss` -> `^4.1.18`
- `@tailwindcss/postcss` added for Tailwind v4 PostCSS integration
- `@typescript-eslint/eslint-plugin` -> `^8.55.0`
- `@typescript-eslint/parser` -> `^8.55.0`
- `eslint-plugin-unused-imports` -> `^4.4.1`
- `eslint-plugin-vue` -> `^10.7.0`
- ESLint migrated to flat config with `eslint` `^9.39.2`
- `lodash` added as explicit direct dependency
### Remaining npm outdated entries
- `eslint` `9.39.2 -> 10.0.0` (left intentionally due current `@typescript-eslint` v8 peer range support targeting
eslint 8/9)
## Post-Upgrade Verification
- `composer outdated --direct --format=json`: no direct outdated packages (`"installed": []`).
- `php artisan test --compact`: `16 passed (28 assertions)` (includes new block schema regression coverage).
- `npm run build`: successful client + SSR build on Vite 7.
- `vendor/bin/pint --dirty`: passes after fixups.
- `php artisan filament:upgrade --no-interaction`: succeeds and republishes Filament assets/caches.
- `php artisan route:list --path=admin`: fails because a constructor-level abort is triggered (documented in exception
#16).
- `npx eslint resources/js/app.js resources/js/ssr.js`: passes with flat ESLint config.
## Historical Lessons (From Earlier Upgrade Logs)
- Source consulted: `UPGRADE-LOG-CLEANSHOPPING.md`.
- Stale `bootstrap/cache/packages.php` and `bootstrap/cache/services.php` can retain removed service providers after
package changes.
- Filament major upgrades can require running upgrade tooling for custom roots (`app/` and `modules/`).
- Filament upgrade scripts may miss manual fixes; always run targeted grep and tests after codemods.
- Tailwind major migrations can fail with advanced config/plugin patterns; run as a dedicated pass.
- Filament codemod success output is not sufficient validation: cross-check every custom code root and run class-import
existence scans.
## Reusable Rules
- Keep this file as the canonical `UPDATE_LOG` for each future upgrade pass.
- For each exception, always record:
- Symptom
- Failing command
- Root cause
- Exact fix
- Preventive rule
## Pending TODOs
1. **Migrate remaining PHPUnit-style tests to idiomatic Pest tests**
- Reference guide: `https://pestphp.com/docs/migrating-from-phpunit-guide`
- Scope: convert class-based PHPUnit style tests (`tests/Feature/*`, `tests/Unit/*`) to Pest `it()` style where
practical.
- Keep/validate safety guard before every migration test run: `tests/TestCase.php` must enforce in-memory sqlite (
`database.default=sqlite` and `database.connections.sqlite.database=:memory:`) to prevent accidental writes to
non-test databases.
## Session Notes — 2026-02-10 (Browser Testing Bootstrap)
1. **Composer dev-package install can rerun Filament upgrade hooks**
- Symptom: installing browser-test dependencies triggered `php artisan filament:upgrade` via Composer scripts,
republishing Filament assets and clearing caches.
- Failing command: `composer require pestphp/pest-plugin-browser --dev --no-interaction`.
- Root cause: project-level `post-update-cmd` in `composer.json` always runs `@php artisan filament:upgrade`.
- Exact fix: treat as expected side effect and avoid repeated Composer writes when not needed.
- Preventive rule: on upgrade branches, assume Composer package changes may create large Filament asset diffs.
2. **Pest browser tests need Playwright runtime setup in addition to npm package install**
- Symptom: browser tests can fail on clean machines if Playwright browser binaries are not installed.
- Failing command: `./vendor/bin/pest tests/Browser/...` before Playwright install.
- Root cause: installing `playwright` npm package does not guarantee browser binaries are present.
- Exact fix: run `npx playwright install` (or `npx playwright install chromium`) after dependency install.
- Preventive rule: include Playwright browser install in local + CI bootstrap for browser test suites.
3. **Pest v4 browser tests conflict with `public setUp()` in custom TestCase**
- Symptom: fatal error
`Access level to Pest\Concerns\Testable::setUp() must be public (as in class Tests\TestCase)`.
- Failing command: `./vendor/bin/pest tests/Browser/PageBrowserTest.php`.
- Root cause: `tests/TestCase.php` used `public function setUp()`, which conflicts with Pest's trait method
visibility expectations.
- Exact fix: change `tests/TestCase.php` `setUp()` visibility to `protected`.
- Preventive rule: keep custom `setUp()` visibility aligned with Laravel/PHPUnit defaults (`protected`) for Pest
compatibility.
4. **Browser title assertions can be empty on Inertia pages during early render**
- Symptom: browser test failed with `Expected page title ... but found ''` on a valid Inertia route.
- Failing command: `./vendor/bin/pest tests/Browser/PageBrowserTest.php`.
- Root cause: in this stack, `<title>` can be empty at initial render timing while the page payload is already
present.
- Exact fix: assert against rendered source payload (`assertSourceHas(...)`) plus smoke assertions instead of strict
immediate title assertion.
- Preventive rule: for Inertia browser tests, prefer stable assertions (`assertSourceHas`, `assertSee`,
`assertNoSmoke`) over immediate `<title>` checks unless waiting for a client-side update.
5. **Block settings detection can still trigger uninitialized Filament container access**
- Symptom: browser request crashed with
`Typed property Filament\\Schemas\\Components\\Component::$container must not be accessed before initialization`.
- Failing path: `Cms\\Filament\\Blocks\\Block::hasSettings()` calling `getSettings()->getChildComponents()` during
content arraying.
- Root cause: `hasSettings()` used a direct child-component accessor that requires a mounted container, even in
non-Livewire rendering.
- Exact fix: change `hasSettings()` to use the safe `getSchemaChildComponents()` resolver and treat closure schemas
as non-resolvable (`false`) in this context.
- Preventive rule: in backend-only schema introspection, avoid direct `getChildComponents()` calls and rely on
guarded reflection/fallback accessors.
6. **Pest browser `assertSee()` can fail on non-SSR Inertia pages**
- Symptom: browser test did not find seeded text as visible DOM text even though the route and payload were correct.
- Failing assertion: `assertSee('Browser Seed Content')`.
- Root cause: this page flow is client-rendered Inertia; text is present in response payload/source before
hydration, not necessarily as immediate visible server-rendered DOM.
- Exact fix: use `assertSourceHas(...)` for payload-level assertions plus route/smoke assertions.
- Preventive rule: for non-SSR Inertia browser tests, prefer `assertSourceHas` over immediate `assertSee` unless
explicitly waiting for frontend hydration.
## Session Notes — 2026-02-10 (Remaining Update Pass)
1. **Final direct Composer updates are complete**
- Action: upgraded remaining direct outdated packages (`filament/filament`,
`filament/spatie-laravel-media-library-plugin`, `filament/spatie-laravel-settings-plugin`, `filament/upgrade`)
from `5.2.0` to `5.2.1`.
- Verification: `composer outdated --direct --format=json` now returns `"installed": []`.
2. **Composer update still triggers Filament asset republish side effects**
- Symptom: `composer update ...` executed `post-update-cmd` and reran `php artisan filament:upgrade`, republishing
`public/js/filament/*`, `public/css/filament/*`, and font assets.
- Impact: expected large public asset churn in git diff.
- Preventive rule: keep Filament public-asset changes grouped and reviewed separately when running Composer updates.
3. **npm is fully up to date except ESLint major**
- Action: ran `npm update` and refreshed lockfile.
- Result: only `eslint` remains in `npm outdated` (`9.39.2 -> 10.0.0`).
- Decision: keep on ESLint 9 for this pass to avoid forced major migration risk while finishing framework/package
updates.
4. **Build + tests remain green after patch pass**
- `npm run build`: successful (client + SSR).
- `php artisan test --compact`: `16 passed (28 assertions)`.
- `./vendor/bin/pest tests/Browser/PageBrowserTest.php`: passes.
- `php artisan test tests/Feature/Cms/Blocks/BlockSchemaTest.php --compact`: passes.
- `vendor/bin/pint --dirty`: passes.
5. **Tailwind v4 can silently reintroduce core `.container` and override custom layout containers**
- Symptom: major frontend layout degradation after upgrade (page spacing/width looked "missing CSS" across many
pages).
- Root cause: Tailwind v4 no longer supports `corePlugins.container = false` from JS config in active CSS-first
mode; the core `.container` utility was generated and overrode project custom `.container`.
- Exact fix: in `resources/css/app.css`, add `@source not inline('container');` (and
`@source not inline('container!');`) to prevent Tailwind from generating the core container utility, keeping the
custom `.container` from `resources/css/utility.css` as the only definition.
- Verification: built CSS now contains only the custom `.container` rule (`grep -o '\\.container{' ... | wc -l` =
`2` from base + media rule), browser + schema tests pass.
- Preventive rule: after Tailwind major upgrades, always diff/check compiled `.container` output when projects
define their own container class name.
6. **Tailwind v4 does not require `tailwind.config.js`, but compatibility file can remain accidentally**
- Symptom: repository still had `tailwind.config.js` after migration, creating ambiguity about active config source.
- Root cause: Tailwind v4 uses CSS-first config; JS config is compatibility-only and not auto-loaded unless
`@config` is explicitly used.
- Exact fix: remove unused `tailwind.config.js` when fully migrated to CSS directives and no tooling references
remain.
- Preventive rule: after v4 migration, run a repo-wide search for `@config` and `tailwind.config.js` references; if
none exist, remove stale config files to avoid future confusion.
## Tailwind Upgrade Workflow Directive (Authoritative)
1. **Always run the Tailwind upgrade utility first**
- Required default path: use the official updater before doing manual migrations.
2. **If the upgrade utility errors, resolve blockers and rerun the utility**
- Do not switch to a manual full migration as first fallback.
- Iterate: fix error -> rerun updater -> repeat until updater succeeds.
3. **If custom classes block the updater, temporarily comment them out**
- Typical case: custom utility/class patterns causing updater parse/apply failures.
- Workflow: comment problematic custom classes -> run updater -> reapply custom classes after successful upgrade.
4. **Manual edits are only for unblocking/reapplying around the updater**
- Manual work should support the upgrader workflow, not replace it.
5. **Unpack all custom classes before running Tailwind upgrades**
- Rule: do not keep "custom class inside custom class" patterns (for example, a custom class that `@apply`s another
custom class).
- Required approach: expand nested custom-class usage to raw utility classes first, then run the upgrader.
- After upgrade: keep this flattened structure; avoid reintroducing custom-class-in-custom-class composition.
- Verification snapshot (2026-02-10): repository-wide `@apply` scan found only raw utility usage in
`resources/css/app.css`; no custom-class tokens were referenced inside other custom classes.
6. **Blade-injected runtime colors require `@theme inline` in Tailwind v4**
- Symptom: color utilities tied to runtime CSS variables from `resources/views/app.blade.php` behaved inconsistently
after migration.
- Root cause: theme tokens in `resources/css/app.css` referenced runtime vars (`var(--primary-color)` etc.) using
plain `@theme` instead of v4's recommended `@theme inline` for variable references.
- Exact fix: switch to `@theme inline` for color tokens and ensure every referenced token exists.
- Verification snapshot (2026-02-10): compiled CSS now emits `.bg-primary{background-color:var(--primary-color)}`
and `.from-primary-medium{...var(--primary-color)...}`.
- Preventive rule: when colors are injected at runtime (Blade/style attributes), define Tailwind color tokens with
`@theme inline`.
7. **Missing theme token prevents utility generation (`from-primary-medium`)**
- Symptom: `from-primary-medium` class in `resources/js/Components/Header.vue` had no compiled CSS.
- Root cause: no `--color-primary-medium` token existed in the Tailwind theme namespace.
- Exact fix: add `--color-primary-medium` mapping in the `@theme inline` block (`resources/css/app.css`).
- Preventive rule: after migration, grep for custom color utility names and verify each has a corresponding
`--color-*` theme variable.