- Single unified 'Corbeille' table replaces the two separate lists
(stale/orphelin + restorable). Each row has a Statut column showing
'↺ Restaurable' or 'Orphelin', plus a checkbox for bulk ops.
- Checkboxes on restorable rows carry data-restorable='1' attribute
so the bulk JS can distinguish them.
- Bulk actions bar now has both 'Supprimer la sélection' and
'↺ Restaurer la sélection' buttons. The restore button only appears
when at least one restorable item is checked.
- New cleanupBulkRestore() JS function populates a dedicated hidden
form and confirms before submitting. Only restorable items are sent.
- restore-trash.php refactored to handle both single (trash_file) and
bulk (trash_files[]) inputs via a loop, accumulating restored/skipped
counts and re-rendering the fragment on HTMX requests.
Two critical fixes:
1. Relink flow no longer destroys/recreates FilePond instances:
The relink (XamxamRelinkFile) and PeerTube relink (XamxamRelinkPeerTube)
previously refreshed the entire fichiers fragment via HTMX after
pond.addFile(). This triggered destroyFilePondsIn on ALL pools, which
could fire server.remove callbacks and move existing files to corbeille.
Now just closes the modal — the file is already added to the pool in-place,
and syncOrderInput creates the hidden form input.
2. Cleanup page « Corbeille (restaurable) » now actually shows files:
_cleanup-stats-data.php previously classified trash files by checking if
the thesis_files DB row still existed. But both deleteThesisFileToTrash
and FilepondHandler::handleRemove DELETE the DB row. So ALL trash files
appeared as `stale` (not restorable). Now uses the JSON sidecar file
presence as the classification criterion — if the sidecar exists and is
recent, the file is restorable regardless of DB row state.
Also removed unused DB query from _cleanup-stats-data.php.
- Fix createThesis SQL: extra ? placeholder in VALUES (27 values for 26 columns)
- Add createThesis integration tests to catch column/value mismatches
- Add CSV_COLUMNS as single source of truth in ExportController, deriving
csvHeaders() and positional fallback from it
- Add missing columns to export query + CSV: duration_pages, duration_minutes,
has_annexes, license_custom, contact_visible, objet
- Add missing columns to import INSERT: license_id, license_custom, cc2r,
exemplaire_baiu, exemplaire_erg, objet, contact_visible, duration_pages,
duration_minutes, has_annexes
- Resolve license name → license_id during import
- Fix XamxamInitFilePonds: add file-upload-filepond.js to admin-entry.js
so FilePond initialization code is available on the admin list page
- Add FilePond vendor CSS to admin.min.css bundle (import dialog styling)
- Generate .admin-file-hint from csvHeaders() instead of hardcoded stale list
- Use positional fallback from CSV_COLUMNS for import cell parsing
- Remove Mo option from duration, keep only pages and minutes
- Redesign duration fieldset: separate Pages input + Durée h:m inputs, both can be set together
- Fix minutes input visibility: wider inputs (6ch), proper CSS layout
- Add has_annexes checkbox to fichiers fragment + DB column + controllers
- Display duration on admin backoffice recap page
- Display duration on public partage recap page
- Update public TFE page for new combined duration format
- Migration 041: add duration_pages, duration_minutes, has_annexes columns; migrate data; recreate views
Unify the three public pages (à propos, charte, licence) onto a single
grid layout (.page-content) with sticky TOC sidebar, replacing the old
separate / / markup.
- Merge about.php, charte.php, licence.php templates into shared
.page-content / .content-section structure
- Add CommonMark HeadingPermalinkExtension for stable heading anchors
- Use SlugNormalizer for TOC links so they match rendered heading IDs
- Standardize link styling across content blocks: bold black, accent on
hover (consistent with global link style)
- Fix code block wrapping: use pre-wrap instead of pre, constrain grid
columns with min-width:0, auto scrollbar
- Fix apropos page grid placement: force content-section into column 2
so contacts and credits stay in the content area, not the sidebar
Also includes accumulated WIP changes:
- Header gradient: hardcoded purple-to-green (replaces CSS variables)
- Search placeholder font
- Duration field: replace minutes/sec/heures with h:m:s time inputs
- TFE file optional for formats 1,4,6 with client-side JS toggle
- Licence form: em-dash to hyphen, details/summary classes
- Pill search: block Enter key form submission when no results
- Draft autosave: remove CSRF rotation (broke concurrent FilePond uploads)
- Language pill: clear hints for excluded main languages
- Search results: gradient placeholder cards for items without covers
- TFE display: format durée values as XhYm instead of decimal
- New fragment endpoint POST/GET /partage/fragments/draft.php:
saves all form fields to PHP session, excludes file/csrf/slug fields
GET returns JSON for JS hydration on page load
rotates both global CSRF and share CSRF tokens in sync
- form.php accepts optional $formExtraAttrs and $showAutosaveStatus:
allows injecting HTMX attributes and 'Brouillon enregistré' indicator
- renderShareLinkForm adds hx-post with change/input debounce trigger,
loads autosave-handler.js, hydrate fields from draft on page load
- Draft cleared on successful form submission in handleShareLinkSubmission
- autosave-handler.js now also updates share_link_token hidden input
when rotating CSRF token (partage form uses both csrf_token and share_link_token)
- Added .autosave-status CSS to form.css (was admin.css-only)
- Updated fragment routing to accept GET requests (needed for draft hydration)
WCAG 3.3.1 (Error Identification): failing fields now get
aria-errormessage pointing to the flash-error container and
aria-invalid="true". WCAG 3.3.3 (Error Suggestion): <small>
hint text on inputs, selects, and file fields is now linked via
aria-describedby (always, not just on error).
Changes:
- text-field.php, select-field.php, checkbox-list.php: accept
$errorFieldName; add aria-errormessage/aria-invalid on match;
add id to <small> and aria-describedby on the control
- fieldset-tfe-info.php: aria-invalid on synopsis textarea
- fichiers-fragment.php: aria-describedby on cover, note
d'intention, TFE, annexes, and website inputs; aria-invalid
on format checkboxes when error matches 'formats'
- form.php: id="flash-error" + tabindex="-1" on flash-error
div; accept $errorFieldName from callers
- admin/add.php: set $errorFieldName, wire $withAutofocusFn
(was identity default)
- admin/edit.php: set $errorFieldName
- partage/index.php: consume autofocus field, wire autofocus
function, add App::flashAutofocus() in submit catch block
Also fixes WCAG standards issue: removed invalid 'required'
HTML attribute from <fieldset> elements in checkbox-list.php
and fichiers-fragment.php (only aria-required stays). Added
role="group" for explicit ARIA semantics.
validateAndSanitise() no longer cross-contaminates:
- contact_interne overwrote mail, which then copied to contact_visible
- Fixed: contactInterne from contact_interne (admin) or confirmation_email (student)
- Fixed: contactVisible from contact_visible (admin) or mail (student)
- Fixed: submit() uses contactInterne as author email, not mail
- Identifiant: mise à jour automatique quand l'année change en back-office (updateThesis + ThesisEditController)
- Contact: hint enrichi (1 seul contact, formatage Instagram/Mastodon)
- Fichiers: TFE rendu optionnel pour Site web/Performance/Installation (note d'intention reste obligatoire)