Traduction indisponible
Cette page n’existe pas encore en français. Nous affichons la version anglais pour éviter une page cassée.
NEW_STRUCTURE — Webkernel is PHP. Laravel is the panel host.
Status: working note (not an ADD). Date: 2026-08-19 Owner: Yassine If this becomes law: promote a slim version into software/codebase/docs/decisions/add/. Do not let this file become a second constitution.
This file exists because the frustration is real, the impulse to “take Laravel + Livewire + Filament + Alpine and rebuild Webkernel from scratch” is understandable, and doing that as a rewrite would destroy the product you already have.
The job here is to name what you actually own, what you actually rent, what the 11 000 files really are, and which new structure gives you control without spending two years reimplementing Illuminate poorly.
0. The sentence that upset you
Taylor Otwell, 2026-08-19, on X:
PHP is an implementation detail of Laravel.
For Laravel Inc. that sentence is a product claim: the brand is Laravel, the language is interchangeable.
For Webkernel that sentence is inverted and wrong.
| Laravel Inc. | Webkernel (ADD-0002, ADD-0004) |
|---|---|
|
Product = Laravel
|
Product = a sovereign PHP platform operators can own
|
|
PHP is a detail
|
PHP is the foundation
|
|
Filament/Livewire are the ecosystem
|
Filament/Livewire are the operator UI
|
|
Apps are Laravel apps
|
Hosts are Webkernel hosts that currently boot through Illuminate
|
You already named the application Webkernel in config/app.php. You already subclass Illuminate\Foundation\Application as Webkernel\WebApp. You already expose webapp() instead of auth(). You already 304 public markdown before bootstrap/app.php.
The identity is half-extracted. The boot path is not. That mismatch is what drives you crazy — not the absence of a kernel.
Target sentence (the one this structure is for):
Laravel is an implementation detail of the operator plane. PHP is the implementation of Webkernel.
Not: “rewrite Laravel.” Not: “stay Laravel forever.” This: three planes, one product.
1. What Claude got right, and what it missed
Claude’s method is correct: inventory first, then decide. Claude’s options table is generic and does not describe this tree.
Right
- This is an architectural-clarity crisis, not a mood.
- PHP is not a detail here. Laravel is the tool.
- The useful question is ally vs enemy inside Laravel, not “leave Laravel tonight.”
Wrong or incomplete on this repo
| Claude said | This tree actually |
|---|---|
|
No native ETag
|
Public MD already has ETag + 304 before Laravel boot (public/index.php → md_http_revalidate.php). Lifecycle assets also send ETag (Webkernel\Paths\Assets).
|
|
11k files loaded for no reason
|
node_modules has 11 046 files. Composer has 201 packages. third_party has 20 341 PHP files. Your software/ has ~715 PHP files. Disk ≠ runtime.
|
|
Hello world is slow because Laravel is heavy
|
Two different hello worlds. Native PHP + OPcache (your number) is 0.002 ms / 14.1 MB / 90.9% hit. HTTP GET / without ETag is ~1.5 s and still X-Md-Http-Cache: RENDER via DynamicMdPage. Do not mix those.
|
|
Options: prune / Octane / Symfony / PHP-PSR / rewrite
|
Octane is already a codebase dependency. You already have a PHP-native router (Webkernel\Paths\Router) and a PHP-native 304 path. Symfony is already inside Laravel (HttpFoundation, Console, Process). A from-scratch kernel is not option 5 — it is what you started and did not finish.
|
Do not run Claude’s “Étape 2 questionnaire” as if the answers were unknown. They are in the tree. See §4.
2. Measured inventory (this host, 2026-08-19)
Numbers from this working copy. Re-measure before any prune PR.
Disk
| Tree | Size | Files |
|---|---|---|
|
third_party/ (Composer vendor-dir)
|
358 M
|
20 341 *.php
|
|
node_modules/
|
141 M
|
11 046 files ← the “11k”
|
|
software/ (first-party PHP you write)
|
52 M (incl. 6400 imagery SVGs)
|
~715 *.php
|
Composer
| Kind | Count |
|---|---|
|
Installed packages (composer show)
|
201
|
|
Discovered Laravel packages (bootstrap/cache/packages.php)
|
38
|
|
Host require (production identity)
|
webkernel/{codebase,i18n,imagery,onboarding,plugin-mdsite} + numerimondes/{bookstore-manager,numerimondes-website}
|
|
Host require-dev
|
pest, pint, rector, boost, pail, pao, faker, mockery, collision, phpstorm-stubs, pretty-routes
|
Host composer.json does not require laravel/framework or filament/filament directly. Those enter through webkernel/codebase (and then every other package repeats them).
Heaviest third_party/ (disk)
| Path | Size | Mostly |
|---|---|---|
|
jetbrains/
|
113 M
|
dev stubs
|
|
phpstan/
|
48 M
|
dev
|
|
laravel/
|
41 M
|
framework + octane + …
|
|
filament/
|
39 M / 4 623 PHP
|
operator UI
|
|
rector/
|
25 M / 3 151 PHP
|
dev
|
|
livewire/
|
12 M / 355 PHP
|
panel runtime
|
|
fakerphp/
|
12 M
|
dev
|
|
symfony/
|
9.8 M / 1 213 PHP
|
HttpFoundation, Console, Process, …
|
|
phpunit/
|
8.8 M / 1 311 PHP
|
dev
|
|
nesbot/
|
5.8 M / 921 PHP
|
Carbon + locale data
|
|
blade-ui-kit/
|
5.3 M
|
Heroicons
|
Roughly half of vendor disk is tools you do not boot on a public GET (JetBrains stubs, PHPStan, Rector, PHPUnit, Pest, Faker). The other half is the real tax: Filament + Laravel + Livewire + Carbon + Symfony.
Discovered providers that boot with the app
From bootstrap/cache/packages.php (abridged into jobs):
| Job | Packages |
|---|---|
|
Operator UI
|
filament/* (10 packages), livewire, livewire/blaze, blade-heroicons, blade-icons, blade-capture-directive
|
|
Auth / ACL
|
laravel/sanctum, spatie/laravel-permission, bezhansalleh/filament-shield, filament-plugin-essentials
|
|
Host / platform
|
webkernel/codebase (empty providers array — registered from WebApp::configure), i18n, imagery, onboarding, plugin-mdsite (including CmsPanelProvider)
|
|
Modules
|
numerimondes/bookstore-manager (4 providers), numerimondes/numerimondes-website (3 providers)
|
|
Infra already present
|
laravel/octane, carbon
|
|
Dev leaking into discover
|
laravel/boost, laravel/mcp, laravel/pail, laravel/pao, laravel/roster, laravel/tinker, collision, pretty-routes
|
That last row is part of the upset. Dev tooling is on the discovered-provider list of a product named Webkernel.
3. Classes you own vs classes you rent
You asked for a clear vision of classes you use compared to classes in the vendor tree. Start here. This is a map, not a complete census.
3.1 First-party surface (the kernel you already started)
Owned HTTP / host types that already pretend Laravel is an implementation detail:
| Class / entry | What it actually is |
|---|---|
|
Webkernel\WebApp
|
extends Illuminate\Foundation\Application. Snake_case aliases over vendor camelCase.
|
|
Webkernel\WebAppApi
|
Discover-and-dispatch façade for webapp()->auth(), host(), panel().
|
|
Webkernel\Request
|
extends Illuminate\Http\Request + short-circuit hooks. Capture still goes through Symfony HttpFoundation.
|
|
Webkernel\Paths\Router
|
Framework-free dispatcher under /__webkernel-app/. Called from WebApp::configure().
|
|
Webkernel\Paths\Assets
|
Native ETag for package assets.
|
|
public/index.php
|
304 for MD before bootstrap/app.php. Then (require bootstrap/app.php)->handleRequest(Request::capture()).
|
|
md_http_revalidate.php
|
Pure PHP ETag/304. Must stay lockstep with MdPageConditionalGetMiddleware.
|
|
bootstrap/fast-boot.php
|
Autoload / integrity / npm / vite.webapp.ts. Not a request kernel.
|
3.2 What you rent (and cannot “take” without the host)
Filament 5 is Laravel + Livewire + Alpine. You cannot fork Filament onto a homemade kernel without forking Filament. That is the whole constraint.
| You want | You also get |
|---|---|
|
Filament Pages, Actions, Tables, Forms, Panels
|
Livewire components, Alpine, Blade, Illuminate Http/Routing/View/Auth/Session/Validation
|
|
Livewire islands (DashboardCreateFolder, …)
|
Full Livewire service provider on the app
|
|
Eloquent in bookstore
|
Query builder, Carbon, collections, pagination
|
|
Illuminate\Console\Command
|
Artisan / Symfony Console
|
|
Blade for MD blocks
|
Blade compiler, view finder, Filament’s view stack if the page is a Filament Page
|
3.3 Illuminate you actually import in software/ (observed)
Not a complete list — a use-shape:
| Illuminate slice | Used for | Ally / enemy |
|---|---|---|
|
Http\Request / Response / JsonResponse / RedirectResponse
|
PublicFront + i18n routes
|
Ally (or Symfony HttpFoundation under it)
|
|
Support\ServiceProvider
|
Every package
|
Enemy as global discovery; ally as a register/boot hook
|
|
Support\Facades\{Route,View,Event,Auth,Http,Blade,File}
|
Glue
|
Enemy if they leak into domain; ally at the adapter edge
|
|
Contracts\Support\Htmlable
|
Almost every Filament Page title
|
Noise from Filament
|
|
Contracts\View\View
|
Livewire render()
|
Panel plane
|
|
Database\Eloquent\Model
|
CmsSite/CmsPage, i18n Language, Site Member, Subsite, bookstore domain
|
Ally in modules that are databases. Enemy as the page runtime for MD.
|
|
Cookie / Session / CSRF middleware
|
CmsPanelDefinition
|
Panel plane — correct
|
|
Console\Command
|
Warm/purge cache, fetch fonts
|
Ally
|
|
Pagination\*
|
MD search
|
Could be yours
|
|
Support\Collection, Str, HtmlString
|
Everywhere
|
Convenience tax. Do not rebuild Carbon/Collections first.
|
|
Foundation\Http\Events\RequestHandled
|
ETag on the Laravel miss path
|
Proof the miss path is still the Laravel kernel
|
|
Http\Client
|
cache warmer
|
Fine
|
Queue / Mail / Notifications: almost unused in software/. Three ShouldQueue jobs live in software-modules/module-webkernelphp-com. Codebase still ships jobs / job_batches / failed_jobs migrations. That is Laravel imposing a shape you barely fill.
3.4 Filament / Livewire you actually import
Filament is not “a CRUD admin you might drop.” It is the operator product (ADD-0002: no-code default).
In software/ today:
- Filament Page: Site CMS workspace pages, settings cluster, page builder, onboarding, and public DynamicMdPage + MdSiteSearchPage.
- Filament Resources / Tables / Forms: markdown pages, sites, authoring custom blocks (TipTap), imagery icon picker.
- Filament Actions + Livewire islands: DashboardCreateFolder, BuilderTopbarActions, SidebarAddFolder — this is the AGENTS.md pattern, not a Livewire app.
- Livewire attributes: #[On], #[Url], #[Session], #[Validate], WithPagination on public search.
The architectural bug is not “we use Filament.” The bug is Filament Page is the public renderer.
plugin-mdsite/docs/ARCHITECTURE.en.md already forbids this:
Public HTTP never loads Filament, TipTap editor runtime, or Cms/*.
That sentence is the new structure. It is not implemented on the miss path.
4. Answers to “what do you actually use?” (from the tree, not vibes)
| Question | Answer in this repo |
|---|---|
|
Eloquent?
|
Yes for modules that are databases (bookstore: sales, stock, identity). No as the public page store (MD is files). CmsSite/CmsPage exist as Filament/Sushi-shaped models, not as the public source of truth.
|
|
Laravel routing?
|
Yes for panels and leftover public pages. Also Webkernel\Paths\Router for /__webkernel-app/*. Two routers is a smell and a seed.
|
|
Container / providers?
|
Yes. WebAppApi already sits on top of it. Replacing it with PHP-DI now is theatre.
|
|
Blade?
|
Yes. Block rendering, panel chrome, error pages. Livewire does not replace Blade.
|
|
Artisan?
|
Yes. Warm/purge, fonts, installer, integrity. Symfony Console underneath. Keep Artisan until the operator plane stays Laravel.
|
|
Queue / Jobs?
|
Barely. Do not rebuild queues. Do not let jobs tables define the kernel.
|
|
Events / Listeners?
|
RequestHandled for ETag. Onboarding events. Not a domain bus.
|
|
Filament?
|
The operator UI. Panels, forms, tables, actions, TipTap blocks, onboarding, bookstore boards. Not optional if ADD-0002 still holds.
|
|
Alpine?
|
Filament’s. You do not have a separate Alpine app.
|
|
Octane?
|
Already required. Not a new option — an unused lever until the public miss path stops being a Filament Page.
|
5. Why hello world is slow (the real sequence)
A public GET today:
So:
- Repeat visitors with a fresh ETag already skip Laravel. Native ETag exists. Stop saying it does not.
- First byte / miss / HTML generation still pays the full operator stack. That is the hello-world you hate.
- Octane would amortize boot. It would not make a Filament Page a hello world. It would make a warm Filament Page cheaper. Different problem.
P0 table — measured 2026-08-19 from this host → https://staging.numerimondes.com
Five samples each after one warm. Median TTFB. Client is curl on the same machine (TLS + nginx + FPM included). No extra instrumentation in index.php.
| Probe | Median TTFB | Min–max | Status | Body | Header / note |
|---|---|---|---|---|---|
|
0. Native PHP hello (your number, not HTTP)
|
0.002084 ms
|
—
|
—
|
—
|
OPcache on, JIT buffer 64 MB unused, 14.1 MB used, 90.91% hit
|
|
Static floor GET /robots.txt
|
74.5 ms
|
—
|
200
|
24 B
|
nginx + TLS + FPM, almost no PHP app
|
|
1. GET / + matching If-None-Match
|
145.9 ms
|
102–171
|
304
|
0
|
X-Md-Http-Cache: REVALIDATED-304 — preboot, not Filament
|
|
2. GET / no INM (public HTML)
|
1504.7 ms
|
1441–1784
|
200
|
1.45 MB
|
X-Md-Http-Cache: RENDER every time — DynamicMdPage
|
|
3. GET /up
|
402.9 ms
|
329–441
|
200
|
1.8 KB
|
Laravel health, no MD page
|
|
4a. GET /site-cms (no follow)
|
424.7 ms
|
349–533
|
302
|
434 B
|
→ /site-cms/login
|
|
4b. GET /site-cms follow to login
|
883.8 ms
|
811–1293
|
200
|
277 KB
|
operator plane, unauthenticated
|
|
5. php artisan about
|
~1.01 s (2nd run)
|
0.97–1.34 s
|
CLI
|
—
|
Laravel 13.26.1, PHP 8.4.24, APP_ENV=production, debug off
|
What this actually says:
| Claim | Measured |
|---|---|
|
PHP is slow
|
False. 0.002 ms inner, ~75 ms HTTP static.
|
|
No native ETag
|
False. Probe 1 is 304 in ~146 ms, ~10× faster than probe 2.
|
|
Public miss is a Filament page
|
True. Probe 2 is 1.5 s and RENDER, not a disk HTML hit.
|
|
/up is the Laravel boot floor
|
~400 ms over HTTP from this client.
|
|
Operator may be heavy
|
Login HTML ~880 ms. Allowed.
|
|
304 is as fast as native PHP
|
False. 146 ms vs 0.002 ms. 304 still pays TLS + FPM + Composer autoload. It does not pay Filament.
|
Included-file / peak-memory counts were not added to index.php. You asked for no more mess. TTFB + X-Md-Http-Cache is enough for P1.
P1 is imposed by probe 2, not by probe 0: public GET / must stop being a Filament Page.
6. The trap of “from scratch”
The fantasy:
Take Laravel + Livewire + Filament + Alpine and create Webkernel with them from scratch so I control it all.
What that actually means, if you are honest:
- Reimplement Application, HTTP kernel, pipeline, container, config, Blade compiler, translator, session, cookies, validation, hashing, Eloquent (bookstore), console, filesystem.
- Keep Filament working. Filament will not run on your kernel unless your kernel is Laravel’s contracts.
- Keep Livewire’s checksum/morph/islands protocol.
- Lose two years of bookstore + Site CMS + i18n + onboarding while you chase hello world ms.
- Still ship Carbon, Symfony HttpFoundation, and a CSS/JS toolchain.
You would not control “all of it.” You would control a worse Laravel, and Filament would still own the operator UI.
Control is not obtained by owning every class. Control is obtained by owning the planes and the contracts between them.
You already practice this in small:
- webapp()->auth() not auth()->user()->name
- snake_case on Webkernel types, camelCase only on Filament/Livewire forced APIs
- MD on disk as the portable source; TipTap is authoring-time
- 304 before the kernel
- Paths\Router with zero Illuminate
The new structure is: finish that split, do not start a fourth framework.
7. The structure (three planes)
Plane A — Serve (the hello world)
Owns: resolve host → site → path → load .md → render Blade blocks → ETag / disk cache → HTML | MD | RSS | sitemap.
Must not load: Filament, Livewire, TipTap, Cms/*, Shield, bookstore providers, onboarding wizard.
Already exists: md_http_revalidate.php, MdPageConditionalGetMiddleware, PageRenderer / block rendering, disk cache under bootstrap/cache/webkernel/md-site/.
Missing: miss path is still DynamicMdPage extends Filament\Pages\Page.
End state: a public miss is PHP + your renderer + Blade. Laravel may still create the app for a while (pragmatic), but Filament must not. Later, the miss path should not create WebApp at all — same trick as 304, with HTML from disk cache, and a PHP renderer only on cache cold.
Plane B — Operator (the product you sell to non-developers)
Owns: Site CMS, onboarding, imagery picker, bookstore boards, Shield, panels.
May be heavy. Operators accept a 200 ms Filament page. Visitors do not.
Stack: Laravel + Livewire + Filament + Alpine. This is allowed, named, and isolated.
Rule: operator packages register providers only when the request is operator (path prefix, or a dedicated front controller). They do not ride along on GET /.
Plane C — Modules
A module that is a database (bookstore) uses Eloquent, migrations, Filament resources. A module that is a website (numerimondes-website) contributes MD content + maybe blocks, and must not pull the operator stack onto the public GET.
Modules must not decide the kernel. The kernel decides what a module may touch per plane.
Host contract (already half-built)
Keep these as the only types a plane is allowed to assume:
- webapp()->… composables (Auth, Host, Panel, later Site, Cache, Clock if needed).
- Webkernel\Request short-circuits (plugins inject, return true if handled).
- Webkernel\Paths\Router for kernel-owned URLs and assets.
- Filesystem + ETag helpers that do not boot Illuminate.
- Integrity / fast-boot for install, not for request.
WebApp extends Application stays until Plane A no longer needs it. When Plane A is free, WebApp can become a panel host adapter instead of the process singleton. That is the from-scratch you actually want — and it is an extraction, not a rewrite.
8. What to do (sequence). What not to do.
Do not
- Start software/kernel as a new Illuminate.
- Replace Eloquent in bookstore to “be pure.”
- Replace Blade.
- Replace Filament as the operator UI in this phase.
- Swap the container for PHP-DI.
- Treat Octane as the architecture.
- Delete Carbon because locale files are fat.
- Write a second constitution. This file stays a working note.
Do — in order
P0 — Measure (one afternoon) Five probes in §5. Record ms, included files, memory, provider count. Put the table back here. Without it, every later PR is folklore.
P1 — Stop lying about the public page DynamicMdPage / MdSiteSearchPage must stop extending Filament\Pages\Page. Public HTML is a controller + Blade (or a non-Livewire renderer you already have). This is the ARCHITECTURE.en.md contract. This is also the hello-world fix.
Search can stay Livewire later, behind /search, not on /.
P2 — Split package discovery by plane Public GET must not register CmsPanelProvider, Filament, Shield, onboarding, bookstore boards, Boost, Pail, Tinker.
Concrete levers (pick one, do not invent three):
- dont-discover for operator packages + explicit register from panel providers.
- Two front controllers: public/index.php (serve) and public/app.php or /site-cms bootstrap (operator).
- Deferred providers gated on path prefix.
Dev packages must not be in packages.php for production.
P3 — One HTML cache, two doors 304 door already exists. Add a full-document disk-cache door in index.php (autoload + read cached HTML + ETag, no WebApp). Renderer runs only on cold cache. etag_rerender() islands stay the exception, fetched over a tiny endpoint (LiveIslandsController already exists).
P4 — Host API grows instead of Facades Every new auth(), config(), View:: in first-party code is a regression unless it is at a plane-B edge. Prefer webapp()->…. Add composables when a concern repeats. Do not add composables for show.
P5 — Only then: can WebApp stop extending Application? If Plane A is fast and Plane B still needs Laravel, you have the product. If you still want to own HTTP for the panel, you are choosing a multi-year Filament fork. That is a different ADD, and it needs a reason stronger than a tweet.
Optional parallel (low risk)
- --no-dev production installs (drop JetBrains 113 M, Rector, PHPStan, PHPUnit from the deployed tree). This does not fix hello world. It fixes the “I am loading 11k for no reason” feeling on disk.
- Keep Octane for Plane B and for CLI workers. Do not use it to hide a Filament public page.
9. How this maps onto files you already have
| Existing file | Role in the new structure |
|---|---|
|
plugin-mdsite/docs/ARCHITECTURE.en.md
|
Canonical for Plane A + authoring. Obey it.
|
|
plugin-mdsite/docs/Product_ARCHITECTURE.md
|
Plane B (workspaces, folders).
|
|
software/codebase/docs/decisions/add/
|
Platform law. This file is not that.
|
|
AGENTS.md
|
Coding rules while Laravel/Filament remain Plane B.
|
|
src/possible-new-structure.md
|
Transitional MD plugin inventory. Do not expand.
|
|
public/index.php
|
Front door of Plane A. Grow the pre-kernel doors. Do not grow Filament here.
|
|
bootstrap/app.php
|
Plane B (+ current miss path). Should become “operator + modules,” not “the world.”
|
|
WebApp / WebAppApi
|
Host contract. Extract, don’t replace.
|
|
Paths\Router + Paths\Assets
|
Proof Plane A can live without Illuminate. Expand this family, don’t start another.
|
10. Decision (until a later ADD)
- Webkernel stays a PHP platform. Laravel is not the identity.
- Filament stays the operator UI for this phase. That implies Laravel + Livewire + Alpine on Plane B.
- Public HTTP is not a Filament Page. That is the non-negotiable structural change.
- No from-scratch Illuminate. Control comes from plane isolation + host contract + pre-kernel doors.
- ETag is already native on the 304 path. Finish the miss path and the disk-cache door so you stop accusing the platform of lacking it.
- Bookstore keeps Eloquent. Different plane, different storage.
- P0 is done. Native PHP is not the problem. Probe 2 is. The 11k is mostly node_modules. The HTTP tax is DynamicMdPage + full provider boot on miss.
The tweet is useful as a reminder, not as a roadmap. Otwell can say PHP is a detail of Laravel. You do not have to build a world where Laravel is the detail of nothing.
You already started the world where Laravel is the detail of the panel. Finish that.
11. Next physical step (when you say go)
- ~~Write the five-probe table into §5.~~ Done 2026-08-19.
- Open a single PR: public GET no longer extends Filament\Pages\Page.
- Gate operator providers off the public boot.
- Then talk about whether WebApp still needs to be an Application.
Until (2) exists, “from scratch” is just pain. After (2), you will know whether you still want to own more. )