30 KiB
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-langcurrent 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.13tailwindcss 3.4.18 -> 4.1.18laravel-vite-plugin 1.3.0 -> 2.1.0vite 6.4.1 -> 7.3.1@vitejs/plugin-vue 5.2.4 -> 6.0.4pinia 2.3.1 -> 3.0.4swiper 10.3.1 -> 12.1.0vue-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-mappingdatabase is older than two months.
Package Blockers
codezero/laravel-localizerremoved per decision (no Laravel 12 support).codezero/laravel-localized-routesreplaced byopgginc/codezero-laravel-localized-routes(Laravel 12 support).resend/resend-laravelmust move to^1.0.laravel-lang/langandlaravel-lang/commonmust move to newer major lines compatible with Laravel 12.
Exceptions & Weirdness
-
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 aftercomposer update --no-scripts. - Root cause: stale
bootstrap/cache/packages.phpandbootstrap/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.
-
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.
-
Ziggy v2 client dependency gap
- Symptom: Vite build failed with
Rollup failed to resolve import "qs-esm"fromvendor/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.
- Symptom: Vite build failed with
-
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.
- Symptom: only methods named
-
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 toapp/helpers.php. - Preventive rule: once discovery is fixed, rerun full suite and treat newly exposed failures as real upgrade regressions.
- Symptom:
-
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.
- Symptom:
-
npm package location warning due duplicate entries
- Symptom: npm warned about removing duplicated packages from
dependenciesin favor ofdevDependencies. - 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.
- Symptom: npm warned about removing duplicated packages from
-
Build warning persisted (non-blocking)
- Symptom:
[baseline-browser-mapping] ... over two months oldand generated empty chunks in Vite output. - Impact: build still successful.
- Action: log-only.
- Symptom:
-
Filament v5 codemod scope gap caused delayed runtime failures
- Symptom: project appeared upgraded, but frontend routes later crashed with missing Filament classes in
cms/*andcommerce/*. - Root cause: initial codemod pass was executed for
app/andmodules/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.).
- Symptom: project appeared upgraded, but frontend routes later crashed with missing Filament classes in
-
vendor/bin/filament-v5can print Rector cache deletion errors but still exit successfully
- Symptom: commands like
vendor/bin/filament-v5 cmsandvendor/bin/filament-v5 commercereportedUnable 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.
- Filament v5 namespace migration gaps (schemas vs forms)
- Symptom:
Class "Filament\\Forms\\Components\\Fieldset" not foundon runtime page render. - Root cause: layout/util classes moved to
Filament\\Schemas\\...but legacy imports remained incms/Filament/Form/*. - Exact fix: migrate imports:
Filament\\Forms\\Components\\Fieldset->Filament\\Schemas\\Components\\FieldsetFilament\\Forms\\Components\\Grid->Filament\\Schemas\\Components\\GridFilament\\Forms\\Components\\Component->Filament\\Schemas\\Components\\ComponentFilament\\Forms\\Get|Set->Filament\\Schemas\\Components\\Utilities\\Get|SetFilament\\Forms\\Components\\Tabs\\Tab->Filament\\Schemas\\Components\\Tabs\\TabFilament\\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)andFilament\\Forms\\Get|Set.
- 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 keepActions\\*Action::make()calls. - Preventive rule: grep for
use Filament\\Pages\\Actions;after v5 migration and replace all matches.
- Resource/relation manager form signature drift
- Symptom: files still used
Filament\\Forms\\Formtype andform(Form $form): Formsignatures. - 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
- Resources:
- Preventive rule: grep for
use Filament\\Forms\\Form;and remove all remaining occurrences.
TextInput\\Maskbuilder class no longer available in current Filament line
- Symptom: legacy closures typed as
Forms\\Components\\TextInput\\Maskbecame 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\\Maskand rewrite masks using current v5 text input API.
- Running the official Filament upgrade utility can generate a large public-asset diff
- Symptom:
php artisan filament:upgrade --no-interactionrepublished many files underpublic/js/filament/*,public/css/filament/*, andpublic/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.
route:listcan throw due controller-constructor abort guards
- Symptom:
php artisan route:list --path=adminfailed withSymfony\\Component\\HttpKernel\\Exception\\NotFoundHttpExceptionoriginating fromApp\\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.
- npm dependency graph got stuck during ESLint major migration
- Symptom: repeated
npm installattempts failed withERESOLVE could not resolvewhile movingeslint-plugin-vueto^10andvue-eslint-parserto^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_modulesand 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.
eslint-plugin-vuev10 required ESLint flat config migration
- Symptom: linting failed with legacy config errors (
plugin:vue/vue3-recommendednot found / invalid config shape withdefaultkey). - Root cause: newer Vue ESLint plugin line expects modern flat-config flow; legacy
.eslintrcextends become incompatible. - Exact fix: add
eslint.config.cjsand migrate rules/plugins there; keep eslint on v9. - Preventive rule: treat ESLint plugin major upgrades as config-schema migrations, not package-only updates.
- Tailwind v4 upgrader refuses dirty git state by default
- Symptom:
npx @tailwindcss/upgradeaborted with “Git directory is not clean”. - Exact fix: run with
--forceon controlled upgrade branches.
- Tailwind upgrader scanned backup directories and failed config linking
- Symptom: upgrader reported it could not determine config for CSS files in
node_modules.bakandresources/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.
- 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').
- Tailwind template migration failed on
@applyof custom class
- Symptom:
Cannot apply unknown utility class 'pl-container'. - Root cause:
@applyreferenced custom class aliases instead of raw utilities inresources/css/utility.css. - Exact fix: expand
.containerrule to raw utility set (pl-8 lg:pl-32 pr-8 lg:pr-32) and rerun upgrader.
- Tailwind v4 PostCSS plugin package split
- Symptom: Vite build failed with “trying to use
tailwindcssdirectly as a PostCSS plugin”. - Root cause: Tailwind v4 moved PostCSS integration to
@tailwindcss/postcss. - Exact fix: install
@tailwindcss/postcssand updatepostcss.config.jsplugin key.
eslint-plugin-tailwindcssis not Tailwind v4 compatible
- Symptom: linting failed with
ERR_PACKAGE_PATH_NOT_EXPORTEDfortailwindcss/resolveConfig. - Root cause: current plugin line peers against Tailwind
^3.4.0only. - Exact fix: remove
eslint-plugin-tailwindcssintegration from active lint config during Tailwind v4 migration.
- Clean npm install exposed missing direct dependency (
lodash)
- Symptom: Vite build failed resolving
lodashimported byresources/js/utilities.js. - Root cause: package was previously present transitively in old lock state but not declared directly.
- Exact fix: add
lodashas direct dependency. - Preventive rule: after lock regeneration, treat unresolved imports as missing direct dependency declarations and fix explicitly.
- Filament schema traversal can access uninitialized component container
- Symptom: runtime crash
Typed property Filament\\Schemas\\Components\\Component::$container must not be accessed before initializationwhile 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 protectedchildComponentsvia reflection, treat closure child schemas as empty in non-Livewire context, and guardgetName()withThrowablefallback 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)
COMPOSER_CACHE_DIR=/tmp/composer-cache composer outdated --direct --format=jsoncomposer why-not laravel/framework ^12 -t- Multiple Composer major updates (Laravel/Filament ecosystem, plugin replacements, package removals)
npm outdated --jsonnpm update
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
npm install uuid@^13npm install qs-esmphp artisan test --compactvendor/bin/pint --dirtynpm run buildvendor/bin/filament-v5 cmsvendor/bin/filament-v5 commercevendor/bin/filament-v5 support- Repository-wide Filament import existence scan against Composer autoload
php artisan tinker --execute="Cms\\Filament\\Form\\Fieldset\\ImageForBlock::make('image'); echo 'ok';"php artisan filament:upgrade --no-interactionvendor/bin/pint --dirtyphp artisan test --compactnpm run buildcomposer outdated --direct --format=jsonnpm outdated --jsonphp artisan route:list --path=admin(fails due constructor abort; see exception #16)php artisan about
npm pkg delete dependencies.@typescript-eslint/eslint-plugin dependencies.eslint-plugin-unused-imports dependencies.eslint-plugin-vue
- npm dependency graph regeneration for ESLint package upgrades
- Added flat ESLint config (
eslint.config.cjs) and migrated rules from.eslintrc.json npx @tailwindcss/upgrade --force -c tailwind.config.jsnpm install -D @tailwindcss/postcssnpm install lodash- Verification reruns:
npx eslint ...,npm run build,php artisan test --compact,composer outdated --direct --format=json,npm outdated --json php artisan test tests/Feature/Cms/Blocks/BlockSchemaTest.php --compact- 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.4laravel-vite-plugin->^2.1.0vite->^7.3.1@vueuse/core->^14.2.1pinia->^3.0.4swiper->^12.1.0vue-i18n->^11.2.8imagetools-core->^9.1.0vite-imagetools->^9.0.2@formkit/auto-animate->^0.9.0@gtm-support/vue-gtm->^3.1.0uuid->^13.0.0qs-esmadded for Ziggy v2 compatibilitytailwindcss->^4.1.18@tailwindcss/postcssadded for Tailwind v4 PostCSS integration@typescript-eslint/eslint-plugin->^8.55.0@typescript-eslint/parser->^8.55.0eslint-plugin-unused-imports->^4.4.1eslint-plugin-vue->^10.7.0- ESLint migrated to flat config with
eslint^9.39.2 lodashadded as explicit direct dependency
Remaining npm outdated entries
eslint9.39.2 -> 10.0.0(left intentionally due current@typescript-eslintv8 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.phpandbootstrap/cache/services.phpcan retain removed service providers after package changes. - Filament major upgrades can require running upgrade tooling for custom roots (
app/andmodules/). - 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_LOGfor each future upgrade pass. - For each exception, always record:
- Symptom
- Failing command
- Root cause
- Exact fix
- Preventive rule
Pending TODOs
- 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 Pestit()style where practical. - Keep/validate safety guard before every migration test run:
tests/TestCase.phpmust enforce in-memory sqlite (database.default=sqliteanddatabase.connections.sqlite.database=:memory:) to prevent accidental writes to non-test databases.
- Reference guide:
Session Notes — 2026-02-10 (Browser Testing Bootstrap)
-
Composer dev-package install can rerun Filament upgrade hooks
- Symptom: installing browser-test dependencies triggered
php artisan filament:upgradevia 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-cmdincomposer.jsonalways 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.
- Symptom: installing browser-test dependencies triggered
-
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
playwrightnpm package does not guarantee browser binaries are present. - Exact fix: run
npx playwright install(ornpx playwright install chromium) after dependency install. - Preventive rule: include Playwright browser install in local + CI bootstrap for browser test suites.
-
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.phpusedpublic function setUp(), which conflicts with Pest's trait method visibility expectations. - Exact fix: change
tests/TestCase.phpsetUp()visibility toprotected. - Preventive rule: keep custom
setUp()visibility aligned with Laravel/PHPUnit defaults (protected) for Pest compatibility.
- Symptom: fatal error
-
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.
- Symptom: browser test failed with
-
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()callinggetSettings()->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 safegetSchemaChildComponents()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.
- Symptom: browser request crashed with
-
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
assertSourceHasover immediateassertSeeunless explicitly waiting for frontend hydration.
Session Notes — 2026-02-10 (Remaining Update Pass)
-
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) from5.2.0to5.2.1. - Verification:
composer outdated --direct --format=jsonnow returns"installed": [].
- Action: upgraded remaining direct outdated packages (
-
Composer update still triggers Filament asset republish side effects
- Symptom:
composer update ...executedpost-update-cmdand reranphp artisan filament:upgrade, republishingpublic/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.
- Symptom:
-
npm is fully up to date except ESLint major
- Action: ran
npm updateand refreshed lockfile. - Result: only
eslintremains innpm 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.
- Action: ran
-
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.
-
Tailwind v4 can silently reintroduce core
.containerand 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 = falsefrom JS config in active CSS-first mode; the core.containerutility 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.containerfromresources/css/utility.cssas the only definition. - Verification: built CSS now contains only the custom
.containerrule (grep -o '\\.container{' ... | wc -l=2from base + media rule), browser + schema tests pass. - Preventive rule: after Tailwind major upgrades, always diff/check compiled
.containeroutput when projects define their own container class name.
-
Tailwind v4 does not require
tailwind.config.js, but compatibility file can remain accidentally- Symptom: repository still had
tailwind.config.jsafter 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
@configis explicitly used. - Exact fix: remove unused
tailwind.config.jswhen fully migrated to CSS directives and no tooling references remain. - Preventive rule: after v4 migration, run a repo-wide search for
@configandtailwind.config.jsreferences; if none exist, remove stale config files to avoid future confusion.
- Symptom: repository still had
Tailwind Upgrade Workflow Directive (Authoritative)
-
Always run the Tailwind upgrade utility first
- Required default path: use the official updater before doing manual migrations.
-
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.
-
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.
-
Manual edits are only for unblocking/reapplying around the updater
- Manual work should support the upgrader workflow, not replace it.
-
Unpack all custom classes before running Tailwind upgrades
- Rule: do not keep "custom class inside custom class" patterns (for example, a custom class that
@applys 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
@applyscan found only raw utility usage inresources/css/app.css; no custom-class tokens were referenced inside other custom classes.
- Rule: do not keep "custom class inside custom class" patterns (for example, a custom class that
-
Blade-injected runtime colors require
@theme inlinein Tailwind v4- Symptom: color utilities tied to runtime CSS variables from
resources/views/app.blade.phpbehaved inconsistently after migration. - Root cause: theme tokens in
resources/css/app.cssreferenced runtime vars (var(--primary-color)etc.) using plain@themeinstead of v4's recommended@theme inlinefor variable references. - Exact fix: switch to
@theme inlinefor 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.
- Symptom: color utilities tied to runtime CSS variables from
-
Missing theme token prevents utility generation (
from-primary-medium)- Symptom:
from-primary-mediumclass inresources/js/Components/Header.vuehad no compiled CSS. - Root cause: no
--color-primary-mediumtoken existed in the Tailwind theme namespace. - Exact fix: add
--color-primary-mediummapping in the@theme inlineblock (resources/css/app.css). - Preventive rule: after migration, grep for custom color utility names and verify each has a corresponding
--color-*theme variable.
- Symptom: