apropos/licence/charte use .page-content as a nested scroll container
(overflow-y:auto) inside html/body {height:100%; overflow:hidden} flex.
Firefox drops/clips that container's own padding-bottom (and last-child
margin) from the scrollable area -- scrollHeight came out ~80px short and
the last ~80px of content was unreachable. Chromium honours the padding.
Move the trailing space off the container padding onto a real content
spacer (.page-content > article::after, display:block; height) which both
browsers count as scrollable content. Verified via Playwright (Chromium +
Firefox): delta between article and scrollHeight is now 0 in both on all
three pages, consistent at all breakpoints. not-found unaffected (loads
its own not-found.css). 294 PHPUnit tests pass.
Replace the eager <img> on the home page with an htmx placeholder <figure>
that fetches a /cover-fragment endpoint when it scrolls into view
(hx-trigger="revealed"), so heavy cover bytes load only on demand.
Add spinner + settle-fade transition CSS, and load htmx.min.js on home.
Ajoute une fonction SQLite UDF accfold() (app/src/AccentFolding.php) qui plie les caractères accentués latin vers leur base ASCII (é→e, ç→c, …), et l'applique à chaque condition de recherche côté colonne ET côté terme (accfold(column) LIKE accfold(:term)). Comme le runtime n'a ni intl (Normalizer) ni iconv, le repli passe par une map de translitération manuelle (Western Latin-1 + Latin Extended-A, NULL-safe).
Couvert :
- recherche publique (searchTheses / countSearchResults) : titre, sous-titre, synopsis, auteurs, promoteurs, tags ;
- popover étudiant (getThesesByAuthorName / getThesesForAuthors) : a.name ;
- recherche backoffice (getThesesList / getThesesListCount) : titre, sous-titre, identifiant, auteur.
L'UDF est enregistré dans Database::registerSqliteFunctions() (appelé par le constructeur et par le harnais de test TestDatabaseInstance). Évite aussi la dépréciation PHP 8.5 de PDO::sqliteCreateFunction() : la connexion est créée via \Pdo\Sqlite quand disponible (createFunction()) avec repli sur PDO (sqliteCreateFunction()) sur les anciens runtimes ; les tests passent de @dataProvider docblock à l'attribut #[DataProvider] (fin de la dépréciation PHPUnit). Détache .php-cs-fixer.cache (gitignoré) pour ne plus polluer le working copy.
294 tests PHPUnit verts sans dépréciation, phpstan niveau 5 OK.
- 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).
- 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).
The openEditDialog JS function didn't populate the locked_year input
in the edit dialog, and the PHP template didn't pass locked_year as an
argument. Any edit to a link (even just changing the name) would clear
the locked academic year to NULL.
- Pass $linkLockedYear as 5th argument to openEditDialog in the template
- Accept and populate edit-locked-year input in the JS handler
- 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
- fix page nav buttons, smaller default pages, violet bg
- Build toolbar after renderAllPages so it queries existing canvases;
query canvases fresh in the scroll handler so page tracking works
after re-render (zoom in/out).
- Default data-pdf-scale 0.85 (JS fallback 0.8) so pages are a bit
smaller by default.
- Add dark transparent violet background (accent-secondary 22%) to
.pdf-expanded, behind the pages.
- expand to fill column top-to-bottom via CSS flex
Use a flex chain (.tfe-files flex:1, .tfe-file-item:has(.pdf-expanded)
flex:1) so the expanded PDF wrapper fills the right column from under
the header to the bottom of the viewport, instead of viewport-based
clamp math that didn't account for the header height.
- fill right column, center pages, freeze column on expand
The expanded viewer now stays inside the right column instead of
growing the page: the column switches to overflow:hidden while the
PDF panel scrolls internally. Pages are centered horizontally via
flexbox align-items: center.
- fix toolbar not full-width, hide sibling file items
The toolbar was constrained by the parent's align-items:center; fixed
with align-self:stretch. Hiding sibling .tfe-file-item elements when
a PDF is expanded prevents them from rendering above the overlay.
- disable overscroll on expanded view
- use overscroll-behavior-y on container, none on toolbar
- disable body overscroll via .pdf-viewer-open class
- mobile full-viewport, iframe matches pdf height
- mobile overlay above everything via position:fixed
- opaque dark-violet bg on mobile overlay
- tfe: add 'Open in new tab' link above website iframes
- trim column padding when expanded for more vertical space
- 5% zoom increments, subtle padding on expanded column
- prevent horizontal overflow/overscroll