diff --git a/commits.txt b/commits.txt
new file mode 100644
index 0000000..a3c07ea
--- /dev/null
+++ b/commits.txt
@@ -0,0 +1,6268 @@
+72d3075466cc | 2026-06-29 17:27
+
+---
+a790ddc02ad0 | 2026-06-24 14:57
+Fix admin login heading: prevent 'Administration' from hyphenating or overflowing box
+
+---
+21f16ee093ec | 2026-06-24 14:49
+chore: move #rep-polish to Completed, investigate #tighten-csp blockers
+
+---
+eb706214ce16 | 2026-06-24 14:45
+feat: scroll-position memory on repertoire HTMX swaps + swap transition polish
+
+- Add repertoire-scroll-restore.js: snapshots scrollTop of each column
+ before htmx:beforeSwap, restores after htmx:afterSwap (keyed by data-col)
+- Add subtle opacity transition on #repertoire-index during htmx-swapping
+- Tighten rep-indicator opacity transition to 0.1s for snappier feedback
+- Import new module in public-entry.js
+
+---
+e0cf9f8f5746 | 2026-06-24 14:26
+chore: update TODO — mark icon-color-verify and 3 security tasks complete
+
+---
+0062b296782e | 2026-06-24 14:17
+Fix security issues from audit: gate partage fragments on share_active session, add CSRF to retry-email POST, remove dead App::verifyCsrf()
+
+---
+84869ad96811 | 2026-06-24 14:15
+Fix open redirect in tag.php + language.php: reject protocol-relative URLs (//evil.com) by also checking for // prefix
+
+---
+6ecd3d4540ef | 2026-06-24 13:21
+Fix biome lint errors: remove duplicate CSS properties, apply safe auto-fixes
+
+CSS:
+- Remove duplicate 'background' fallbacks in base.css, header.css, search.css
+ (solid color declared before gradient — gradient always wins)
+- Remove duplicate 'padding' in admin.css .admin-import-log
+
+JS (biome --write safe fixes applied):
+- function() → arrow functions in all IIFEs and callbacks
+- forEach/callback → arrow functions
+- evaluePtrn → parseInt(x, 10) in admin-contacts-form.js
+- Cleaned label text in build.mjs lint step
+
+Remaining warnings are intentional: !important overrides, descending
+specificity (admin.css cascade), noUnusedVariables (functions exported
+to window/onclick), useTemplate style preference.
+
+---
+82d3dcb084b7 | 2026-06-24 13:18
+Fix repertoire column scrolling + admin TOC duplication
+
+- repertoire.css: add min-height: 0 to column
scroll containers so
+ grid 1fr row shrinks below content and overflow-y: auto activates
+- admin-toc.js: add __adminTocBuilt guard + nav.children check to prevent
+ double population when loaded both via admin.min.js and direct
+ at the end of the #format-fichiers-block fragment.
+- Removed hx-preserve wrappers — they prevented re-render after
+ format/annexes toggles changed visible inputs.
+- This also fixes .zip removal from TFE accept and jury repopulation
+ array crash from the previous commit.
+
+refactor: simplify file-upload-queue.js, remove file-preview.js
+
+- file-upload-queue.js rewritten from ~250 lines to ~120 lines:
+ no more DataTransfer machinery, no IIFE wrapper, uses .onchange
+ instead of addEventListener for simpler HTMX re-init.
+- window.XamxamInitFileUploads is the function itself (not an IIFE export).
+- Merged file-preview.js functionality into file-upload-queue.js
+ (single-file .data-preview handling). Deleted file-preview.js.
+- fichiers-fragment.php inline script calls XamxamInitFileUploads()
+ after every HTMX swap (same as before).
+
+debug: add console.log to file-upload-queue.js for file input behavior
+
+Adds logging at key points to diagnose why only one file is displayed:
+- XamxamInitFileUploads called
+- TFE queue picker init (id, multiple attribute state)
+- onchange event (files count, names)
+- fileArray post-concat length
+- Single-file preview bindings (id, multiple attribute)
+
+Remove after debug session.
+
+---
+8f4f9d00b457 | 2026-05-08 17:03
+Refactor: Form improvements and cleanup: note contextuel, annexes, fichiers
+
+1. fix: form improvements — multiple promoteurices, asterisks, contact dedup, bentopdf
+
+- Multiple promoteurice (interne + ULB): both fieldsets now support dynamic
+ add/remove rows (same pattern as lecteurs). field names changed to arrays
+ (jury_promoteur[], jury_promoteur_ulb_name[]). Controllers accept both
+ scalar and array forms for backwards compat.
+- ULB promoteurice: when finality=Approfondi, asterisk appears on legend
+ and first ULB input is marked required (JS toggle). Non-Approfondi hides
+ the fieldset and clears values.
+- Contact visibility duplication: removed redundant contact_public checkbox
+ from admin add/edit forms (showContact=false). The 'mail' field in
+ fieldset-tfe-info already serves this purpose.
+- Asterisk fixes: website URL field now has asterisk+required when Site web
+ format selected. Video/audio already had correct required handling.
+- bentopdf link: clearer full URL 'https://bentopdf.com/' in both
+ fichiers-fragment.php and form.php (edit mode)
+
+2. refactor: merge Note contextuelle into Backoffice, add Lien BAIU, reorder fields
+
+Backoffice fieldset now contains in order:
+1. Note contextuelle (was standalone fieldset)
+2. Points du jury
+3. Remarques
+4. Lien BAIU (moved from Métadonnées complémentaires)
+5. Exemplaire physique BAIU
+6. Exemplaire physique ERG
+7. Contact interne
+
+
+Métadonnées complémentaires now only has: pages, minutes, annexes checkbox.
+Removed dead showContextNote variable from form.php, add.php, edit.php.
+Controller baiu_link still mapped to input name "lien" (no migration needed).
+
+3. refactor: move annexes checkbox from Métadonnées into Fichiers fieldset
+
+- Removed 'Ce TFE comporte des annexes' checkbox from
+ fieldset-metadata.php.
+- Added annexes checkbox + conditional file input to
+ fichiers-fragment.php. When checked, an HTMX swap reveals
+ the 'annexes' file input (multiple, PDF or ZIP/TAR, max 500 MB).
+- form.php seeds ['has_annexes'] for initial fragment render.
+- Métadonnées complémentaires now only contains pages + minutes.
+
+---
+03c5fd217eaa | 2026-05-08 16:48
+feat: dual upload system — direct file storage + PeerTube API integration
+
+Adds a parallel PeerTube upload system behind a feature flag (disabled by default
+until upload quota is granted). When disabled, the existing direct file upload
+path works unchanged.
+
+Files:
+- src/PeerTubeService.php — credential storage (encrypted), OAuth2 token
+ retrieval, multipart upload to /api/v1/videos/upload
+- migrations/021_peertube_settings.sql — peertube_settings singleton table
+ + peertube_upload_enabled site_setting (default 0)
+- admin/actions/settings.php — peertube section handler
+- admin/parametres.php / templates/admin/parametres.php — PeerTube UI section
+- partage/fichiers-fragment.php — shows file inputs when enabled, TODO notice otherwise
+- ThesisCreateController / ThesisEditController — handlePeerTubeUpload()
+- tfe.php — PeerTube iframe embed detection
+- AdminLogger — logPeerTubeUpdate()
+
+---
+11e61226e2aa | 2026-05-08 16:35
+fix: justfile shebang recipes indentation (spaces → tabs)
+
+---
+e6829994b6d1 | 2026-05-08 13:03
+Refactor + feat: unify format/fichiers HTMX fragment, reorder format types, add file constraints, fix admin auth
+
+* **Unified Format + Fichiers into a single HTMX fragment**
+
+ * Introduced `app/public/partage/fichiers-fragment.php` as shared dynamic block returning both format checkboxes and adaptive “Fichiers” fieldset
+ * Logic adapts inputs based on selected formats:
+
+ * no selection / upload formats → standard file inputs
+ * “Site web” → URL fields only
+ * “Site web + upload” → file inputs + URL sub-fieldset
+ * Added admin wrapper: `app/public/admin/fichiers-fragment.php` (gated via `admin_mode=1`)
+ * Added `app/public/admin/format-website-fragment.php` for edit-mode website URL toggling
+ * Wired route `/partage/fichiers-fragment` in `app/public/partage/index.php`
+ * Refactored `form.php` (add/edit partage) to use single `#format-fichiers-block` instead of separate fragments
+ * Edit mode format checkboxes now target `format-website-fragment.php` → `#edit-website-url-fieldset`
+ * Added `$hxInclude` support in `checkbox-list.php` for configurable HTMX includes
+
+* **Format system migration + ordering**
+
+ * Migration `020_format_types_sort_and_rename.sql`:
+
+ * added `sort_order` column to `format_types`
+ * inserted new format **Image**
+ * defined ordering: Écriture · Image · Audio · Vidéo · Site web · Performance · Objet éditorial · Installation · Autre
+ * `Database.php`: format queries now use `ORDER BY sort_order, id`
+ * `fichiers-fragment.php`:
+
+ * uses ordered format list
+ * resolves Image/Vidéo/Audio by name
+ * introduces `$hasImage` flag
+ * preserves `admin_mode` across HTMX requests
+
+* **File constraints and UX updates**
+
+ * Enforced **100 MB PDF limit**
+
+ * `ThesisCreateController`: `MAX_PDF_SIZE = 100MB` for PDFs only
+ * `ThesisEditController`: same PDF-specific constraint applied
+ * Other file types remain capped at 500 MB
+ * Updated UI hints in `fichiers-fragment.php` and edit form:
+
+ * explicitly mention 100 MB PDF limit
+ * added reference to `bentopdf.com` for compression guidance
+ * `file-field.php`: added `$hintRaw` to allow HTML rendering in hints
+
+* **Admin authentication fix**
+
+ * Fixed missing auth in admin fragments
+ * Added `require_once AdminAuth.php`
+ * Replaced direct usage with `AdminAuth::requireLogin()`
+ * Applied consistent pattern with existing fragment authentication approach
+
+* **Migrations included**
+
+ * `019_add_ecriture_format.sql`
+ * `020_format_types_sort_and_rename.sql`
+
+* **Files affected**
+
+ * Controllers: `ThesisCreateController`, `ThesisEditController`
+ * DB layer: `Database.php`
+ * Public fragments: `partage/fichiers-fragment.php`, `admin/fichiers-fragment.php`, `admin/format-website-fragment.php`
+ * Templates: `form.php`, `checkbox-list.php`, `file-field.php`
+ * Routing: `partage/index.php`
+ * Misc: `TODO.md`
+
+This consolidates format normalization, HTMX UI simplification, file validation rules, and admin stability fixes into a single coherent system update.
+
+---
+7e35bba5304c | 2026-05-08 12:48
+Encrypt SMTP password at rest with AES-256-GCM
+
+---
+95fcbc919a44 | 2026-05-08 12:40
+Remove required from all admin add/edit form inputs
+
+- Skip required-field validation for orientation/ap/finality/licence/jury in admin add+edit
+
+---
+5735ccbc3878 | 2026-05-08 11:30
+Fix issues with nginx access to pages
+
+- fix: 403 on /language-autre-fragment.php — add explicit nginx location block
+
+ The nginx catch-all blocked direct access
+ to all PHP files except /index.php and files inside /admin/.
+
+ language-autre-fragment.php lives at the public root and is POSTed to by
+ HTMX from both the admin edit form and the partage form. Added an explicit
+ fastcgi block so it is executed
+ rather than denied.
+
+- fix: replace .php-suffixed public URLs blocked by nginx catch-all
+
+ Audit of all client-facing PHP URL references against nginx routing:
+
+ - fetch('/request-access.php') in tfe.php -> '/request-access'
+ (clean URL already routed by Dispatcher)
+ - /media.php?path= in form.php (x2) and admin/recapitulatif.php -> /media?path=
+ (nginx only has location = /media, no location for /media.php)
+
+ All these .php-suffixed URLs hit the nginx catch-all
+ location ~ \.php$ { deny all; }
+ which takes precedence over location / { try_files ... } for regex matches.
+
+---
+6ba13e00eabd | 2026-05-08 10:56
+test: add ShareLinkTest + PureLogicTest (TDD), fix coverMap undefined in SearchController
+
+---
+15d54fa19ee3 | 2026-05-08 10:55
+add Néerlandais language option and make language_autre conditionally required
+
+---
+f3d961556260 | 2026-05-08 10:46
+merge banners into covers: remove banner field, migrate files, add covers to search/home/repertoire cards
+
+---
+e3896811c416 | 2026-05-07 23:45
+Fix migrations and deploy issues + errors + linting
+- scan both pending/ and applied/ dirs so remote catch-up works
+- fix remote 500s: run.php handles per-statement errors so VIEW rebuilds run after duplicate columns; replace mb_strimwidth with substr (no mbstring extension on server)
+- add missing migration: 015_license_custom.sql (column existed in schema.sql but was never migrated)
+- remote: fgetcsv enclosure single-char + AdminLogger permission-denied
+guard + deploy always migrates
+- fix admin-filters wrapping: restore flex-wrap, flex-basis on
+inputs/selects, shrink-protect buttons
+- fix phpstan: remove redundant ?? [] after isset guard in
+ThesisEditController
+- biome: exclude vendored min.js via includes patterns;
+lint whole js dir; modernise beforeunload-guard.js
+
+---
+bdd95341b09c | 2026-05-07 22:48
+Extract shared TFE form partial — single source of truth for add/edit/partage
+
+Created templates/partials/form/form.php as the unified form template driven by
+$mode ('add'|'edit'|'partage') and boolean flags for optional sections.
+
+The three calling templates (templates/admin/add.php, templates/admin/edit.php,
+partage/index.php renderShareLinkForm) now only set variables then include the
+shared partial. ~200 lines of duplicated fieldset HTML eliminated.
+
+---
+ac0008df6cc5 | 2026-05-07 21:24
+Add website-type TFE support: URLs stored as thesis_files rows, HTMX-toggle on Site web format
+
+---
+9dc7ea98f252 | 2026-05-07 20:58
+fix: password-protected share links never load form after password entry
+
+The main GET handler in partage/index.php always showed the password gate
+for links with password_hash set, even after successful verification. The
+session flag share_verified_ was being set by requirePasswordGate()
+but never checked when deciding whether to re-show the gate.
+
+Added a check: if the session flag is already set, skip the gate and
+render the form directly.
+
+Also added error_log() calls throughout the password flow to help
+diagnose future issues.
+
+---
+03121d6b7ef5 | 2026-05-07 20:52
+form: add spacing between elements inside fieldsets
+
+- common.css: fieldset > *:not(:last-child) gets margin-bottom: var(--space-xs)
+- admin.css: .param-form fieldset > * zeroes margin to avoid double-spacing with flex gap
+
+---
+d9dd4bdbc777 | 2026-05-07 20:42
+Edit Email confirmation styling
+
+---
+696259afae08 | 2026-05-07 19:54
+Fix form field required states & missing fields per spec
+
+- Admin add: add contact_public checkbox (matching edit form)
+- All forms: formats checkbox-list now required
+- All forms: jury promoteur·ice interne required, lecteur·ice interne/externe required
+- All forms: licence select now required
+- Admin edit: add E-mail de confirmation fieldset
+- Partage: contact always visible when provided (no contact_public field)
+- Partage: filter PACS from AP programs dropdown
+- Server-side validation: formats, jury, licence required (create + edit controllers)
+- Autofocus mappings for new validation errors
+- No duplicate asterisks — verified across all rendered fields
+- fix: add missing old() function in admin edit controller
+- refactor: move admin email field to Backoffice as Contact interne, never send email
+- Untrack admin.log (covered by .gitignore)
+
+---
+51f9f56e0995 | 2026-05-07 19:40
+Replace span with a link + href in about.php for credits
+
+---
+e0c748d8e7b0 | 2026-05-07 18:44
+Refactor about.php
+
+- Hardcode source code URL and credits in about template, remove from DB/admin interface; only contacts remains editable
+- Merge apropos editables into one À propos section, remove charte, add editable source code URL
+
+---
+24d68dda598e | 2026-05-07 17:52
+refactor form structure per new spec + fix
+- split jury into interne/externe/ULB,
+- remove president from student form,
+- add language_autre,
+- split duration into pages+minutes+annexes,
+- move licence to degrés d'ouverture with CC2r,
+- add license_custom,
+- filter PACS from student AP list,
+- editable généralités help block,
+- Libre toggle per settings
+
+Fix:
+- missing comma after cc4r column in schema.sql
+- remove duplicate form footer from partage template
+- remove couverture from student files fieldset; add promoteur ULB conditional disable via JS on Approfondi
+- promoteur ULB: remove 'si applicable', make required when visible
+
+---
+dce0e0b30194 | 2026-05-07 17:30
+schema: validate against new TFE field spec
+- add exemplaire_baiu, exemplaire_erg, cc4r, remarks;
+- add is_ulb to jury;
+- split jury_lecteurs into interne/externe in view;
+- refactor admin edit form with backoffice fields;
+- update public fiche to show promoteur ULB and split lecteurs
+
+---
+7793b6f86d4f | 2026-05-07 16:28
+add file export system for admins
+
+- ExportController: getAllThesisFiles(), buildExportManifest(), createExportZip()
+ builds a ZIP archive with manifest.json + files/ mirror of storage/theses/
+- Database: getAllThesisFilesForExport() queries all thesis_files + identifier
+- AdminLogger: logFilesExport() audit log entry
+- admin/actions/export-files.php: thin dispatcher, streams zip with headers
+- templates/admin/index.php: 'Exporter fichiers' button next to CSV export
+
+---
+821369f00468 | 2026-05-07 12:27
+exclude maintenance.flag from rsync deploy and git
+
+---
+3f87d71e389e | 2026-05-05 18:54
+Fix: CSV importer and imported data
+- pad rows, distinguish empty year, better error diagnostics
+- derive year from identifier when year column is empty
+- fix remaining 18 theses: Installation/Performance (slash→dash) orientation alias
+- csv importer: use column-name-based header detection instead of hardcoded positions
+
+---
+b063312642c5 | 2026-05-05 18:27
+centralise repertoire filter column rendering
+- shared repFilterEntry() and config array
+- shared repFilterEntry() and $filterColumns config array
+- fix single-valued FK fading via full intersection
+
+---
+bca707ee9687 | 2026-05-05 11:34
+standardise buttons: .btn base class (border-radius 10px, padding var(--space-xs))
+
+---
+b58445f71c84 | 2026-05-05 11:09
+sticky save/cancel buttons at top-right of admin edit page
+
+---
+95066de7b4d3 | 2026-05-05 10:31
+standardise multi-author support across all forms
+
+- ThesisCreateController: comma-split auteurice, sort alphabetically,
+ use setThesisAuthors() instead of hardcoded createThesis() author_id
+- Database::createThesis(): removed author_id param and hardcoded insert
+- Database::findDuplicateThesis(): accepts array of author names, matches
+ any shared author via IN + DISTINCT
+- ThesisEditController::save(): sort authors alphabetically on save
+- File folder naming: slug from all authors alphabetically sorted
+- v_theses_full GROUP_CONCAT: ORDER BY a.name ASC for deterministic display
+- Migration 012_author_view_order.sql: rebuilds view with alphabetical order
+
+---
+125c501f4080 | 2026-05-04 18:33
+Fix 403 on HTMX fragment requests: AdminAuth Basic Auth sets session key
+
+---
+37111eaac452 | 2026-05-04 18:19
+fix: add missing remote DB migrations and deploy-migrate recipe
+
+Four ALTER TABLE / CREATE TABLE statements were applied locally but never
+deployed to the remote production database, causing:
+
+- acces.php → 500: share_links.is_archived missing (ShareLink::listActive/listArchived)
+- parametres.php → 500: smtp_settings.notify_email missing (SmtpRelay::getSettings)
+- /tfe?id=N → redirect-to-home: thesis_files.sort_order missing (getThesisFiles ORDER BY)
+- admin_audit_log table missing (AdminLogger::insertDb, best-effort but noisy)
+
+Adds four pending migrations (008–011) covering all missing schema changes.
+Adds 'deploy-migrate' just recipe to run migrations on the remote after deploy.
+
+---
+ae6d9b86b32a | 2026-05-04 17:52
+Replace browser alert/confirm dialogs with