Files
xamxam/TODO.md
T
Pontoporeia 554ba3ee8d fix(admin): stop logging out active long-form work; raise idle timeout to 4h
The admin idle timeout (30 min) was refreshed only by navigations and HTMX
requests. During long encoding sessions on an open form there are none, so
an actively-typing admin was logged out mid-work after ~30-45 min.

Add an activity-driven keepalive:
- /admin/session-keepalive.php: 204 when authenticated (refreshes
  admin_last_activity via AdminAuth::isAuthenticated()), 401 otherwise.
- admin-session-keepalive.js: marks activity only on real user input
  (pointer/keyboard/input/scroll/wheel/touch/focus) and pings at most once
  per 5 min while the tab is visible. A genuinely idle tab never pings, so
  the idle timeout still applies.

Raise the idle window 30 min -> 4 h: for a single-/few-admin back-office
whose main workflow is data entry, 30 min still kicked admins who stepped
away mid-form. With the keepalive in place, 4 h means "no interaction at
all", not "no navigation". Absolute timeout stays 12 h.

Also fix session ID rotation, which never fired: it used
`$absolute % IDLE_TIMEOUT_SECONDS === 0`, i.e. required a request to land
exactly on a multiple of the interval relative to login time. Replaced with
an explicit admin_last_rotation timestamp and a ROTATION_INTERVAL_SECONDS
(30 min) constant decoupled from the idle timeout, so raising the idle
window does not widen the fixation/replay window.

Refactor AdminAuth::enforceSessionTimeout() to return bool instead of
redirecting/exiting, so the keepalive endpoint can report 401 cleanly
rather than letting fetch follow a redirect to the login page.

Smoke test (just smoke-session-keepalive) covers activity refresh, 2 h idle
accepted, rotation firing, idle rejection+destruction, and unauthenticated
rejection. Docs updated.
2026-09-18 16:26:49 +02:00

11 KiB

TODO

Last updated: 2026-09-10 Context: CSS waste strategy: run unusedSymbols report-only (10-14) first to get go/no-go reclaimable-byte data; per-page split (u-x) second, reusing template inventory in docs/css-split-analysis.md.

In Progress

Pending

  • #test-env-deploy-code-chown-regression test-env deploy-code --chown regression documented in README --context test-env

