- fins de ligne \r\n/\r → \n,
- blocs de lignes vides → \n\n, è
- espaces/tabulations/NBSP → espace unique,
- trim.
Les retours à la ligne simples intra-paragraphe sont LAISSÉS INTACTS :
une coupure de ligne peut être une vraie frontière de mot (→ espace)
ou une coupure du mot ("dyna\nmiques" → "dynamiques"),
indistinguables sans dictionnaire — automatiser corromprait l'entrée
("poursuivantsur").
Migration batch 045 (backfill des données existantes) RETIRÉE : le
nettoyage automatique fiable des synopsis existants est impossible.
Chaque modification est testée (277 tests PHPUnit).
(heure de dépôt was showing UTC)
- feat: add date_depot column (real TFE deposit date)
with CSV round-trip + Brussels→UTC sanitization
- fix: keep PHP default tz at UTC to preserve token/share-link
expiry consistency; convert to Brussels only in db_datetime()
.tfe-main (main) is a block container by default, so .tfe-layout's
height:100% and .tfe-content-row's flex:1 never resolved to a
definite height. The columns' overflow-y:auto thus had nothing to
constrain, and main's overflow:hidden clipped the content — scroll
was visually broken. Adding display:flex lets the column/height
chain resolve so both columns scroll independently as intended.
- admin parametres: move Compte administrateur into Identifiants as a fieldset
- admin parametres: space the account fieldset and drop confirm-password top border
- admin parametres: tighten param-form row spacing, relabel password button
- admin parametres: move password submit button outside the account fieldset
Clicking orientation, AP program, finality, year, format, or keyword links
on the TFE page was passing both a dedicated filter param AND a query=
param. The query= text search doesn't look at the filter columns
(orientation, ap_program, etc.), so the AND combination yielded zero
results when the filter value didn't appear in title/authors/etc fields.
Also fix the language link: it was passing language as query= which
searches only title/authors/etc, not the languages column. Now uses
the dedicated language= param (DB layer already supported it, and
collectSearchParams now collects it).
FilePond 4.x wraps the original input element in its own DOM structure
which can break CSS attribute selectors. querySelector with
.tfe-file-picker[data-queue-type='...'] returned null even though the
element existed and was initialized.
Switched to getElementById with a queueType→inputId map:
cover → couverture
note_intention → note_intention
tfe → tfe-files-input
annexe → annexe-files-input
getElementById is both faster and immune to FilePond's DOM wrapping.
Switched from pond.addFile(type:'limbo') to pond.addFiles([{source, options:{type:'local'}}])
in both XamxamRelinkFile and XamxamRelinkPeerTube.
addFile with type 'limbo' doesn't trigger server.load to fetch the file
for the preview/thumbnail. The data-existing-files format (type:'local')
correctly tells FilePond to call server.load for the file identified by
the source (DB ID), rendering it properly in the pool.
- 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.
Wrap .log-output and the copy button in a .log-output-wrapper div
with position:relative, so the button sits outside the overflow-x:auto
scroll container and stays fixed at the top-right corner.
- Override aggressive word-break: break-word from base.css
with word-break: normal + overflow-wrap: break-word + hyphens: auto
on .tfe-title, .tfe-author, .tfe-meta-item
- Replace regular spaces before colons with in all meta labels
(French typographic rule: non-breaking space before double punctuation)
- Rebuild tfe.min.css
Log level: production default was Level::Warning, but all facades
(AppLogger, AdminLogger, Audit) write at Monolog INFO level. This
silently discarded submission, admin action, and audit logs when
LOG_LEVEL env var was unset. Changed to Level::Info.
PeerTube: replaced raw error_log() calls in PeerTubeService::upload(),
deleteVideo(), FilepondHandler::process(), ThesisCreateController,
and ThesisFileHandler with structured logging:
- Successes → Logger::get('app')->info() (visible in App — soumissions tab)
- Failures → ErrorHandler::log('peertube_*', ...) (visible in Erreurs tab)
Added require_once for Logger and ErrorHandler in PeerTubeService.
redirect internal metadata links from /repertoire to /search, add query param for search bar prefill, fix filter visibility on desktop
redirect metadata links from /repertoire to /search, prefill search bar, fix filter visibility
link metadata to /search, preserve keyword filter in form, show active keyword chip
Replace var(--step-5)/--step-3/--step-1 for h1/h2/h3 with explicit
clamp() values. New hierarchy: h1 ~2rem, h2 ~1.5rem, h3 ~1.2rem.
Also update .content-section-title to use direct clamp (was --step-3,
now matches h2). Mobile override swapped from --step-2 to explicit
clamp. Kept --step-0 for body text (consistent with site baseline).
Switch from swapping the entire #repertoire-index block to targeted
hx-swap-oob swaps: only the <ul> list elements are replaced, while
section headers and accordion chrome stay in the DOM untouched.
- Filter column <ul>s get IDs (rep-list-years, rep-list-ap, etc.)
and render with hx-swap-oob=true on HTMX requests
- Students <ul> gets id=rep-students as main swap target
- Filter buttons now target #rep-students instead of #repertoire-index
- Server sets $isOob=true when rendering HTMX partial responses
- Accordion re-init on swap no longer needed (headers never replaced)
- Scroll-restore updated to capture/restore per <ul> ID
Fading fixes:
- Restore rep-entry--faded class with simplified logic: any active
filter fades entries whose matched flag is false (removed the
$colHasMatch gate that prevented fading when a column had no matches)
- Add $noResults guard: when matched_ids is empty, force-fade all
non-selected entries across ALL columns
- Fix keyword matched computation: use full intersection
($buildWhere('__none__')) instead of excluding the keyword filter.
Previously keywords were matched against only non-keyword filters,
so selecting a keyword would not narrow the available keyword set,
causing entries to appear valid that would yield zero results.
Switch from swapping the entire #repertoire-index block to targeted
hx-swap-oob swaps: only the <ul> list elements are replaced, while
section headers and accordion chrome stay in the DOM untouched.
- Filter column <ul>s get IDs (rep-list-years, rep-list-ap, etc.)
and render with hx-swap-oob=true on HTMX requests
- Students <ul> gets id=rep-students as main swap target
- Filter buttons now target #rep-students instead of #repertoire-index
- Server sets $isOob=true when rendering HTMX partial responses
- Accordion re-init on swap no longer needed (headers never replaced)
- Scroll-restore updated to capture/restore per <ul> ID
Fading fixes:
- Restore rep-entry--faded class with simplified logic: any active
filter fades entries whose matched flag is false (removed the
$colHasMatch gate that prevented fading when a column had no matches)
- Add $noResults guard: when matched_ids is empty (zero theses match
all active filters), force-fade all non-selected entries across ALL
columns. This covers the keyword edge case where per-column matched
is computed excluding the keyword filter (many-to-many), causing
keywords to appear valid even though the full intersection is empty.
Remove the system that sorted valid (matched) filter entries to the top and
faded/disabled unmatched ones when any filter was active. Entries now stay in
their natural alphabetical/numeric order regardless of selection state.
- Drop usort calls that reordered entries by matched status
- Simplify repFilterEntry: remove anyActive and colHasMatch params
- Remove colHasMatches computation
- Remove rep-entry--faded CSS rule
- All filter buttons remain clickable at all times