update: CSS per-page split and lightningcss unusedSymbols report

docs: record CSS template inventory + decide unusedSymbols-before-split ordering

feat(css): add content-source collector + dynamic-class safelist for unusedSymbols report

- scripts/css-content-sources.mjs: buildCorpus() gathers templates/public/src
  PHP + first-party js/app JS (vendor excluded), returns {corpus, sources,
  totalBytes, safelist, prefixes}
- Mined 22 exact runtime classes + 5 DB/state-derived suffix prefixes from
  status-badge.php, SystemController statusClass/logLineClass, and class=<?=?>
  ternaries
- docs/css-split-analysis.md notes content-corpus section
- td: #11 collect-content-sources done; feeds #12 report script

feat(css): unusedSymbols report script + just css-report recipe

- scripts/css-unused-report.mjs: per-bundle class/id extraction vs buildCorpus()
  corpus + safelists; measures reclaimable bytes via lightningcss transform
  unusedSymbols (report-only, no stripping to disk)
- just css-report: rebuild CSS then run the report
- css-content-sources.mjs: add VENDOR_CLASS_PREFIXES (filepond--*, htmx-*)
- RESULT: 216,383B total, ~6.2KB (2.9%) reclaimable; FilePond/HTMX exclusion
  corrected inflated 26% (56KB) false-positive down to honest 2.9%
- docs/css-split-analysis.md findings table + TODO 12/13 done

docs(css): record go/no-go decision — split NO-GO, pruning conditional-go

- Decision analysis in docs/css-split-analysis.md
- ~6.2KB (2.9%) reclaimable of 216KB; base.min.css only 484B (2.3%)
- SPLIT NO-GO: base.css already well-used; parked u/w/x/y(/z) as deferred
- PRUNING conditional-go on hand-verifiable dead selers from source, never dist;
  re-run just css-report after each edit; keep needs-review + vendor-prefix cls
- td: task 14 done; split stream 41 tasks -> 2 pending / 32 done / 7 deferred