Completed

  • #audit-all-docs-and [!high] Audit all docs/ and classify accurate vs stale
  • #rewrite-development-md-to-match [!high] Rewrite development.md to match current just dev / app/ layout / PHPUnit
  • #rewrite-deployment-md-to-match [!high] Rewrite deployment.md to match just deploy / /var/www/xamxam/ / backup
  • #update-database-md-to-current [!high] Update database.md to current schema (tags, share_links, migrations, xamxam.db)
  • #update-security-md-to-current [!medium] Update security.md to current state (xamxam paths, resolved items)
  • #update-search-md-to-current [!medium] Update search.md to current schema (tags not keywords)
  • #verify-fix-import-md-csv-format [!medium] Verify/fix import.md CSV format against current import
  • #update-bookmarklet-md-form-field [!medium] Update bookmarklet.md form field names (cc2r, duration_pages/minutes, drop contact_public)
  • #consolidate-archive-historical-analysis-docs [!medium] Consolidate/archive historical analysis docs
  • #fix-nginx-readme-md-title [!low] Fix nginx README.md title + export.md cross-refs
  • #fix-app-storage-readme-md-missing-database-spec-quick-schema [!medium] Fix app/storage/README.md (missing DATABASE_SPEC/QUICK_SCHEMA refs, wrong path)
  • #update-css-md-asset-structure Update CSS.md asset structure + build bundling
  • #correct-file-uploads-md-limits-banner Correct file-uploads.md limits, banner removal, FilePond transition
  • #rewrite-app-public-admin-readme-md-to-current Rewrite app/public/admin/README.md to current structure
  • #fix-nginx-docs-security-headers-md-csp-x-frame-options Fix nginx/docs/SECURITY_HEADERS.md CSP/X-Frame-Options
  • #create-docs-readme-md-index-classifying Create docs/README.md index classifying all docs
  • #standardise-all-log-filenames Standardise all log filenames to xamxam-{service}[-{date}].log convention
  • #add-server-side-log-rename Add server-side log rename script + just recipe (migrate-log-names) for pre-existing logs
  • #move-production-app-logs Move production app logs from storage/logs to /var/log/xamxam + add logrotate config
  • #add-date-dropdown-to Add date dropdown to admin log viewer to browse retained daily log files
  • #fix-heure-de-d-p-t Fix heure de dépôt (submitted_at) fausse dans backoffice récapitulatif: timezone Europe/Brussels + interpréter datetimes SQLite (UTC) comme UTC
  • #add-date-de-d-p-t Add 'Date de dépôt' (date_depot) column: DB migration + view + recap display
  • #csv-export-emit-date-depot CSV export: emit date_depot as Brussels-local date
  • #csv-import-parse-date-depot CSV import: parse date_depot as Brussels-local and convert to UTC storage
  • #default-exemplaire-baiu Default Exemplaire BAIU + ERG to true on student (partage) submission in ThesisCreateController, replicating admin add-form default
  • #m-style-links-on Style links on licences, charte, a propos pages (underline + accent hover)
  • #recherche-backoffice-par-identifiant Recherche backoffice par identifiant : ajouter t.identifier a la WHERE de getThesesList/getThesesListCount
  • #add-admin-only-route-to [!low] Add admin-only route to open Interdit thesis files from backoffice --context repo
  • #identify-which-templates-load [!high] Identify which templates load base.min.css and the page types — DONE inventory: docs/css-split-analysis.md. Key finding: head.php loads base.min.css on EVERY full page (App::render => templates/head.php unconditional). is the per-page-type mechanism (public/tfe/repertoire/content-page/not-found/form/admin/system). Centralize inventory in docs/css-split-analysis.md. Gates both split (w) and unusedSymbols corpus (11).
  • #collect-content-sources-for [!high] Collect content sources for the unusedSymbols pass — Built scripts/css-content-sources.mjs: buildCorpus() returns {corpus, sources, totalBytes, safelist, prefixes}. Gathers templates/public/src PHP + first-party js/app JS (vendor excluded). Mined 22 exact dynamic classes (status-, log-, input-error, active, disabled, btn--, fhb-, admin-icon-btn--, status-published/pending/badge) + 5 suffix prefixes (status-access--, toc-level-, admin-import-log__item--, admin-body, student-body) from status-badge.php, SystemController statusClass/logLineClass, class= ternaries. Documented docs/css-split-analysis.md. Feeds task 12 report.
  • #write-report-script-to [!medium] Write report script to compute unused selectors + reclaimable bytes — Built scripts/css-unused-report.mjs + just css-report recipe. Extracts class/id symbols per dist bundle, matches against buildCorpus() corpus + safelists, measures reclaimable bytes via lightningcss transform unusedSymbols (no stripping to disk). FULL RESULT: 216,383B total, ~6.2KB (2.9%) reclaimable. See docs/css-split-analysis.md table. Feeds task 14 go/no-go.
  • #flag-or-safelist-dynamic [!medium] Flag or safelist dynamic classes before trusting the report — Vendor + dynamic safelists shipped. Dynamic: 22 exact classes (status-, log-, input-error, active, disabled, btn--, fhb-, admin-icon-btn--, status-badge/published/pending) + 5 DB/state suffix prefixes (status-access--, toc-level-, admin-import-log__item--, admin-body, student-body). VENDOR: filepond--* and htmx-* prefixes excluded (runtime vendor JS assembly) — without these the report inflated to 26% (56KB); with them honest 2.9%. Report surfaces needs-review (kept, not flagged removable).
  • #summarize-findings-and-decide [!low] Summarize findings and decide whether to proceed to pruning — Decision recorded in docs/css-split-analysis.md. Data: 216,383B total, ~6.2KB (2.9%) reclaimable; base.min.css only 484B. SPLIT: NO-GO (base.css already well-used; split adds complexity for ~2% of one bundle) -> defer u-w-x-y. PRUNING: CONDITIONAL-GO on hand-verifiable dead set only, from SOURCE css files, re-run just css-report after each edit, never touch needs-review/vendor-prefix classes.
  • #archive-non-documentation-one-offs-from Archive non-documentation one-offs from docs/ (plans, incidents, proposals, assessments); keep only reference docs
  • #verify-every-retained-docs Verify every retained docs/ file against current repo; fix inaccuracies found
  • #document-app-key-env-first Document APP_KEY/.env + first admin setup in dev and deploy docs
  • #add-idempotent-provisioning-scripts Add idempotent provisioning scripts (local + server) for APP_KEY/.env and just provision/provision-server
  • #podman-compose-test-env podman compose test env for fresh Debian server --context test-env
  • #deploy-permissions-fix-ownership-break deploy-permissions: fix ownership break so just deploy-code can't cause HTTP 500
  • #fix-desync-deploy-code-exit-23 Fix desync: deploy-code exit-23 'Operation not permitted' — drop -p/-t (destination owned by www-data:xamxam, setgid; SSH user can't chmod), use -rlDz --size-only --context deploy
  • #deploy-permissions-sudo-prompt-can-t deploy-permissions sudo prompt can't accept input: ssh -t drops pty when local stdin isn't a TTY. Add scoped NOPASSWD sudo drop-in (deploy/xamxam-fix-permissions.sudoers) + deploy-sudoers recipe and wire into deploy --context deploy
  • #fix-raw-markdown-leaking Fix raw markdown leaking into TOC labels on licence/charte/about pages
  • #rewrite-cc2r-checkbox-label Rewrite CC2r checkbox label to 'J'adhère au Collective Commitment to Reuse (CC2r)' (italic, both Libre and Interne branches)
  • #fix-admin-contenus-page [!high] Fix admin contenus page slowdown: langues/mots-clés fragments ship 2.4MB HTML (737 tag rows + 217 lang rows, ~1960 inline SVG icons + per-row CSRF forms). Implement htmx infinite-scroll 'load more' (paged fragments via limit/offset) for both tables.
  • #reduce-contenus-initial-page [!high] Reduce contenus initial page size from 100 to 25 rows per table (payload ~160KB total vs ~534KB)
  • #add-admin-session-keepalive Add admin session keepalive endpoint /admin/session-keepalive.php (204, gated, refreshes admin_last_activity)
  • #add-admin-session-keepalive-js-activity-driven-throttled Add admin-session-keepalive.js: activity-driven throttled ping (5min), visibility-aware, wired into admin-entry
  • #update-docs-security-md-to-document Update docs/security.md to document activity-driven keepalive vs navigation-only timeout
  • #add-test-for-session Add test for session keepalive endpoint auth gating + activity refresh
  • #raise-admin-idle-timeout Raise admin idle timeout 30min -> 4h for long-form encoding; keep absolute 12h
  • #fix-never-firing-session-id Fix never-firing session ID rotation (modulo on absolute age); use admin_last_rotation + interval
  • #decouple-rotation-interval-30min Decouple rotation interval (30min) from idle timeout constant
  • #update-docs-security-md-deploy-server-sh Update docs/security.md + deploy-server.sh comment for new idle value
  • #extend-smoke-test-for Extend smoke test for rotation and 4h idle boundary

