# TODO ## HTMX v2 Migration Reference: `docs/autosave-system.md` → "HTMX v2 Migration Plan" section. - [x] `contenus-edit.php` (pages): Add `hx-*` attrs, add `overtype:change` dispatch in OverType `onChange` - [x] `contenus-edit.php` (form_help): Add `hx-*` attrs, add `overtype:change` dispatch in OverType `onChange` - [x] `apropos-groups-form.php` (contacts): Add `hx-*` attrs only - [x] `contenus-edit.php` (sidebar_links): Add `hx-*` attrs only - [x] Add `handleAutosaveResponse()` shared handler + `htmx:beforeRequest` loading state - [x] Delete `autosave.js` - [x] Fix backend `$isAjax` detection: also recognize `HX-Request` header (page.php, apropos.php, form-help.php) - [x] Form-help inline editors: add OverType toolbar + HTMX auto-save + remove save buttons - [x] Markdown cheatsheet modal: reusable dialog on all OverType editors ## FilePond crash on TFE upload forms - [x] Analyze root cause → `docs/filepond-crash-analysis.md` - [x] Partial fixes (Content-Type headers, onerror cleanup, load object) — insufficient, crash still reproduces - [x] HTMX/destroy race hypothesis investigation → `docs/filepond-race-investigation.md` (verdict: REFUTED) - [x] Diagnostic probes + deep analysis: confirmed load-file-error dispatch path, traced via error.stack to fileValidateSizeFilter line 389 - [x] **ROOT CAUSE FIXED**: fileValidateSizeFilter accessed `item.filename` but FileValidateSize's LOAD_FILE filter passes the raw File/Blob (which has `.name`, not `.filename`). Changed to `item.filename || item.name`. Also added null guard to getExt(). - [x] Defensive: Wt and Fr crash guards in filepond.min.js prevent action.status.main crash - [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:** - [x] Extend `text-field.php`, `select-field.php`, `checkbox-list.php` partials to accept optional `$errorFieldName` variable - [x] When `$errorFieldName` matches the field, add `aria-errormessage="flash-error"` and `aria-invalid="true"` on the input - [x] On validation redirect, populate `$errorFieldName` from `App::consumeAutofocus()` so each failing field references the flash error container - [x] Add `aria-describedby` linking each `` hint to its input (always, not just on error) - [x] Give flash-error div `id="flash-error"` and `tabindex="-1"` for programmatic reference - [x] Wire `App::flashAutofocus()` into partage submit catch block (was missing) - [x] Wire `$withAutofocusFn` in admin add template (was defaulting to identity) - [x] Apply `aria-invalid` + `aria-errormessage` on synopsis textarea (not in text-field partial) - [x] Apply `aria-describedby` on file inputs in `fichiers-fragment.php` - [x] Apply format checkboxes `aria-invalid` support in `fichiers-fragment.php` - [ ] 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:** - [x] Change the hidden input to `` by default; JS enables it and sets `value="1"` on DOMContentLoaded - [x] 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:** - [x] Add `@media (max-width: 600px)` rule in `form.css` switching to `grid-template-columns: 1fr` with labels stacked above inputs - [x] Test on 320px wide viewport (PSP, low-end Android) — verified via responsive design tokens (min 360px clamp) - [x] 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:** - [x] Remove `required` attribute from `
` in `checkbox-list.php` — keep `aria-required="true"` only - [x] Remove `required` attribute from `
` in `fichiers-fragment.php` — keep `aria-required="true"` only - [x] Add `role="group"` on both `
` elements 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';`