Commit Graph
100 Commits
Author SHA1 Message Date
Pontoporeia 0979267c68 stretch heading scale (h1→step-5, h3→step-1) + reduce heading bottom margin (space-l → space-m) 2026-07-06 19:51:20 +02:00
Pontoporeia 148ffe79f0 fix: search bar icon/placeholder overlap caused by specificity conflict
The base input rule uses a high-specificity :not() chain (0,8,1) that
overpowered .header-search-input-wrap input (0,1,1). The base padding
won, so the search input's padding-left (which makes room for the icon)
was overridden. Switched to #site-search-input (1,0,0) to win the
specificity battle.
2026-07-05 15:00:47 +02:00
Pontoporeia 10df92b643 fix: load autosave-handler.js on about_page edit view, persist contact group deletion
Two fixes:

1. about_page edit was missing autosave-handler.js: the first if block
   (editType === 'page' || 'about_page') overrode $extraJs with only
   overtype.min.js, making the elseif that added autosave-handler.js
   unreachable for about_page. Added autosave-handler.js to the first
   block, removed dead 'about_page' from the elseif.

2. Deleting a contact group (Contacts 3/4/5) only removed the DOM
   element but never triggered htmx autosave. Dispatched a 'change'
   event on the form after removal+reindex so the hx-trigger fires.

Also expanded autosave-handler.js URL filter to accept apropos.php
so the status indicator (Enregistrement/Enregistré) works for contacts saves.
2026-07-05 14:14:13 +02:00
Pontoporeia 1e714982d3 fix: restore contact_interne/contact_public column names in v_theses_full
Migration 041 accidentally renamed contact_interne → author_email and
contact_public → author_show_contact, but all application code still
references the old names. Migration 042 rebuilds the views with the
correct column aliases. Also updated schema.sql to match.
2026-07-05 14:04:29 +02:00
Pontoporeia 8e309bbcb7 Remove public contact visibility checkbox from admin add/edit forms 2026-07-05 14:04:29 +02:00
Pontoporeia ec6c939f4e Fix CC2r: uncheck not persisting + want_license hiding cc2r when active
Issue 1 — hidden seeds overriding user input:
Removed all hidden seed inputs (cc2r, want_license, license_id,
license_custom). The .licence-license-choice div is now rendered
directly from $formData on page load instead of via HTMX load
trigger. This eliminates stale-seed interference on both HTMX
radio changes and full form submits.

Issue 2 — Interne want_license uncheck clearing CC2r:
When want_license is unchecked, CC2r and licence selections are
cleared (section hidden). Added hidden want_license=0 so the
fragment handler can distinguish "arriving from another access
type" (no want_license in POST → preserve values) from "explicit
Interne toggle" (want_license present → clear CC2r if off).

CC2r is independently toggleable within the visible section.
Licence dropdown placeholder changed to 'Aucune licence' for
an explicit no-licence choice.
2026-07-05 14:04:05 +02:00
Pontoporeia 83e9dd0bc2 structure-formulaire.php: replace ← text back link with arrow-left-circle icon + admin-back-btn class 2026-07-05 12:51:44 +02:00
Pontoporeia 3c845f9d40 acces.php: remove Mot de passe column, add key icon copy button, reorder actions, switch URL icon to link-simple; fix dialog functions not on window; fix archive: move form into dialog (native submit); add caret icon to archived links details 2026-07-05 12:05:53 +02:00
Pontoporeia 3e93150c76 justfile: standardise test recipes to lint-php/lint-css/lint-js/test + add fix recipe
- Removed ambiguous aliases: phpstan, cs-check, syntax
- Split lint-biome into lint-css and lint-js with correct paths
- Added lint meta-recipe and fix recipe (biome --unsafe + php-cs-fixer)
- Fixed FormBootstrap dead null check, CSS shorthand override bug
- Updated phpstan baseline, suppressed noDescendingSpecificity/noInnerDeclarations
- Applied ~74 biome auto-fixes across CSS/JS
2026-07-05 11:07:41 +02:00
Pontoporeia 34b5d3e585 justfile: standardise test recipes to lint-php/lint-css/lint-js/test + add fix recipe
- Removed ambiguous aliases: phpstan, cs-check (both pointed to lint-php which
  also ran php-cs-fixer, making the names misleading)