Deferred / Blocked

  • #just-setup-backs-a [!medium] just setup backs a stale setup-dev.sh (clones php-live-reload, legacy admin/data/ dirs) — needs rewrite or removal
  • #csv-export-writes-cc2r-exemplaire [!low] CSV export writes CC2r/Exemplaire columns that import's $cell mapping ignores — document or wire up
  • #split-base-min-css-into-per-page [!medium] Split base.min.css into per-page bundles to reduce cross-page CSS waste — NO-GO (task 14): per-page split parked. base.min.css already well-used (only 484B/2.3% reclaimable); split adds build/template complexity for ~2% of one bundle. See docs/css-split-analysis.md decision.
  • #define-per-page-type-css-bundle [!medium] Define per-page-type CSS bundle boundaries and entry files — Using the template->page-type map, decide which source files (components/*, utilities.css, reset, typography, etc.) belong in each page-type bundle. Document the mapping so shared-vs-page-specific splits are explicit and reviewable.
  • #implement-per-page-bundles-in [!medium] Implement per-page bundles in scripts/build-css.mjs — Extend build-css.mjs with new bundle targets following the existing bundleCss/concatBundle pattern. Emit per-page-type .min.css outputs into app/public/assets/dist/. Keep base.min.css for pages not yet split.
  • #wire-templates-to-load [!medium] Wire templates to load their page-type bundle instead of base.min.css — Update each template's tags to point at the appropriate per-page bundle. Ensure no page loads CSS it doesn't need and that shared pages still get full styling.
  • #measure-before-after-css-bytes [!low] Measure before/after CSS bytes shipped per page type — Moot: part of split stream which is NO-GO (task 14). Skip unless split is revived.
  • #add-lightningcss-unusedsymbols-report-only [!high] Add lightningcss unusedSymbols report-only pass for base.min.css — SEQUENCING: do this (unusedSymbols report-only) BEFORE the per-page split (u). Reasoning: high priority, zero-risk diagnostic-only, produces reclaimable-byte go/no-go (14) against ~216KB baseline; if pruning is not worthwhile the split may not be either. Reuses inventory from v (docs/css-split-analysis.md). Start: 11 collect-content-sources.