Pontoporeia a9877b1d1d docs: accessibility audit — add WCAG 2.1 AA analysis to TODO.md
Measured contrast ratios, traced every interactive element, checked all four
WCAG principles across public and admin surfaces. Current state confirmed:
zero ARIA attributes, zero skip links, zero focus-visible styles, zero
prefers-reduced-motion guards in the live codebase.

Key failures by criterion:

1.1.1: TFE file images use raw filename as alt; search bar SVG not aria-hidden;
       jury remove buttons have no accessible name (bare ✕)

1.3.1: TFE metadata is div/span soup with no programmatic label-value association;
       search filter selects have no associated label; checkbox groups need fieldset/legend

1.4.1: Status badges distinguish state by colour only; active nav link has no
       non-colour indicator and its CSS class has no rule at all

1.4.3 (measured failures):
  - Nav links at opacity:0.92 on purple: 4.05:1 (fails AA 4.5:1)
  - filter-info purple text on purple-light bg: 4.08:1 (fails AA)
  - Placeholder #aaa on white: 2.32:1 (fails AA)
  - Gradient cards: white text on L=65% HSL — every warm hue fails AA,
    some as low as 1.46:1 (yellow). Only blue/indigo hues pass.
  - admin-text-muted #888 on bg-alt #242424: 4.38:1 (fails AA)
  - admin-purple on dark bg: 3.57:1 (fails for normal text size)

1.4.10: Répertoire 4-column grid has no mobile breakpoint
1.4.11: Search select border #ddd on white: 1.6:1; admin input border: 1.8:1

2.1.1: Disabled pagination links have pointer-events:none but remain keyboard-focusable
2.4.1: No skip-to-main link anywhere in the site
2.4.4: Pagination arrows (« ‹ › ») have no aria-label
2.4.6: tfe.php h1=author h2=title is inverted; index/search have no h1 at all
2.4.7: No :focus-visible defined anywhere; outline:none suppresses browser default
       on search input with no replacement

3.1.1: 429 response has no lang attribute
3.3.1: Form errors not announced as live regions; no autofocus on invalid field
3.3.2: Search input has no label, only placeholder

4.1.1: pages-edit.php has <link> in <body>
4.1.2: <video> has no caption track; <embed> PDF has no fallback download link;
       bulk action results not announced to AT

Motion: no prefers-reduced-motion guard on any transition or animation

Infrastructure gaps: no .sr-only class, no skip link, no :focus-visible,
four explicit outline:none suppressions with no replacement
2026-03-26 23:04:21 +01:00
2026-03-11 12:39:18 +01:00
2026-03-11 12:39:18 +01:00

posterg

Répertoire des travaux de fin d'études de l'ERG (École de Recherche Graphique).

Requirements

  • PHP 8.4
  • SQLite3 (php8.4-sqlite3)
  • nginx (production)

Project structure

posterg/
├── public/          # DocumentRoot — web-accessible only
│   ├── admin/       # Admin panel (session-authenticated)
│   ├── assets/      # CSS, fonts, icons
│   ├── media.php    # Controlled file serving (covers, PDFs)
│   └── *.php        # Public pages (index, search, tfe, apropos)
├── src/             # PHP classes (not web-accessible)
│   ├── AdminAuth.php
│   ├── Database.php
│   ├── RateLimit.php
│   └── config.php
├── templates/       # Shared PHP template partials
├── config/          # Bootstrap and credentials (not web-accessible)
├── storage/         # Database and uploaded files (not web-accessible)
│   ├── schema.sql
│   ├── test.db
│   └── fixtures/
├── tests/
├── scripts/         # Dev and server management scripts
│   ├── setup-dev.sh
│   ├── deploy-server.sh      # Run on server with sudo to apply nginx config
│   └── manage-admin-users.sh # Run on server with sudo to manage htpasswd
└── nginx/           # nginx config and reference files
    └── posterg.conf

Uploaded files (PDFs, covers) live in storage/ — outside the webroot — and are served exclusively through public/media.php, which validates paths and MIME types.

Development

just setup   # first-time: installs dev dependencies
just serve   # http://localhost:8000  (public) and /admin/
just test    # run test suite

Admin credentials in development are set via config/admin_credentials.php (see config/admin_credentials.example.php).

Deployment

Files are pushed to the server with rsync — there is no repo on the remote.

just deploy     # rsync app files → posterg:/var/www/posterg/
just deploy-db  # push local test.db → remote (only if remote DB is absent)

deploy-db refuses to run if a database already exists on the server, to avoid accidental overwrites of production data.

First-time server setup

ssh posterg
sudo mkdir -p /var/www/posterg
sudo chown www-data:posterg /var/www/posterg
sudo chmod 775 /var/www/posterg
exit

Then deploy once, copy nginx config, and apply:

just deploy
rsync -v nginx/posterg.conf posterg:/tmp/posterg.conf
ssh posterg "sudo bash /var/www/posterg/scripts/deploy-server.sh"
ssh posterg "sudo systemctl reload nginx"

Admin users (htpasswd)

ssh posterg "sudo bash /var/www/posterg/scripts/manage-admin-users.sh"

Security notes

  • Admin panel protected by nginx auth_basic + PHP session (AdminAuth)
  • Uploads stored outside webroot, served via controlled media.php
  • Rate limiting on public search (src/RateLimit.php)
  • See docs/TODO.SECURITY.md for outstanding items
Description
Site permettant de consulter la collection de TFE de l'erg
Readme 74 MiB
Languages
PHP 80.5%
CSS 14.9%
Shell 2.8%
JavaScript 1.3%
Just 0.5%