TODO: add form accessibility & resilience tasks from assessment

This commit is contained in:
Pontoporeia
2026-06-11 10:14:37 +02:00
parent 2c6b55777f
commit c0ba99e861

98
TODO.md
View File

@@ -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 `<small>` 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 `<small>` 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 `<input type="hidden" name="filepond_mode" value="1">`. 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 `<input type="hidden" name="filepond_mode" value="0" disabled>` 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/<slug>/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 `<fieldset>` (standards compliance)
**Current state:** `checkbox-list.php` and `fichiers-fragment.php` put `required aria-required="true"` on `<fieldset>` elements. The `required` attribute is not valid on `<fieldset>`. 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 `<html>` 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 ~4060 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';`