- Removed syntax (php -l) — redundant, phpstan already catches parse errors
- Split lint-biome into lint-css and lint-js with correct paths
- Added lint meta-recipe that runs all three linters
- Added fix recipe: biome check --write (CSS/JS format+lint) + php-cs-fixer fix (PHP)
- Updated build-lint to delegate to lint-css + lint-js
2026-07-05 10:50:42 +02:00
Pontoporeia 786eef6df5 cleanup: remplacer <details> par <fieldset>/<legend> (comme contenus.php) 2026-07-03 17:12:15 +02:00
Pontoporeia a1f32acaa1 cleanup: sélection multiple + page dédiée (/admin/cleanup.php)
- cleanup-tmp.php: accepte les arrays filepond_dirs[] et trash_files[] pour suppression en lot
- cleanup-stats-fragment.php: ajout checkboxes + barre d'actions groupées + formulaire bulk HTMX
- admin-cleanup-bulk.js: module JS pour toggleAll, updateBulk, bulkDelete
- Nouvelle page /admin/cleanup.php remplace la modale (tmp-cleanup.php supprimé)
- Bouton Nettoyer dans index.php devient un lien vers la page dédiée
2026-07-03 17:06:23 +02:00
Pontoporeia a5f658ce0c retirer l'orientation des cartes de résultats de recherche 2026-07-03 16:58:29 +02:00
Pontoporeia 880d169e72 search: pagination and scroll, add range counter, mobile version 2026-07-03 16:53:16 +02:00
Pontoporeia e607833b2e style: remove border-top from admin-form grid rows 2026-07-03 16:01:54 +02:00
Pontoporeia 4fa57d592a refactor: combined duration (pages + minutes), has_annexes checkbox, remove Mo
- Remove Mo option from duration, keep only pages and minutes
- Redesign duration fieldset: separate Pages input + Durée h:m inputs, both can be set together
- Fix minutes input visibility: wider inputs (6ch), proper CSS layout
- Add has_annexes checkbox to fichiers fragment + DB column + controllers
- Display duration on admin backoffice recap page
- Display duration on public partage recap page
- Update public TFE page for new combined duration format
- Migration 041: add duration_pages, duration_minutes, has_annexes columns; migrate data; recreate views
2026-07-03 15:58:10 +02:00
Pontoporeia 42c4e78df6 toc: add independent scrollbar for desktop TOC sidebar
- Added max-height, overflow-y: auto, overscroll-behavior: contain,
  and scrollbar-width: thin to .toc in the desktop media query (toc.css)
- This gives the TOC its own scrollbar when its content exceeds the
  viewport height, scrolling independently from the main content
- Affects both public pages (about, charte, licence via .page-content)
  and admin pages (acces, parametres, contenus via .admin-main--toc)
2026-07-03 15:29:05 +02:00
Pontoporeia e6a989bb36 fix: add missing bootstrap require in markdown-cheatsheet-fragment.php, add bottom padding to dialog 2026-07-03 15:24:07 +02:00
Pontoporeia 00a106138e Docs: ajouter nombre de commits par groupe fonctionnel dans livraisons mensuelles 2026-06-29 17:57:13 +02:00
Pontoporeia 4b6f9212b5 Docs: regrouper livraisons par familles fonctionnelles dans chaque mois 2026-06-29 17:49:02 +02:00
Pontoporeia 4ea12f0172 Docs: livraisons par mois extraites de commits.txt 2026-06-29 17:38:57 +02:00
Pontoporeia a790ddc02a Fix admin login heading: prevent 'Administration' from hyphenating or overflowing box 2026-06-24 14:57:28 +02:00
Pontoporeia 21f16ee093 chore: move #rep-polish to Completed, investigate #tighten-csp blockers 2026-06-24 14:49:11 +02:00
Pontoporeia eb706214ce feat: scroll-position memory on repertoire HTMX swaps + swap transition polish
- Add repertoire-scroll-restore.js: snapshots scrollTop of each column <ul>
  before htmx:beforeSwap, restores after htmx:afterSwap (keyed by data-col)
