8.1 KiB
AGENTS.md - Development Guidelines
Essential information for AI agents and developers working on this codebase.
Project Overview
Web application for "Harmonie de Montpellier-Jacou" (music band) to manage and share sheet music (partitions).
- Frontend: SvelteKit (Svelte 5, runes) + Tailwind CSS in
partitions/ - Backend: PHP 8.5 REST API in
api/(no framework) - Storage: flat files under
legacy/Scores/(no database) - Auth: JWT (HS256), see
api/lib/Auth.php - Production:
https://partitions.c.nadal-fr.com(front) +https://ohmj-api.c.nadal-fr.com(API)
There is no MySQL and no Vue.js in the active codebase. The old Vue.js 2 / MySQL
site is archived in legacy/ and must not be modified.
Stack & Versions
- Node.js 26 / npm 11
- Svelte 5, SvelteKit 2,
@sveltejs/adapter-static, Vite 5, Tailwind 3 - PHP 8.5 (CLI built-in server in development)
- Dependencies:
axios,pdfjs-dist,lucide-svelte(front);setasign/fpdi,setasign/fpdf,tecnickcom/tcpdf(back)
PHP 8.5 note: nullable parameters MUST use explicit
?typesyntax (e.g.?string $x = null). Implicit nullable is deprecated and pollutes API output.
Build & Run Commands
Backend (PHP)
cd api
php -c php-upload.ini -S localhost:8000 router.php
-c php-upload.iniis required so PDF uploads work (upload_max_filesize/post_max_size= 64M).ini_set()cannot change those directives at runtime.- API base URL:
http://localhost:8000(paths have no/apiprefix in the client). - Copy
api/.env.exampletoapi/.envand set a strongJWT_SECRET.
Frontend (SvelteKit)
cd partitions
npm install
npm run dev # http://localhost:5173
npm run build # outputs to partitions/build
npm run preview
frontendis a symlink topartitions/build(the deployed static site).- No ESLint / test framework is configured for the frontend.
VITE_API_URLis defined but currently unused (base URL is chosen fromimport.meta.env.DEV).
Deploy
./deploy/deploy.sh
- Do NOT include
legacy/orapi/tests.phpin the deploy archive. api/.env(withJWT_SECRET) is part of the deployment.- The frontend build must be present in
frontend/.
Testing
PHP API tests
cd api
php -c php-upload.ini -S localhost:8000 router.php & # if not already running
rm -f /tmp/rate_* 2>/dev/null # clear rate limiting first
php tests.php
- Expected: 53/53 tests passed (100%).
- Covers: auth (login, bad credentials, missing/invalid token), scores CRUD, create-score-with-pieces, files tree, path traversal, rate limiting, upload validation, INI injection, security headers.
tests.phpis kept for development only and MUST NOT be shipped in the deploy zip.
Authentication
- Login:
POST /loginwith{"username":"admin","password":"password"}(development). - Token: JWT HS256 signed with
JWT_SECRET, 1 hour expiry. - API calls: send
Authorization: Bearer <token>. - Token is stored in
localStorageon the frontend and attached via an axios request interceptor. - Roles:
admin(full access) anduser(read-only). Admin routes live under/admin/.... - Frontend dev login page: http://localhost:5173
API Endpoints
Public / all authenticated users:
POST /loginGET /scoresGET /scores/:idGET /scores/:id/instruments[?piece=N]GET /pieces/:scoreIdGET /download/:path(token viaAuthorizationheader or?token=)
Admin only (role === 'admin'):
POST /admin/scoresPUT /admin/scores/:idPUT /admin/scores/:id/piecesDELETE /admin/scores/:idPOST /admin/scores/:id/uploadGET /admin/scores/:id/filesDELETE /admin/scores/:id/files?path=...
File Organization
/
├── api/ # PHP REST API (active)
│ ├── index.php # Router / entry point
│ ├── router.php # Dev server router (+ CORS, .env loading)
│ ├── lib/Auth.php # JWT auth
│ ├── lib/ScoreScanner.php# File-based score index
│ ├── config/users.json # Users (bcrypt hashes)
│ ├── tests.php # Functional tests (dev only)
│ └── .env # JWT_SECRET, SCORES_PATH
├── partitions/ # SvelteKit app (active)
│ ├── src/lib/api.ts # Axios client + apiService
│ ├── src/lib/stores/ # auth store
│ ├── src/lib/components/ # PdfViewer
│ ├── src/routes/ # pages (login, scores, admin)
│ └── build/ # static output (gitignored)
├── frontend -> partitions/build # symlink used for deployment
├── deploy/deploy.sh # deploy archive builder
├── scripts/convert_final_v2.js # one-off filename converter
├── legacy/ # archived old site + Scores/ (do not modify)
└── _builds/ # temporary work files (gitignored)
Data Model (files)
legacy/Scores/<NUM>/<PIECE>/<INSTRUMENT>/<VERSION>/<PARTIE>.pdf
legacy/Scores/<NUM>/score.ini
score.ini:
[info]
name = Nom du morceau
compositor = Compositeur
ressource = https://... # optional external link
[pieces]
count = 1
NUM= score id (zero-padded, e.g.102).PIECE= movement/piece number (1 for single-piece scores).INSTRUMENT= trigram code (see below).VERSION= version number.PARTIE= part number.
Instrument codes: dir, pic, flu, cla, clb, sax, sat, sab, coa,
cba, cor, trp, trb, tub, htb, bas, per, crn, eup, har, pia,
plus sup/par for combined parts.
Code Style
PHP
<?phpopening tags, PascalCase classes, camelCase methods.- 3-space indentation, opening brace on the same line (legacy style).
- Consistent with surrounding code; prefer
include_once/require_oncefor includes. - No database layer: file operations via
ScoreScanner.php.
JavaScript / Svelte
- Svelte 5 runes (
$state,$props,$derived,$effect); prefer$app/stateover the deprecated$app/stores. - TypeScript, camelCase variables, PascalCase components, 2-space indent, semicolons.
General
- UTF-8, French for user-facing content.
- Do not add comments unless necessary.
- NEVER delete files or directories without asking first.
Design Guidelines
- Clean, sober, modern.
- Use SVG icons instead of emojis.
- Integrate related actions (view + download) in a single row with consistent styling.
- Hide redundant labels ("Piece 1", "Version 1") when there is only one.
- Use spacing, subtle borders, and hover effects for visual hierarchy.
Security Checklist (before deployment)
JWT_SECRETis strong and unique (openssl rand -base64 32); never commitapi/.env.api/config/users.jsonuses non-default credentials (changeadmin/user).- CORS origins restricted to the real domains (no wildcard fallback).
- HTTPS enforced in production.
- Rate limiting active (clear
/tmp/rate_*only in tests, never in prod). php tests.phppasses 53/53.npm auditreviewed.- No secrets in code or git history.
Manual checks:
# JWT login
curl -s http://localhost:8000/login -X POST \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"password"}'
# Directory traversal must NOT return 200
curl -s http://localhost:8000/download/../../../etc/passwd \
-H "Authorization: Bearer <token>"
# Security headers on any response
curl -s -I http://localhost:8000/login -X POST \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"password"}'
Known security findings from the latest review (not yet fixed): static file passthrough in
api/router.phpexposes.env/users.json;deleteScoreFilelacks path containment;api/.envis tracked by git; JWT is standard base64 (not base64url) and expiry is optional; stored XSS via theressourcelink. See the project issue tracker / recent review notes.
Temporary Files
- Put temporary scripts and outputs in
_builds/(gitignored). scripts/convert_final_v2.jsis the only committed script inscripts/.
Git
- Default branch:
master. - No CI/CD configured.
- Only commit when explicitly requested.