From c0ba99e8615114b5324167b6049e5cf4fc684401 Mon Sep 17 00:00:00 2001 From: Pontoporeia Date: Thu, 11 Jun 2026 10:14:37 +0200 Subject: [PATCH] TODO: add form accessibility & resilience tasks from assessment --- TODO.md | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/TODO.md b/TODO.md index c1ca336..74b9690 100644 --- a/TODO.md +++ b/TODO.md @@ -25,3 +25,101 @@ Reference: `docs/autosave-system.md` → "HTMX v2 Migration Plan" section. - [x] process.onload: replaced throw with error-marker return (prevents FilePond crash when server returns HTML) - [x] Routing: partage index.php now routes /partage/actions/* directly to PHP files (was treating 'actions' as a slug and returning full HTML page) - [x] **All crashes resolved** — verified working on partage form + +## Form Accessibility & Resilience — Assessment Follow-up + +Reference: Assessment against progressive-enhancement / WCAG-AA / "never lose data" / low-common-denominator guidelines. + +### 1. WCAG AA: Add field-level `aria-errormessage` + `aria-invalid` to TFE form + +**Current state:** Flash error divs have `role="alert"` but individual fields are never linked to their error via `aria-errormessage`. The `autofocusFieldForError()` mechanism focuses the field after a validation redirect but does not announce the error to screen readers. Help `` text is not linked via `aria-describedby`. + +**To do:** +- [ ] Extend `text-field.php`, `select-field.php`, `checkbox-list.php` partials to accept optional `$error` and `$errorId` variables +- [ ] When `$error` is set, add `aria-errormessage="$errorId"` and `aria-invalid="true"` on the input +- [ ] On validation redirect, populate per-field error IDs so each failing field references the flash error container +- [ ] Add `aria-describedby` linking each `` hint to its input (always, not just on error) +- [ ] Test with VoiceOver and NVDA on the full add/edit/partage form flows + +### 2. No-JS file uploads silently fail (data loss) + +**Current state:** `form.php` hardcodes ``. Without JS, no `queue_file[]` hidden inputs are populated → server gets `filepond_mode=1` with empty queue → all files silently dropped. The form is supposed to work without JS. + +**To do:** +- [ ] Change the hidden input to `` by default; JS enables it and sets `value="1"` on DOMContentLoaded +- [ ] Add server-side fallback in `ThesisCreateController::submit()` and `ThesisEditController::save()`: when `filepond_mode=1` but no `queue_file` data is present, fall through to the legacy `$_FILES` path +- [ ] Test end-to-end: submit the partage form with JS disabled, verify files arrive via `$_FILES` + +### 3. Autosave text fields on partage form + +**Current state:** Only FilePond files are persisted to the server before submit. Text/select/checkbox fields live only in DOM and are lost on tab close / crash / network failure. `beforeunload-guard.js` warns but cannot recover data. The admin panel already has an HTMX-based autosave pattern (`contenus-edit.php`). + +**To do:** +- [ ] Implement a per-session draft endpoint (`POST /partage//draft`) that saves form data to a session-backed store +- [ ] On field blur/change, POST the field name+value via HTMX (debounced 500ms) +- [ ] On page load, hydrate all fields from the draft endpoint +- [ ] Draft is cleared on successful submission; expires after 24h with session +- [ ] Add a visible "Brouillon enregistré" indicator to reassure the student + +### 4. FilePond temp file IDs lost on partage validation redirect + +**Current state:** After a validation error, `storePrimedFiles()` shows file *names* so the user knows what to re-select. But the actual FilePond hex IDs (server-side temp files) are not preserved — the user must re-upload everything. For large files on slow connections, this is painful. + +**To do:** +- [ ] After a validation error in the partage form, preserve FilePond temp file IDs in `$_SESSION['share_filepond_ids_' . $slug]` by queue type +- [ ] Use these IDs to build the `data-existing-files` JSON attribute on the re-rendered form, seeding the FilePond pools +- [ ] Ensure the temp files' server-side lifetime covers at least one validation round-trip (extend `FilepondHandler` TTL if needed) + +### 5. Two-phase commit: protect against crash during file moves + +**Current state:** DB transaction commits, then files are moved from tmp to storage. If the process crashes between COMMIT and file move completion, the DB has a thesis record with no files (or partial files). This is a public-facing service — no acceptable failure rate. + +**To do:** +- [ ] Add an `is_published` or `status` flag to theses table (if not already present) +- [ ] In the partage submit flow: INSERT thesis with `status='draft'` inside the transaction → COMMIT → move files → UPDATE `status='active'` +- [ ] On partage submission error recovery, roll back draft theses that are older than N minutes with no files +- [ ] Add a periodic cleanup job (`just cleanup-drafts`) for orphaned drafts + +### 6. Mobile-responsive form layout + +**Current state:** Form field rows use `grid-template-columns: 260px 1fr` with no breakpoint. Below ~520px viewport width, labels wrap and inputs are cramped. + +**To do:** +- [ ] Add `@media (max-width: 600px)` rule in `form.css` switching to `grid-template-columns: 1fr` with labels stacked above inputs +- [ ] Test on 320px wide viewport (PSP, low-end Android) +- [ ] Ensure touch targets are at least 44×44px (WCAG 2.5.5) + +### 7. Split form.css: shared base vs admin-only styles + +**Current state:** `form.css` is 36KB (1,500+ lines). The partage form loads all of it but uses only ~40% (base input/fieldset styles). Admin-specific styles (`.admin-jury-*`, `.admin-backoffice-*`, etc.) are dead weight for students. + +**To do:** +- [ ] Split `form.css` into `form-base.css` (~15KB: inputs, selects, textareas, fieldsets, legends, validation states, flash messages, `aria-invalid` styling) and `form-admin.css` (~21KB: admin layout, jury fieldset, backoffice, tag pills, autocomplete) +- [ ] Load `form-base.css` in the partage form; load both in admin forms +- [ ] Update `head.php` to support `$extraCssAdmin` for admin-only stylesheets + +### 8. Fix `aria-required` on `
` (standards compliance) + +**Current state:** `checkbox-list.php` and `fichiers-fragment.php` put `required aria-required="true"` on `
` elements. The `required` attribute is not valid on `
`. Screen readers may not interpret this correctly. + +**To do:** +- [ ] Move `required` + `aria-required` to individual checkboxes inside the group +- [ ] For checkbox groups (languages, formats): mark at least one checkbox as `required`, keep `aria-required="true"` on the fieldset (without the HTML `required` attribute) +- [ ] Add `role="group"` on the fieldset for explicit ARIA semantics + +### 9. Refactor partage form page wrapper to a template + +**Current state:** `renderShareLinkForm()` in `partage/index.php` outputs an entire `` document inline (~120 lines of HTML inside a PHP function). This duplicates `head.php`/`header.php` logic. + +**To do:** +- [ ] Extract the partage form page chrome to `templates/partage/form-page.php` +- [ ] Use `App::render()` or a dedicated `renderPartageForm()` helper +- [ ] Share meta tags, favicon references, CSRF meta, and live-reload script from `head.php` + +### 10. Reduce form bootstrap duplication across 3 entry points + +**Current state:** `admin/add.php`, `admin/edit.php`, and `partage/index.php` (inside `renderShareLinkForm()`) each repeat ~40–60 lines of identical setup: load `ThesisCreateController`, call `loadFormData()`, build `$helpFn`, load site settings, set up jury/default access variables, etc. + +**To do:** +- [ ] Add a `ThesisFormSetup` helper class (or static method on the controllers) that returns a standardised array of form variables +- [ ] Each entry point becomes: `$formVars = ThesisFormSetup::prepare($mode, $slug); extract($formVars); include '...form.php';`