- Add subtle opacity transition on #repertoire-index during htmx-swapping
- Tighten rep-indicator opacity transition to 0.1s for snappier feedback
- Import new module in public-entry.js
2026-06-24 14:45:55 +02:00
Pontoporeia e0cf9f8f57 chore: update TODO — mark icon-color-verify and 3 security tasks complete 2026-06-24 14:26:10 +02:00
Pontoporeia 0062b29678 Fix security issues from audit: gate partage fragments on share_active session, add CSRF to retry-email POST, remove dead App::verifyCsrf() 2026-06-24 14:17:24 +02:00
Pontoporeia 84869ad968 Fix open redirect in tag.php + language.php: reject protocol-relative URLs (//evil.com) by also checking for // prefix 2026-06-24 14:15:25 +02:00
Pontoporeia 6ecd3d4540 Fix biome lint errors: remove duplicate CSS properties, apply safe auto-fixes
CSS:
- Remove duplicate 'background' fallbacks in base.css, header.css, search.css
  (solid color declared before gradient — gradient always wins)
- Remove duplicate 'padding' in admin.css .admin-import-log

JS (biome --write safe fixes applied):
- function() → arrow functions in all IIFEs and callbacks
- forEach/callback → arrow functions
- evaluePtrn → parseInt(x, 10) in admin-contacts-form.js
- Cleaned label text in build.mjs lint step

Remaining warnings are intentional: !important overrides, descending
specificity (admin.css cascade), noUnusedVariables (functions exported
to window/onclick), useTemplate style preference.
2026-06-24 13:57:00 +02:00
Pontoporeia 82d3dcb084 Fix repertoire column scrolling + admin TOC duplication
- repertoire.css: add min-height: 0 to column <ul> scroll containers so
  grid 1fr row shrinks below content and overflow-y: auto activates
- admin-toc.js: add __adminTocBuilt guard + nav.children check to prevent
  double population when loaded both via admin.min.js and direct <script>
- admin-toc.php: remove duplicate <script src="admin-toc.js"> tag —
  JS is already bundled in admin.min.js
2026-06-24 13:18:19 +02:00
Pontoporeia 9f8a4be84e Add dev-watch recipe with chokidar-cli for live rebuild on CSS/JS changes
- just dev now spawns a background chokidar watcher alongside the PHP server
- CSS/JS changes auto-rebuild into dist/ (~200ms per rebuild)
- just stop kills both the PHP server and the watcher
- just dev-watch still available standalone for split-terminal workflows
- chokidar-cli added as devDependency
2026-06-24 13:12:41 +02:00
Pontoporeia 20fe4b6c8c Add biome + rolldown + lightningcss build pipeline for JS/CSS bundling & minification
- package.json with biome, rolldown, lightningcss devDependencies  
- biome.json: add CSS formatter support
- scripts/build-css.mjs: lightningcss resolves @import chain, bundles/minifies CSS  
- scripts/build-js.mjs: rolldown per-entry JS bundling (no code splitting)
- scripts/build.mjs: orchestrator for both CSS + JS
- scripts/check-build.mjs: staleness checker for CI/deploy guard
- justfile: add build, build-css, build-js, build-install, build-check recipes
- justfile: deploy recipe now runs build before deploy-code
- head.php + form-page.php: use dist/base.min.css instead of style.css
- All controllers + FormBootstrap: reference dist/*.min.{css,js}
- admin footer: load admin.min.js for all admin pages
- repertoire: use public.min.js instead of individual app JS files
- Fix stray '}' syntax error in admin.css line 305
- .gitignore: add app/public/assets/dist/
2026-06-24 13:09:50 +02:00
Pontoporeia e74f9210c5 #gzip #extract-inline-js enable gzip in nginx + move ~730 lines of inline JS to 15 external files 2026-06-24 12:56:15 +02:00
Pontoporeia 0ff6ee78d9 #todo add gzip + inline-js extraction tasks from analysis 2026-06-24 12:49:14 +02:00
Pontoporeia 3c786b037a Sort all filter columns: matched entries first, then unmatched, both alphabetical 2026-06-24 11:32:13 +02:00
Pontoporeia 982d91345d Sort keywords column: matched entries first, then unmatched, both alphabetical
When filters are active, viable (matched) keywords now appear at the top
of the Mots-clés column so users don't have to scroll past faded entries
to find clickable tags. Both groups maintain alphabetical order internally.
2026-06-24 11:28:26 +02:00
Pontoporeia 654c884fed Remove mobile filter chip bar from repertoire page
The sticky chip bar added noise: users already see their active
filters highlighted in the accordion columns (with rep-entry--selected
styling and the active-count badge on each toggle). Removing chips
shifts focus back to the accordions, consistent with the expectation
that users return to the dropdowns to adjust filters.
2026-06-24 11:20:47 +02:00
Pontoporeia bb3bb80567 Fix accordion re-init after HTMX outerHTML swap on repertoire page
The htmx:afterSwap handler was calling initAccordions(e.detail.target)
but after an outerHTML swap, e.detail.target references the old detached
DOM element, not the new live element. This meant accordion click
handlers were never attached to the new filter column toggles after
applying or removing a filter — breaking all subsequent interaction.

Fix: query the live DOM with document.querySelector() instead.
2026-06-24 11:13:08 +02:00
Pontoporeia ecb90ba5dd Add accordion + active-filter chip bar for mobile repertoire
- repertoire-index.php: wrap each filter column in rep-accordion with toggle
  button, chevron, badge (active filter count); add rep-chip-bar with
  removable active-filter chips above the columns
- repertoire.css: mobile (≤640px) accordion mode — columns collapse to
  single-open accordion sections with 48px touch targets; chip bar becomes
  sticky; desktop/tablet layout unchanged via display:none on toggle elements
- repertoire.php: JS for single-accordion-open behavior on mobile, HTMX
  re-init after swap, resize-breakpoint cleanup
- docs/repertoire-mobile-propositions.md: analysis + 4 architecture proposals
2026-06-22 16:32:26 +02:00
Pontoporeia cca3d08f05 Replace apropos/licence/charte sticky nav with responsive details/summary TOC
- Convert .apropos-toc <nav> to <details class="toc" open> in all three templates
- Add caret-down icon to summary (visible only on mobile via media queries)
- Desktop (≥768px): sticky sidebar via CSS grid, force-open via pointer-events:none, hide caret
- Mobile (≤767px): single column, collapsible TOC with rotating caret, margin-top below search bar
- Rename .apropos-toc-link to .toc-sidebar-link
- Merge 900px and 600px breakpoints into single 600px mobile media query
- Rename apropos.css → content-page.css; update 3 controller references
2026-06-22 15:33:47 +02:00
Pontoporeia 0a2b4781d1 Fix search icon wrong color 2026-06-21 18:12:47 +02:00
Pontoporeia 6aee5ec131 Clean up cleanup dialog
- Remove duplicate 'Téléversements abandonnés' heading already communicated by the summary
- Replace #peertube-orphans-wrapper with display:contents wrapper so PeerTube details sit as direct grid siblings
- Add hx-confirm to all delete buttons (filepond, trash, PeerTube orphans)
2026-06-21 18:09:22 +02:00
Pontoporeia dfde88eaa5 Migrate all &lt;img&gt;-based icons to inline SVG via PHP helper
Replace every &lt;img src="/assets/icons/..."&gt; with &lt;?= icon('name') ?&gt;
across 26 template files. The PHP helper inlines the SVG markup into the
DOM so CSS color cascades naturally through fill="currentColor".

- Add src/icon.php helper: reads SVG file, sets width/height to 1em,
  injects aria-hidden, supports optional CSS class
- Fix 12 icon SVGs that had hardcoded fill="#000000" or missing fill attr
- Replace search.svg with Phosphor fill-based magnifying glass
- Add explicit SVG sizes for admin header nav icons (16px/20px)
- Scope public search icon CSS to form[role=search]:not(.header-search-form)
  to avoid breaking admin header layout; change stroke to fill
- Remove &lt;img&gt; filter: brightness(0) invert(1) hacks from admin.css
2026-06-21 17:52:27 +02:00
Pontoporeia b1774e6e97 Replace all inline SVGs with icon files, ensure currentColor fill for proper color inheritance on buttons 2026-06-21 17:11:45 +02:00
Pontoporeia 71a92d682b Fix nettoyage modal: SVG icon files, padding/margin consistency, BBBDMSans font, fix HTMX trigger, nested details cleanup 2026-06-21 17:04:37 +02:00
Pontoporeia 03c9c3566f Add SQLite indexes for contenus page language/tag queries + WIP: Peertube orphans, dialogs, contact decoupling, context note, finality types 2026-06-21 16:36:34 +02:00
Pontoporeia 0d5e9dac19 fix: make sticky TOC work for full scroll height and fix heading anchor links 2026-06-20 14:08:16 +02:00
Pontoporeia 19bf9f101a Refactor apropos/charte/licence pages: shared layout, TOC anchors, and UI polish
Unify the three public pages (à propos, charte, licence) onto a single
grid layout (.page-content) with sticky TOC sidebar, replacing the old
separate  /  /  markup.

- Merge about.php, charte.php, licence.php templates into shared
  .page-content / .content-section structure
- Add CommonMark HeadingPermalinkExtension for stable heading anchors
- Use SlugNormalizer for TOC links so they match rendered heading IDs
- Standardize link styling across content blocks: bold black, accent on
  hover (consistent with global link style)
- Fix code block wrapping: use pre-wrap instead of pre, constrain grid
  columns with min-width:0, auto scrollbar
- Fix apropos page grid placement: force content-section into column 2
  so contacts and credits stay in the content area, not the sidebar

Also includes accumulated WIP changes:
- Header gradient: hardcoded purple-to-green (replaces CSS variables)
- Search placeholder font
- Duration field: replace minutes/sec/heures with h:m:s time inputs
- TFE file optional for formats 1,4,6 with client-side JS toggle
- Licence form: em-dash to hyphen, details/summary classes
- Pill search: block Enter key form submission when no results
- Draft autosave: remove CSRF rotation (broke concurrent FilePond uploads)
- Language pill: clear hints for excluded main languages
- Search results: gradient placeholder cards for items without covers
- TFE display: format durée values as XhYm instead of decimal
2026-06-19 19:40:05 +02:00
Pontoporeia 928e074d24 fix: update dev server PHP limits in justfile to match large video uploads
The just dev command hardcodes upload_max_filesize=512M and
post_max_size=520M via -d flags, which override .user.ini.
Raised to 8192M/8704M to match the JS-side 8GB video size
caps.  Also raised max_execution_time and max_input_time to
600s to accommodate large file transfers and PeerTube uploads.
2026-06-15 16:35:13 +02:00
Pontoporeia c8af3bf869 fix: remove leftover debug console.log that crashed HTMX with new FormData(fieldset)
The htmx:beforeSend listener in admin/footer.php was a debugging
leftover that called new FormData(e.target.closest('fieldset')).
FormData only accepts HTMLFormElement or nothing — passing a
<fieldset> throws 'Argument 1 does not implement interface
HTMLFormElement'.  Removed the serialization call; kept the
minimal debug log.
2026-06-15 16:27:35 +02:00
Pontoporeia 2633cb13b5 fix: increase PHP upload limits to support large video files
Raised upload_max_filesize from 512M to 8192M (8G) and post_max_size
from 520M to 8704M to match the JS-side per-extension size caps that
allow up to 8GB for video files (mp4, webm, mov, etc.).  Also raised
memory_limit to 512M and max_input_time to 600s.
2026-06-15 16:26:48 +02:00
Pontoporeia a5db81a73d fix: change adminOld return type from string to string|array
The closure returned arrays when formData values were arrays (e.g.
jury_promoteur), but the PHP return type annotation was :string.
PHP 8.x enforces this strictly, causing a fatal TypeError in
jury-fieldset.php on add mode.
2026-06-15 16:17:29 +02:00
Pontoporeia d588ae004d Reintroduce TFE duration metadata: DB columns, form fields, controllers, views, and migration
Add 'unsafe-eval' to CSP script-src directives (htmx requires Function())
2026-06-15 15:56:52 +02:00
Pontoporeia 00fed5f0e3 Add periodic cleanup of orphaned drafts: cleanup job, just command, deploy cron 2026-06-11 13:05:34 +02:00
Pontoporeia a19e9e1454 Extract FormBootstrap helper to eliminate bootstrap duplication across add/edit form pages 2026-06-11 12:23:55 +02:00
Pontoporeia f4a3e26901 Add thesis status column for two-phase commit lifecycle tracking 2026-06-11 12:09:43 +02:00
Pontoporeia 11a6f6a9f2 Preserve FilePond temp files across partage validation redirects 2026-06-11 11:42:33 +02:00
Pontoporeia b744271cf6 Extract partage page chrome to templates/partage/form-page.php 2026-06-11 11:41:11 +02:00
Pontoporeia cbd369bc72 Split form.css into form-base.css and form-admin.css, drop dead upload-progress code
Also introduces $extraCssAdmin support in head.php for admin-only
stylesheets (form-admin.css, filepond CSS, system.css). Admin pages
now use $extraCssAdmin for admin-only assets and $extraCss for
shared stylesheets like form-base.css.
2026-06-11 11:28:58 +02:00
Pontoporeia 99125cc8e3 Add autosave draft system for partage form with HTMX-based session persistence
- New fragment endpoint POST/GET /partage/fragments/draft.php:
  saves all form fields to PHP session, excludes file/csrf/slug fields
  GET returns JSON for JS hydration on page load
  rotates both global CSRF and share CSRF tokens in sync

- form.php accepts optional $formExtraAttrs and $showAutosaveStatus:
  allows injecting HTMX attributes and 'Brouillon enregistré' indicator

- renderShareLinkForm adds hx-post with change/input debounce trigger,
  loads autosave-handler.js, hydrate fields from draft on page load

- Draft cleared on successful form submission in handleShareLinkSubmission

- autosave-handler.js now also updates share_link_token hidden input
  when rotating CSRF token (partage form uses both csrf_token and share_link_token)

- Added .autosave-status CSS to form.css (was admin.css-only)

- Updated fragment routing to accept GET requests (needed for draft hydration)
2026-06-11 11:04:49 +02:00
Pontoporeia 4b37a05be3 Guard no-JS file uploads: disabled filepond_mode by default, server-side fallback
The partage/admin form had a hardcoded filepond_mode=1 hidden input,
so without JavaScript the server always entered the FilePond async
path — which found no hex IDs and silently dropped all files.

Three-layer fix:
1. HTML: filepond_mode input starts disabled with value=0; JS enables
   it and sets value=1 on DOMContentLoaded (and after HTMX swaps).
   Disabled inputs aren't submitted → server gets no filepond_mode
   → naturally falls to legacy  path.
2. JS: enableFilepondMode() called on page load and hx:afterSwap so
   FilePond-enhanced forms always send filepond_mode=1.
3. Server (defense-in-depth): ThesisFileHandler::hasFilePondQueueData()
   scans POST['queue_file'] for 32-char hex IDs; ThesisCreateController
   and ThesisEditController use it alongside filepond_mode, so even if
   the flag somehow arrives without async upload IDs, the  path
   takes over.
2026-06-11 10:32:50 +02:00
Pontoporeia 63e65d9856 Add mobile-responsive form layout with WCAG 2.5.5 touch targets
Add @media (max-width: 600px) rule to form.css:
- Stack form row labels above inputs (1fr grid, single column)
- Ensure 44×44px minimum touch targets on checkboxes, radios,
  selects, textareas, text inputs, and .btn/.btn--sm
- Stack thesis-add-header and recap-dl grids to single column
- Stack form footer buttons vertically with full width
- Unstick sticky formats fieldset on mobile
- Tighten fieldset margins for narrow viewports
2026-06-11 10:27:17 +02:00
Pontoporeia e17246c850 Add field-level aria-errormessage, aria-invalid, and aria-describedby across the TFE form
WCAG 3.3.1 (Error Identification): failing fields now get
aria-errormessage pointing to the flash-error container and
aria-invalid="true". WCAG 3.3.3 (Error Suggestion): <small>
hint text on inputs, selects, and file fields is now linked via
aria-describedby (always, not just on error).

Changes:
- text-field.php, select-field.php, checkbox-list.php: accept
  $errorFieldName; add aria-errormessage/aria-invalid on match;
  add id to <small> and aria-describedby on the control
- fieldset-tfe-info.php: aria-invalid on synopsis textarea
- fichiers-fragment.php: aria-describedby on cover, note
  d'intention, TFE, annexes, and website inputs; aria-invalid
  on format checkboxes when error matches 'formats'
- form.php: id="flash-error" + tabindex="-1" on flash-error
  div; accept $errorFieldName from callers
- admin/add.php: set $errorFieldName, wire $withAutofocusFn
  (was identity default)
- admin/edit.php: set $errorFieldName
- partage/index.php: consume autofocus field, wire autofocus
  function, add App::flashAutofocus() in submit catch block

Also fixes WCAG standards issue: removed invalid 'required'
HTML attribute from <fieldset> elements in checkbox-list.php
and fichiers-fragment.php (only aria-required stays). Added
role="group" for explicit ARIA semantics.
2026-06-11 10:23:47 +02:00
Pontoporeia c0ba99e861 TODO: add form accessibility & resilience tasks from assessment 2026-06-11 10:14:37 +02:00
Pontoporeia 2c6b55777f Fix logs being captured 2026-06-10 00:21:56 +02:00
Pontoporeia fb752f5ba2 cleanup: remove _write guard — FilePond external API doesn't expose _write
ro=['fire','_read','_write'] is an exclusion list in Ee(), not an inclusion
list. The external pond object has none of these. The only safe interception
point is inside the closure (vendor patch), but the root-cause fix
(fileValidateSizeFilter .filename → .name) already prevents the crash.
2026-06-10 00:18:49 +02:00
Pontoporeia 6d93199fa2 docs: HTMX/destroy race hypothesis investigation — REFUTED
Investigation verdict:
- HTMX does NOT swap the FilePond container on the edit page; the
  htmx:targetError in the crash log is unrelated noise
- The pre-destroy abort in destroyFilePondsIn has a wrong status check
  (filters for nonexistent status 4, misses status 7 LOADING) but is
  moot because no HTMX swap targets the FilePond container
- The load-file-error -> DID_THROW_ITEM_INVALID path is vulnerable
  (passes t.status directly, unlike every other error handler which
  wraps it), but for local files the LOAD_FILE plugins always wrap
  rejections properly
- Likely actual trigger: Firefox XHR abort edge case in server.load
  for the existing cover file, racing with addition of a new local file
  that replaces it in the single-file queue
2026-06-10 00:18:49 +02:00
Pontoporeia d8d925243e docs: add filepond crash analysis report
Documents the 'can't access property main, n.status is undefined'
crash in FilePond 4.32.12. Root cause: vendor code in filepond.min.js
has a property name mismatch — createResponse objects use .code but the
load-file-error handler reads .status. When action.status is undefined,
the view writers crash.

Proposes Option B (custom load function) as the cleanest fix.
2026-06-10 00:18:49 +02:00
Pontoporeia 2829d13a16 filepond: fix crash 'can't access property main, n.status is undefined'
Fixes three root causes of FilePond errors on TFE upload forms:

1. server.process.onerror accessed .status on a string (XHR response
   text body) — now extracts the body safely.

2. server.load was a bare URL string with no error handling — converted
   to object with onload/onerror to prevent FilePond internal _write
   crash when load.php returns HTTP errors.

3. destroyFilePondsIn now aborts in-flight processing before pond.destroy()
   to prevent stale XHR callbacks firing on a torn-down FilePond instance.

Server-side: FilepondHandler now emits Content-Type: text/plain on all
responses (PHP defaults to text/html on die(), confusing FilePond's
response parser).
2026-06-10 00:18:49 +02:00
Pontoporeia 38ef550397 feat: render actual elements in markdown cheatsheet instead of labels
Replace text labels (h1, bold, italic) with rendered HTML in the Rendu column:
headings, strong, em, del, code, links, blockquote, lists, hr, sup, small
2026-06-10 00:18:49 +02:00
Pontoporeia 4a2b000fca Add Charte static page (public + admin editing) 2026-06-10 00:18:49 +02:00
Pontoporeia 317547ac93 Fix #4 v2: decouple contact_interne from contact_visible in ThesisCreateController
validateAndSanitise() no longer cross-contaminates:
- contact_interne overwrote mail, which then copied to contact_visible
- Fixed: contactInterne from contact_interne (admin) or confirmation_email (student)
- Fixed: contactVisible from contact_visible (admin) or mail (student)
- Fixed: submit() uses contactInterne as author email, not mail
2026-06-10 00:18:49 +02:00
Pontoporeia 1490c99268 Fix FilePond: maxFileSize as bytes + temp files survive page reload
1. maxFileSize bug: FileValidateSize plugin overrides core's maxFileSize
   setter. Core uses toBytes('1GB') = 1073741824, but plugin registers
   maxFileSize as [null, Type.INT] which calls toInt('1GB') = 1.
   Fix: all maxFileSize and perExtensionMaxSize values as raw bytes.
   Also fix option name: fileValidateSizeFilterItem → fileValidateSizeFilter.

2. Temp file persistence: files uploaded via FilePond went to
   tmp/filepond/ and vanished from the UI on page reload because
   data-existing-files only included DB-persisted files.
   Fix: session-track temp file_ids in handleProcess, inject via
   getSessionTempFiles() into data-existing-files, teach handleLoad
   to stream temp files from disk, and route JS remove → revert for hex IDs.
2026-06-10 00:18:49 +02:00
Pontoporeia c4a550f9d1 Rework contenus-edit: auto-save, OverType toolbar, dynamic sidebar links
- Auto-save: new autosave.js with 1.5s debounce, watches all forms with
  data-autosave, POSTs to form action with Accept: application/json, shows
  saving/saved/error status indicator
- All action handlers (page.php, apropos.php, form-help.php) now detect
  JSON Accept header and return {success, csrf_token} or {error} responses
- OverType toolbar enabled (toolbar:true) on all three markdown editors
  (page, about_page, form_help)
- Sidebar links: replaced fixed erg_site_url / source_code_url rows with
  dynamic sidebar_links array of {label, url} objects. Add/remove via JS.
  Fallback migration reads legacy keys if sidebar_links is empty.
- Updated AboutController and about.php template to render dynamic links
- Updated apropos.css: unified .apropos-toc-link replacing .apropos-toc-erg
  and .apropos-toc-source
- New CSS: autosave-status states, sidebar-link-row layout
- Removed all Enregistrer + Annuler buttons — auto-save and h1 back-arrow
  make them redundant
2026-06-10 00:18:40 +02:00
Pontoporeia a45a2c9ac4 Changed dev serve recipe to be just dev 2026-06-10 00:17:41 +02:00
Pontoporeia 655dd4c038 feat: clarification contact étudiant + déplacer Contact visible dans Informations du TFE
- Label : « Contact visible (optionnel) », placeholder : mail/site/insta/etc.
- Hint : demander l'URL complète, le système raccourcit à l'affichage
- Affichage public (tfe.php) : extraction d'identifiant depuis l'URL
- Déplacement de contact_visible du Backoffice vers le fieldset Informations du TFE
- Renommage « Identité » → « Informations du TFE » dans le récapitulatif admin
2026-06-10 00:17:41 +02:00
Pontoporeia 021c58925e fix: auto-regenerate thesis identifier on any year-prefix mismatch, support .php migrations in runner
ThesisEditController::save() previously only regenerated the identifier when
the year field changed during an edit. If a thesis had its year corrected in
a past edit (or via other means) and the identifier still carried the old
year prefix, subsequent edits that didn't touch the year field would leave
the mismatched identifier in place.

Now saves() also checks whether the existing identifier's 4-digit prefix
matches the thesis year, and regenerates if not — regardless of whether year
changed in the current edit.

The migration runner (run.php) only scanned for .sql files, so PHP migrations
(013, 016, 018, 038) were never auto-applied. Extended the runner to also
discover and execute .php migrations in a subprocess. If a PHP migration fails
with an idempotent error (no such column, already exists, duplicate column),
the runner treats it as already-applied and continues rather than aborting
— preventing a stale migration like 016 (banner_path already dropped by 028)
from blocking migrations that come after it alphabetically (e.g. 038).

Updated migrations 016 and 038 to accept an optional $argv[1] DB path.
Fixed 016 to gracefully handle the banner_path column already being gone
(exit 0 instead of fatal).
2026-06-10 00:17:30 +02:00
Pontoporeia 07370b7221 search: ajout filtres finalité et format, boutons plus compacts et Réinitialiser en neutre 2026-06-10 00:17:00 +02:00
Pontoporeia 34739d6ae5 feat: migration 038 to fix thesis identifiers mismatched with their year 2026-06-10 00:17:00 +02:00
Pontoporeia 3df1456781 fix: author name casing not updating — use ID lookup priority
Root cause: SQLite uses BINARY collation, so WHERE name = ? is
case-sensitive. When changing 'john doe' to 'John Doe', the name
lookup failed and fell through to the email path which didn't update
the name. The previous fix only added UPDATE in the name-match branch.

Fixes in findOrCreateAuthor:
1. Accept optional $idHint parameter — when known (edit flow), update
   directly by ID (fastest, zero ambiguity)
2. Add COLLATE NOCASE to the name lookup (fallback path)
3. Add UPDATE in the email fallback path too

setThesisAuthors now fetches existing author_ids before deletion and
passes them as position-based hints, so identity is always preserved.
2026-06-10 00:17:00 +02:00
Pontoporeia 3016c199bd Fix edit form: is_published reset, contact decoupling, note label, author name case
- Fix #1: Add is_published to getThesisRawFields() SELECT so the publish
  checkbox stays checked when editing an already-published TFE.
- Fix #2: Rename 'Note contextuelle' → 'Note contextuelle relative à
  soutenance' in all templates and StudentEmail.
- Fix #3: Update findOrCreateAuthor to also UPDATE the author name when
  a record is found by name (fixes inability to capitalise names).
- Fix #4/#5: Decouple contact_interne (private author email) from
  contact_visible (public contact on TFE page). Add migration 037 to
  add contact_visible TEXT column to theses table and rebuild
  v_theses_full view. Update all controllers, templates, and DB methods
  to treat them independently.
- Fix #6: Investigated libre→interne restriction — no code barrier
  found; likely resolved by is_published fix.
2026-06-10 00:17:00 +02:00
Pontoporeia 3d524226a1 formulaire: correctifs identifiant/année, contact, fichiers optionnels
- Identifiant: mise à jour automatique quand l'année change en back-office (updateThesis + ThesisEditController)
- Contact: hint enrichi (1 seul contact, formatage Instagram/Mastodon)
- Fichiers: TFE rendu optionnel pour Site web/Performance/Installation (note d'intention reste obligatoire)
2026-06-10 00:17:00 +02:00
Pontoporeia c4664ec2e9 fix: prevent mid-word break in repertoire column headers
Base.css applies word-break: break-word to all elements inside <main>,
causing mid-word breaks in narrow columns. Override in repertoire.css:
- hyphens: none, word-break: normal, overflow-wrap: normal on all h2
- redistribute grid fractions: shrink Orientations (1.2→0.9fr), 
  Étudiantes (1→0.8fr), boost Finalité (0.7→0.9fr, min 7rem)
2026-06-10 00:16:49 +02:00
Pontoporeia a184e0d253 Ajouter l'affichage de la finalité du master sur la page publique TFE 2026-06-10 00:16:22 +02:00
Pontoporeia a2092b58a7 fix: supprimer les vidéos PeerTube lors de la suppression d'un TFE
- Ajout de PeerTubeService::deleteVideo() qui appelle DELETE /api/v1/videos/{uuid}
- deleteThesisFileToTrash() appelle maintenant deleteVideo() pour les fichiers peertube_ids:
- hardDeleteThesis() supprime aussi les vidéos PeerTube associées
2026-06-10 00:16:22 +02:00
Pontoporeia 312d9eab0e À propos: contacts flexibles, liens sidebar éditables, grille contacts admin, et bouton supprimer
- Contacts: on peut laisser vide le nom OU le rôle (plus besoin des deux)
- Sidebar: les liens « site de l'erg » et « code source » sont éditables depuis /admin/contenus-edit.php?slug=about
- Admin: les champs Nom/Email/Lien des contacts s'affichent en grille 3 colonnes
- Admin: icône corbeille (admin-icon-btn--delete) pour supprimer un contact, avec réindexation automatique
- Database::getAproposContent() gère maintenant les valeurs string (URLs) en plus des arrays
- Database::saveAproposContent() accepte array|string
2026-06-10 00:16:22 +02:00
Pontoporeia a1a9a316ca rework tfe.php layout: row1 author above title, row2 meta+synopsis 2-col grid, row3 flex files 2026-06-10 00:16:10 +02:00
Pontoporeia e0d706c677 tfe.css: tfe-meta-item font-weight 400→700 2026-06-10 00:15:41 +02:00
Pontoporeia c9fa5943cf repertoire: rep-entry + col h2 step-0, years col step-3 2026-06-10 00:15:41 +02:00
Pontoporeia ef6bff895a admin nav-logo: grid layout for icon+text horizontal alignment and vertical centering 2026-06-10 00:15:41 +02:00
Pontoporeia 9e272873e1 style: set tfe-meta-item default to font-weight 400 so Accès/Licence values render at regular weight 2026-06-10 00:15:41 +02:00
Pontoporeia 3588f22d7b style: consolidate aria-current nav styles — remove border-radius from base header links, keep global :focus-visible ring, move border-bottom/padding to shared header.css 2026-06-10 00:15:41 +02:00
Pontoporeia cb2b18e470 style: standardise links to Regular weight (400) with violet accent hover, body to Light (300) 2026-06-10 00:15:41 +02:00
Pontoporeia cee3345ea3 tfe.php: afficher CC2r + licence, formater contact court, supprimer download PDF 2026-06-10 00:15:41 +02:00
Pontoporeia 24b753a992 fix: add missing csrf_token to htmx checkbox in file access restrictions
The 'Activer la restriction d'accès' checkbox in /admin/acces.php used
htmx to POST to settings.php but the #fieldset-restrictions container
was missing a csrf_token hidden input. This caused two bugs:
1. 'Erreur de sécurité, token invalide' error
2. Full /admin/parametres.php HTML injected into #restrictions-response
   (due to HTMX following the 302 redirect on CSRF failure)
2026-06-10 00:15:41 +02:00
Pontoporeia 3f200dae70 TFE page: replace dl/dt/dd with p/span for metadata, remove underlines, lowercase keywords/languages/formats, inclusive text, editable restriction messages 2026-06-10 00:15:41 +02:00
Pontoporeia 71949425c7 TFE page: remove underlines from all links, lowercase keywords/languages/formats, inclusive writing, prevent keyword mid-word breaks, editable restriction messages in admin 2026-06-10 00:15:41 +02:00
Pontoporeia 9a8f0cad65 fix(répertoire): colonnes différenciées, scrollbars discrètes, fontes conformes maquette, AP entre crochets
- grid-template-columns: années=0.4fr, orientations=1.2fr, AP/finalité/intermédiaires
- scrollbars: WebKit 5px transparent + Firefox scrollbar-width:thin global
- rep-entry: BBBDMSans Regular 398, --step--1
- années: BBBDMSans Medium 498 (semi-bold)
- titres colonnes: Ductus Regular 398, text-secondary, letter-spacing 0.12em
- AP: diminutifs entre crochets (ex: Design et Politique du Multiple [DPM])
- TODO: marquer les 4 correctifs répertoire comme faits
2026-06-10 00:15:33 +02:00
Pontoporeia df70fba5d4 feat: convert all file inputs to FilePond for standardized uploading
- Add csv_import queue type (storeAsFile, no async upload) for CSV import dialog
- Convert file-field.php partial to FilePond with field-name→queue-type mapping
- Conditionally skip server config for storeAsFile queues in buildFilePondOptions
- Skip FilePond init for inputs inside closed <dialog> elements
- Trigger FilePond init when import dialog opens
- Load FilePond CSS/JS assets on admin index page
2026-06-09 13:12:22 +02:00
Pontoporeia fad38f4e0d Added more logs to be excluded 2026-06-08 10:25:24 +02:00
Pontoporeia 053f09b181 fix(migration): deduplicate languages before LOWER() in 025_lowercase_languages.sql
Two rows (Néerlandais id=5, néerlandais id=3) collided when lowercased,
violating the UNIQUE constraint on languages.name.

Added DELETE to keep the lowest-ID row per LOWER(name) group before
the UPDATE SET name = LOWER(name).
2026-06-08 10:17:00 +02:00