todo: defer CSS pruning stream (10/u/w/x/y/z), context updated
This commit is contained in:
Pontoporeia
2026-09-18 16:26:36 +02:00
parent a31e4ab307
commit 8accb88452
7 changed files with 566 additions and 28 deletions
+14 -6
View File
@@ -1,16 +1,13 @@
# TODO
> Last updated: 2026-08-26
> Context: Recherche insensible aux accents sur toutes les barres de recherche (publique + backoffice)
> Last updated: 2026-08-28
> Context: CSS waste strategy — full stream DEFERRED (tasks 10/u/w/x/y/z): unusedSymbols report showed only ~6.2KB/2.9% of 216KB reclaimable; pruning + per-page split parked (docs/css-split-analysis.md). #t (Interdit admin route) confirmed already implemented via /admin/media.php + media-viewer.php + recapitulatif link. No pending live tasks.
## In Progress
## Pending
## Completed
- [x] #normalize-synopsis-whitespace & Passages à la ligne inopinés dans les synopsis : jugés INDÉTECTABLES/non-corrigeables automatiquement (une coupure de ligne interne peut être une vraie frontière de mot → espace, ou une coupure du mot → à réjoindre ; indistinguables sans dictionnaire, toute tentative corrompt l'entrée p.ex. "poursuivantsur"). Migration 045 SUPPRIMÉE ; dites à l'admin qu'il n'y a pas de nettoyage fiable des données existantes. Conservation de la NORMALISATION SÛRE sur les nouvelles saisies : fin de ligne \r\n/\r → \n, pertes de lignes vides multiples → \n\n, espaces/tabulations/NBSP → espace unique, trim — retour à la ligne simple intra-paragraphe laissé intact.
- [x] #recherche-insensible-aux-accents Recherche insensible aux accents sur toutes les barres de recherche : "Théophile" et "Theophile" donnent maintenant les mêmes résultats. Implémentation via une fonction SQLite UDF `accfold()` (app/src/AccentFolding.php, UDF enregistré dans Database::registerSqliteFunctions) qui plie les caractères accentués latin (é→e, ç→c, …) vers leur base ASCII ; chaque condition de recherche compare la colonne repliée au terme replié (`accfold(column) LIKE accfold(:term)`). Appliqué à la recherche publique (searchTheses/countSearchResults sur titre/sous-titre/synopsis/auteurs/promoteurs/tags), au popover étudiant (getThesesByAuthorName/getThesesForAuthors) et à la recherche backoffice par auteur (getThesesList/getThesesListCount). NULL-safe, enregistré sur les connexions prod et de test (TestDatabase). Pas d'intl/iconv dispo → map de translitération manuelle (Western Latin-1 + Latin Extended-A). Évite aussi la dépréciation PHP 8.5 de PDO::sqliteCreateFunction() : création de la connexion via \Pdo\Sqlite (createFunction()) quand dispo, repli sur PDO (sqliteCreateFunction()) sur anciens runtimes ; tests passent de `@dataProvider` docblock à l'attribut #[DataProvider] (fin de la dépréciation PHPUnit). 294 tests verts sans dépréciation ; phpstan OK.
- [x] #untrack-build-caches Détrache .phpunit.result.cache et .php-cs-fixer.cache (déjà gitignorés) pour qu'ils ne réapparaissent plus comme modifications dans le working copy.
- [x] #audit-all-docs-and [!high] Audit all docs/ and classify accurate vs stale
- [x] #rewrite-development-md-to-match [!high] Rewrite development.md to match current just dev / app/ layout / PHPUnit
- [x] #rewrite-deployment-md-to-match [!high] Rewrite deployment.md to match just deploy / /var/www/xamxam/ / backup
@@ -38,8 +35,19 @@
- [x] #default-exemplaire-baiu Default Exemplaire BAIU + ERG to true on student (partage) submission in ThesisCreateController, replicating admin add-form default
- [x] #m-style-links-on Style links on licences, charte, a propos pages (underline + accent hover)
- [x] #recherche-backoffice-par-identifiant Recherche backoffice par identifiant : ajouter t.identifier a la WHERE de getThesesList/getThesesListCount
- [x] #admin-open-interdit-files Open 'Interdit' thesis files from backoffice: dedicated /admin/media.php route (AdminAuth session-gated) that bypasses the Interdit visibility gate in MediaController, keeping path jail + MIME checks; recap page links Interdit files to it. Also: file links open via /admin/media-viewer.php HTML wrapper so the tab shows the original file name instead of media.php; media.php sets a meaningful Content-Disposition filename too. Hardened: admin media route restricted to thesis-file prefixes only (defense-in-depth over the MIME/jail checks)
- [x] #add-admin-only-route-to [!low] Add admin-only route to open Interdit thesis files from backoffice --context repo
- [x] #identify-which-templates-load [!high] Identify which templates load base.min.css and the page types — DONE inventory: docs/css-split-analysis.md. Key finding: head.php loads base.min.css on EVERY full page (App::render => templates/head.php unconditional). is the per-page-type mechanism (public/tfe/repertoire/content-page/not-found/form/admin/system). Centralize inventory in docs/css-split-analysis.md. Gates both split (w) and unusedSymbols corpus (11).
- [x] #collect-content-sources-for [!high] Collect content sources for the unusedSymbols pass — Built scripts/css-content-sources.mjs: buildCorpus() returns {corpus, sources, totalBytes, safelist, prefixes}. Gathers templates/public/src PHP + first-party js/app JS (vendor excluded). Mined 22 exact dynamic classes (status-*, log-*, input-error, active, disabled, btn--*, fhb-*, admin-icon-btn--*, status-published/pending/badge) + 5 suffix prefixes (status-access--*, toc-level-*, admin-import-log__item--*, admin-body, student-body) from status-badge.php, SystemController statusClass/logLineClass, class=<?=...?> ternaries. Documented docs/css-split-analysis.md. Feeds task 12 report.
- [x] #write-report-script-to [!medium] Write report script to compute unused selectors + reclaimable bytes — Built scripts/css-unused-report.mjs + just css-report recipe. Extracts class/id symbols per dist bundle, matches against buildCorpus() corpus + safelists, measures reclaimable bytes via lightningcss transform unusedSymbols (no stripping to disk). FULL RESULT: 216,383B total, ~6.2KB (2.9%) reclaimable. See docs/css-split-analysis.md table. Feeds task 14 go/no-go.
- [x] #flag-or-safelist-dynamic [!medium] Flag or safelist dynamic classes before trusting the report — Vendor + dynamic safelists shipped. Dynamic: 22 exact classes (status-*, log-*, input-error, active, disabled, btn--*, fhb-*, admin-icon-btn--*, status-badge/published/pending) + 5 DB/state suffix prefixes (status-access--*, toc-level-*, admin-import-log__item--*, admin-body, student-body). VENDOR: filepond--* and htmx-* prefixes excluded (runtime vendor JS assembly) — without these the report inflated to 26% (56KB); with them honest 2.9%. Report surfaces needs-review (kept, not flagged removable).
- [x] #summarize-findings-and-decide [!low] Summarize findings and decide whether to proceed to pruning — Decision recorded in docs/css-split-analysis.md. Data: 216,383B total, ~6.2KB (2.9%) reclaimable; base.min.css only 484B. SPLIT: NO-GO (base.css already well-used; split adds complexity for ~2% of one bundle) -> defer u-w-x-y. PRUNING: CONDITIONAL-GO on hand-verifiable dead set only, from SOURCE css files, re-run just css-report after each edit, never touch needs-review/vendor-prefix classes.
## Deferred / Blocked
- [ ] #just-setup-backs-a [!medium] just setup backs a stale setup-dev.sh (clones php-live-reload, legacy admin/data/ dirs) — needs rewrite or removal
- [ ] #csv-export-writes-cc2r-exemplaire [!low] CSV export writes CC2r/Exemplaire columns that import's $cell mapping ignores — document or wire up
- [ ] #split-base-min-css-into-per-page [!medium] Split base.min.css into per-page bundles to reduce cross-page CSS waste — NO-GO (task 14): per-page split parked. base.min.css already well-used (only 484B/2.3% reclaimable); split adds build/template complexity for ~2% of one bundle. See docs/css-split-analysis.md decision.
- [ ] #define-per-page-type-css-bundle [!medium] Define per-page-type CSS bundle boundaries and entry files — Using the template->page-type map, decide which source files (components/*, utilities.css, reset, typography, etc.) belong in each page-type bundle. Document the mapping so shared-vs-page-specific splits are explicit and reviewable.
- [ ] #implement-per-page-bundles-in [!medium] Implement per-page bundles in scripts/build-css.mjs — Extend build-css.mjs with new bundle targets following the existing bundleCss/concatBundle pattern. Emit per-page-type .min.css outputs into app/public/assets/dist/. Keep base.min.css for pages not yet split.
- [ ] #wire-templates-to-load [!medium] Wire templates to load their page-type bundle instead of base.min.css — Update each template's <link> tags to point at the appropriate per-page bundle. Ensure no page loads CSS it doesn't need and that shared pages still get full styling.
- [ ] #measure-before-after-css-bytes [!low] Measure before/after CSS bytes shipped per page type — Moot: part of split stream which is NO-GO (task 14). Skip unless split is revived.
- [ ] #add-lightningcss-unusedsymbols-report-only [!high] Add lightningcss unusedSymbols report-only pass for base.min.css — SEQUENCING: do this (unusedSymbols report-only) BEFORE the per-page split (u). Reasoning: high priority, zero-risk diagnostic-only, produces reclaimable-byte go/no-go (14) against ~216KB baseline; if pruning is not worthwhile the split may not be either. Reuses inventory from v (docs/css-split-analysis.md). Start: 11 collect-content-sources.