# TODO
> Last updated: 2026-06-24
> Context: Security audit — fix open redirects, fragment auth, dead code, CSRF gaps
## Completed
- [x] #icon-color-verify Verify icon colors render correctly across all pages (header, admin tables, forms, dialogs, cleanup modal) ✓
- [x] #sec-open-redirect Fix open redirect in tag.php + language.php (protocol-relative URL bypass via str_starts_with) ✓
- [x] #build-pipeline Setup biome + rolldown + lightningcss build pipeline ✓
- [x] #build-packagejson Create package.json with devDependencies ✓
- [x] #build-biomecss Update biome.json to handle CSS formatting ✓
- [x] #build-rolldown-config Create rolldown.config.mjs + build-js.mjs for JS bundling ✓
- [x] #build-lightningcss Add lightningcss CSS bundling (resolve @import chain) ✓
- [x] #build-justfile Add just build/deploy recipes + integrate build into deploy ✓
- [x] #build-head Update head.php + form-page.php + controllers to use bundled assets ✓
- [x] #build-gitignore Add dist/ to .gitignore ✓
- [x] #build-cssfix Fix stray `}` syntax error in admin.css line 305 ✓
- [x] #sec-fragments-auth Gate partagé fragments on share_active session (read-only fragment renderers — no CSRF needed) ✓
- [x] #sec-retry-csrf Add CSRF check to partage/retry-email.php POST ✓
- [x] #sec-cleanup-dead-code Remove dead App::verifyCsrf() or refactor action handlers to use it ✓
## Pending
- [ ] #rep-student-touch Replace hover student popover with tap-to-open drawer for mobile `(repertoire.php, repertoire.css, repertoire-student-popover.js)`
- [ ] #rep-polish Polish: scroll-position memory on HTMX swap, animation tuning `(repertoire.css)`
## Completed (before this session)
- [x] #gzip-nginx Enable gzip compression in nginx config `(nginx/xamxam.conf)` ✓
- [x] #extract-inline-js Move inline JS to external files across 17 templates → 15 new JS files created `(app/public/assets/js/app/*.js)` ✓
- [x] #inline-icon-helper Create `icon()` PHP helper + auto-load in bootstrap `(src/icon.php, bootstrap.php)` ✓
- [x] #icon-fill-currentcolor Ensure all 36 icon SVGs use fill="currentColor" or stroke="currentColor" `(assets/icons/*.svg)` ✓
- [x] #migrate-all-img-icons Replace all remaining `` with `= icon() ?>` across 26 template files ✓
- [x] #icon-css-cleanup Remove `
` filter hacks from admin.css, add explicit sizes for header nav + public search icons `(admin.css, search.css)` ✓
- [x] #icon-em-sizing Set icon helper to output width="1em" height="1em" so icons scale with parent font-size `(icon.php)` ✓
- [x] #inline-svg-to-icons Move all inline SVGs to asset icon files, ensure currentColor fill for proper color inheritance `(33 icons, 22 files, 67+ inline SVGs replaced, 2 dynamic SVGs remain)` ✓
- [x] #cleanup-modal-fixes Fix nettoyage modal: SVG caret icons, margin→padding, BBBDMSans summary `(admin.css, details.css)` ✓
- [x] #structure-formulaire-page Move "Structure du Formulaire" from contenus.php to its own dedicated page with back button `(structure-formulaire.php [new], contenus.php)` ✓
- [x] #contenus-indexes Add index on thesis_languages(language_id) + tags(deleted_at, name); fix count queries to exclude soft-deleted theses `(Database.php, DatabaseMigrations.php, schema.sql, migrations/applied/041_thesis_languages_index.sql)` ✓
- [x] #peertube-orphans-check Add Peertube orphan video check + relink in admin — listChannelVideos (PeerTubeService), peertube-orphans.php endpoint, UI in nettoyage dialog, relink support on edit page `(peertube-relink.php, peertube-browser.php, fichiers-fragment.php, file-upload-filepond.js)` ✓
- [x] #tfe-optional-formats Make TFE file optional when format is Site web (1), Performance (4) or Installation (6) — fixed incorrect format IDs [3→1,4,6] + added client-side JS toggle for TFE required/asterisk. Note d'intention remains required. 🎯 `(fichiers-fragment.php, file-upload-filepond.js)` ✓
- [x] #typography-weight-300 Set search placeholder + apropos/charte/licence
content to BBBDMSans weight 300 `(search.css, apropos.css)` ✓ - [x] #toc-parts-uppercase Hardcode "PARTIES" uppercase + black bottom border on TOC label `(about.php, charte.php, licence.php, apropos.css)` ✓ - [x] #apropos-overflow Prevent #apropos-intro and content-section children from overflowing `(apropos.css)` ✓ - [x] #toc-navigation Fix TOC links not navigating to headings — added `apply_id_to_heading: true` to CommonMark config so IDs land on headings not hidden elements; added scroll-margin-top to headings; unstuck main from flex container so sticky TOC works for full page height `(CharteController.php, LicenceController.php, apropos.css)` ✓ - [x] #apropos-toc-style Fix TOC "Parties" label: Ductus font + lowercase, remove border-left from links, match global link style; rename .apropos-content → section.content, .apropos-section → .content-section, remove .prose wrapper `(apropos.css, about.php, charte.php, licence.php)` ✓ - [x] #apropos-toc-confirm Fixed sticky TOC: removed `flex: 1; min-height: 0` on main for apropos-body so the sticky container is full content height; added `max-height` + `overflow-y: auto` to TOC for long lists `(apropos.css)` ✓ - [x] #fix-finality-types Create standalone script + just command to rename finality types (Approfondi→Approfondie, Enseignement→Didactique, Spécialisé→Spécialisée) `(scripts/fix-finality-types.php, justfile)` ✓ - [x] #context-note-synopsis Display contextual note above synopsis (italic) instead of in meta column on TFE page `(tfe.php, tfe.css)` ✓ - [x] #decouple-contacts Decouple contact_visible (public) & contact_interne (private email): backend already decoupled; made contact_public checkbox functional in admin add/edit forms; contact_public now controls TFE page visibility `(FormBootstrap.php, ThesisCreateController.php, ThesisEditController.php, tfe.php, form.php)` ✓ - [x] #csrf-rotation-race Stop CSRF token rotation in draft.php + remove hx-post from