Scope: variables.css, search.css, todo/04-accessibility.md
- variables.css: add @media (prefers-color-scheme: dark) block scoped to
body:not(.admin-body); overrides all semantic tokens with dark equivalents:
--bg-* (#111→#333 range), --text-* (#eee/aaa/777),
--border-* (#333/#444), --accent-primary lightened to #b87fd4
(4.5:1 contrast on #111 background), --accent-secondary stays #9557b5,
--accent-foreground flipped to #111111 for dark buttons,
--accent-muted adjusted to rgba(184,127,212,0.15),
status colours muted for dark (success #4db886, error #e05555,
warning #d4a830); new --search-error-{bg,border,color} tokens added
to :root (light: #fff0f0/#c00) and overridden in dark (#2a1515/#e05555)
- search.css: replace three hardcoded hex values in .search-error rule
with var(--search-error-bg/border/color) so dark mode applies cleanly
- Admin pages are entirely unaffected: .admin-body body class is excluded
from the dark-mode selector; system.css already has its own dark palette
Add App::flashAutofocus(fieldName) and consumeAutofocus() to the thin App
helper so action handlers can identify which field caused a validation error
and the form page can move browser focus directly to it on reload.
Changes:
- src/App.php — flashAutofocus() stores field name in _flash_autofocus
session key; consumeAutofocus() drains it and returns the name (or null)
- actions/formulaire.php — catch block maps exception messages to field
names (auteurice, titre, synopsis, année, orientation, ap, finality,
languages, tag, lien) and calls App::flashAutofocus()
- actions/edit.php — catch block maps common edit errors to field names
and calls App::flashAutofocus()
- add.php — consumes the hint via App::consumeAutofocus() into
$autofocusField; withAutofocus() helper merges autofocus=>true into
$attrs for every field include; synopsis textarea gets inline autofocus
- edit.php — same pattern with inline ternary merges and textarea autofocus
- templates/partials/form/text-field.php — $attrs loop now emits bare
attribute names (no ="...") when value === true, supporting autofocus,
disabled, readonly etc. without special-casing
- templates/partials/form/select-field.php — same boolean-attr support
added; $attrs variable initialised to [] when caller omits it
Closes WCAG 3.3.1 autofocus item in todo/04-accessibility.md.
Problem: <video> elements on tfe.php had no <track kind="captions"> element,
violating WCAG 4.1.2 (name, role, value) for video content.
Changes:
- public/tfe.php: collect all text/vtt files from the thesis file list before
rendering; skip standalone rendering of .vtt entries; for each MP4 emit a
<track kind="captions" srclang="fr" label="Sous-titres" default> pointing
to the N-th VTT file (N-th video paired with N-th caption in document order)
- public/media.php: add text/vtt to allowed MIME list; normalise finfo
text/plain -> text/vtt for .vtt files; add vtt branch to cache/header
block (Content-Type: text/vtt; charset=utf-8, 1-day cache)
- public/admin/actions/formulaire.php: allow .vtt uploads (text/vtt MIME,
vtt extension); normalise text/plain finfo result; set file_type='caption'
for VTT files so they are distinguishable from other thesis files
- public/admin/add.php: extend files field accept attr to include .vtt;
update hint text to document the VTT sidecar convention
VTT files uploaded under theses/ inherit the same access_type visibility
gate in media.php as all other thesis content (403 for access_type_id=3).
- admin/edit.php: remove mb_strimwidth(60) truncation from access_type
<select> option labels; full 'name — description' text is now the
accessible name so screen readers get unambiguous option text (WCAG 4.1.2)
- public/assets/favicon.svg: new public favicon — brand-purple (#9557b5)
rounded square with white 'P' lettermark; distinct from admin_favicon.svg
(archive-restore Lucide icon in #c104fc) which is admin-only
- templates/head.php: favicon <link> now conditionally serves favicon.svg
(public pages) or admin_favicon.svg (admin pages) based on $isAdmin;
closes the open favicon task in todo/01-css-semantic-refactor.md
- todo/04-accessibility.md: mark WCAG 3.1.1 lang audit and WCAG 4.1.2
select truncation items as done
- todo/01-css-semantic-refactor.md: mark favicon task as done
3.3.2 Labels or instructions
- Replace bare <label>Lecteur·ices :</label> (no 'for', no associated control)
with <fieldset class="admin-jury-lecteurs"><legend>Lecteur·ices</legend>
giving AT a proper programmatic label for the entire lecteur group
4.1.2 Name, role, value — Externe checkboxes lacked group context
- Add aria-label="Promoteur·ice — externe" on the promoteur Externe checkbox
- Add aria-label="Lecteur·ice N — nom" on every lecteur name input
- Add aria-label="Lecteur·ice N — externe" on every lecteur Externe checkbox
- All three attributes added to both PHP-rendered rows and the addJuryRow() JS
that builds new rows dynamically
2.1.1 Keyboard — remove buttons already had aria-label; verified and updated
label text to "Supprimer le lecteur·ice N" (consistent with new numbering)
CSS (admin.css)
- Add .admin-body fieldset fieldset.admin-jury-lecteurs rule: removes
border/padding/background from the nested fieldset so it reads as a
sub-group inside the outer jury fieldset, not a double-bordered card
Audit (no code change)
- WCAG 1.4.4: all font-size values use rem — no px text sizing anywhere
- WCAG 1.4.12: only overflow:hidden on media containers and .sr-only utility;
no essential text content is clipped by text-spacing overrides
- WCAG 4.1.2 bulk JS: result is a redirect to flash-messages.php which already
emits role="alert"/role="status" — no additional JS announcement needed
Add <span class="sr-only">, YEAR</span> to each thesis card <p> in
public/index.php. Screen readers now read "Author – Title, 2024" instead
of bare "Author – Title", so two theses sharing the same title produce
distinct accessible names (WCAG 2.4.4 Link Purpose — In Context).
Also audit and close WCAG 2.4.3: the tfe.php back link (<a class="tfe-back-link">
← Retour</a>) is already the first child of <header class="tfe-left">
in DOM order, preceding <h1 class="tfe-title">. No code change needed;
TODO item marked done.
- Updated 6 admin templates: add.php, edit.php, login.php, account.php,
import.php, pages-edit.php — replaced <div class="admin-submit-wrap">
with <div class="admin-form-footer">
- Updated 8 CSS selectors in admin.css:
- .admin-form-footer { margin-top/padding-top } (was .admin-submit-wrap)
- .admin-form > div:not(.admin-form-footer) grid exclusion guard (×3)
- .admin-login-box .admin-form > div:not(.admin-form-footer) overrides (×2)
- .admin-login-box .admin-form-footer compact spacing override
- No visual change; purely a semantic rename to a descriptive class name
- Also marked status-badge.php partial and WCAG 1.3.1 badge tasks as
already-done in todo/02-php-components.md and todo/04-accessibility.md
(partial + CSS were fully implemented but todo had not been updated)
WCAG 1.4.1 — Active nav link had no non-colour indicator in the admin panel.
Public nav already had border-bottom via common.css; admin nav had nothing.
admin.css:
- Add `[aria-current="page"]` rule on admin nav links:
border-bottom: 2px solid currentColor; padding-bottom: 1px
This gives a visible underline as a non-colour signal for the active page.
- Fix `--admin-purple` undefined CSS variable in pagination button hover.
The variable was referenced but never defined in variables.css (which was
refactored to use --accent-primary / --accent-secondary). Replaced both
border-color and color usages with var(--accent-primary) (#9557b5 — same
value), restoring the intended purple hover tint on pagination buttons.
todo/01-css-semantic-refactor.md:
- Audited ~15 pending CSS/HTML tasks; all were already implemented.
Marked as done: .admin-main, .admin-page-title, .admin-form-row,
.admin-label, .admin-input/select/textarea, .admin-table, .admin-fieldset,
tfe.css class replacements, search.css h2 selector, admin-alert replacement,
login.php/edit.php inline style removal, form partial hints (<small>).
todo/04-accessibility.md:
- Marked WCAG 1.4.1 admin nav and --admin-purple audit items as completed.