Files
projektuebersicht/AGENTS.md
T
2026-08-14 11:47:19 +02:00

11 KiB

AGENTS.md

Overview

Single-page app for tracking engineering projects across weekly sessions ("Sitzungen"). Vanilla JS/HTML/CSS frontend + Node.js/Express server. No build step, no bundler, no tests. Data is persisted as semicolon-separated CSVs under sessions/.

Two HTML entrypoints, both served statically by Express:

  • Test.html — the main app (tabs: Projektboard, Auslastung, Projekte, Projektbeteiligte). Served at /.
  • project-plan.html — per-project Gantt/TODO planning view, opened in a new tab via openProjectPlan() in js/gantt.js. Loads the shared helper modules (globals, utils, ui, date-picker, csv, server, gantt) plus the plan modules under js/plan/ — NOT the full main module set.

All UI strings/comments/CSV content are German. Preserve German in user-facing text.

File structure

.
├── server.js                 # Express server + CSV API
├── package.json             # deps: express, cors only
├── docker-compose.yml       # at repo ROOT; build context ., dockerfile Docker/Dockerfile
├── Docker/Dockerfile        # node:20-alpine
├── .dockerignore
├── .vscode/extensions.json
├── public/
│   ├── Test.html            # main app (served at /)
│   ├── js/                  # shared modules (classic scripts, global fns)
│   │   ├── globals.js       # Konstanten (LPHS, ROLES, LPH-Farben, DEPARTMENTS) + db/currentDepartment
│   │   ├── utils.js         # esc, uid, Datum-/Differenz-Helfer, personIdForName, peopleByRole, assignKey, weekSortKey-Quelle
│   │   ├── ui.js            # LocalStorage-UI-State, Header-Collapse, Theme
│   │   ├── date-picker.js   # Datums-/Bereichspicker-Overlays
│   │   ├── csv.js           # CSV-Parser/Serializer + weekSortKey, filenameForWeek
│   │   ├── server.js        # baseUrl, sendCsvToServer, deleteCsvFromServer, blobToDataUrl
│   │   ├── store.js         # save/syncAllToServer, switchDepartment, load, seed
│   │   ├── weeks.js         # Wochen-Auswahl, -Erzeugung, -Klonen, Auto-Lock, -Löschung
│   │   ├── gantt.js         # GANTT-Diagramm (buildGanttHtml, openProjectPlan), geshared
│   │   ├── showmode.js      # Show-Mode (Präsentationsmodus)
│   │   ├── tabs-auth.js     # showTab, confirmPassword (SHA256-Passwortschutz)
│   │   ├── pdf.js           # PDF-Export (pdfmake, A3 quer)
│   │   └── init.js          # renderAll/initUiState + Seiteneinstieg (nur Test.html)
│   │   ├── board/           # tab-specific: Projektboard
│   │   │   └── board.js     # Projektboard-Rendering, Status-Editor, Anwesenheit, Zuordnung
│   │   ├── people/          # tab-specific: Projektbeteiligte
│   │   │   └── people.js    # Personen-Tab-Rendering + Drag-and-Drop + Abwesenheiten
│   │   ├── projects/        # tab-specific: Projekte
│   │   │   └── projects.js  # Projekte-Tab-Rendering + Drag-and-Drop
│   │   ├── load/            # tab-specific: Auslastung
│   │   │   └── load.js      # Auslastungs-Tab, LP-Modal, Neues-Projekt-Modal
│   ├── project-plan.html    # Gantt/TODO planning view
│   ├── js/plan/             # planning modules (nur von project-plan.html geladen)
│   │   ├── plan-state.js    # PlanApp-Zustand, Datenladepath, savePlan, Toasts
│   │   ├── plan-build-gantt.js # buildGanttHtml-Erweiterung (TODO- und Meilensteinzeilen)
│   │   ├── plan-gantt.js    # renderPlanGantt, Abhängigkeitslinien, Zoom, Ressourcen-Load
│   │   ├── plan-drag.js     # GANTT-Balken-Drag/Resize + TODO-/Meilenstein-Zeilen-Drag
│   │   ├── plan-modals.js   # TODO-, Meilenstein- und Abwesenheits-Modals
│   │   └── plan-schedule.js # Kritischer Pfad, Auto-Schedule, Mail-Link
│   ├── styles.css
│   └── dein_bild.ico
└── sessions/                # data; bind-mounted into Docker container
    ├── ELT/  HLS/  ARCH/    # one folder per department

Running

npm install
npm start                 # http://localhost:3003, or PORT=3004 if occupied
docker compose up --build # Docker variant (compose file is at repo root)
  • PORT env var overrides default 3003 in server.js.
  • Frontend must be served over HTTP: baseUrl() in js/server.js uses window.location.origin or falls back to http://localhost:3003 when opened via file:// (then only seed/demo data loads).
  • Check /health returns {ok:true} after starting.

Server API (server.js)

  • GET / → Test.html; GET /health.
  • Deep links per department + tab: GET /<Dept>/<Tab> → Test.html, e.g. /HLS/Auslastung or /ELT/Projektübersicht. Tab slugs (TAB_SLUGS in js/globals.js) are Projektübersicht|Auslastung|Projekte|Projektbeteiligte; anything else returns 404. Exception: GET /<Dept>/Projektplan(/<ProjName>)? (PLAN_SLUG) serves project-plan.html, the GANTT view, with weekLabel|projNr as query params (the project name is a readable path segment only; identification happens via weekLabel + projNr). The frontend reads/writes these routes via applyUrlRoute()/updateRouteUrl() in js/ui.js and all static assets in the HTML use absolute paths (/js/..., /styles.css) so they resolve on nested routes.
  • GET /load-all?department= → {sessions:[{filename,content}], people} with _people.csv content in people; sessions sorted by filename.
  • POST /save-csv {filename, csv, department} → writes UTF-8 BOM CSV.
  • POST /delete-csv {filename, department}.
  • Valid departments hard-coded as ELT|HLS|ARCH in server.js (VALID_DEPARTMENTS); anything else silently falls back to ELT. Same set in DEPARTMENTS in js/globals.js.
  • safeName() strictly validates filenames (path traversal protection); static assets get Cache-Control: no-store.
  • JSON body limit is 2 MB.

CSV format

  • Delimiter ;, UTF-8 with BOM. Hand-editing is possible but risky; the parser reconstructs objects from the fixed column layout.
  • Week files <Label>.csv (e.g. KW_30___2026.csv — filenameForWeek() replaces non-word chars with _). Row layout is positional: WOCHE (label, date, locked flag) → PERSONEN (name|role|fullName per column) → header row → ANWESENHEIT → one row per project.
  • _people.csv per department: NAME;Rollen;Voller Name;E-Mail;Abwesenheiten (absences as JSON). People are global per department; new people are copied into the current week. Historical weeks keep their own person copies, so deleting a global person leaves old sessions unaffected no matter what.
  • Don't change the column layout without updating both weekToCsv() and parseSessionCsv() (js/csv.js), which must stay inverse of each other.

Frontend architecture

  • The frontend is split into small classic non-module <script> files under public/js/; all functions are globals on window. Two HTML files include them via <script src> tags; load order in the HTML defines dependencies, since there is no bundler:
    • Test.html loads globals → utils → ui → date-picker → csv → server → store → weeks (shared modules), then the tab-specific folders board → people → projects → load, finally gantt → showmode → tabs-auth → pdf → init. See the exact list in Test.html.
    • project-plan.html loads only globals → utils → ui → date-picker → csv → server → gantt plus the js/plan/* modules. It must NOT load modules that touch main-only page elements (store, weeks, board, …, init).
  • Shared state object window.PlanApp (js/plan/plan-state.js) holds all project-plan page state (week, project, zoom level, drag/modal-state), db/currentDepartment live in js/globals.js.
  • State: let db = { weeks, currentWeek, globalPeople } (in-memory only).
  • Project IDs are regenerated on every CSV load, so projects are addressed cross-page by department + week label + projNr (+ readable projName path segment) via URL params: openProjectPlan() builds /HLS/Projektplan/<ProjName>?weekLabel=…&projNr=…, findProjectForPlan resolves them.
  • Person IDs are stable across reloads via personIdForName(name) → p_<lowercased alnum>.
  • save() debounces 500 ms then syncAllToServer() writes all weeks plus _people.csv — not just the changed week. Exception: js/plan/plan-state.js savePlan() bypasses save() and writes only the current week's CSV directly via sendCsvToServer().
  • switchDepartment() cancels a pending save debounce before switching, and load() discards stale /load-all responses after a department switch (frozen requestedDepartment), so late responses can never render or persist data under the wrong department.
  • Person drag-and-drop in the people tab: drag handle (.drag-handle) binds ondragstart/ondragend, the .people-sortable container binds ondragover/ondrop; insertion is computed by getDragAfterElement(container, y, selector) (js/projects/projects.js, used with .person-card here and .person-list-item in the projects tab). reorderPeople() updates both db.globalPeople and the current week's person copy, then save(). No drag handles are rendered on locked weeks.
  • Weeks are sorted by calendar week extracted from the label (KW XX / YYYY) via weekSortKey(), not alphabetically/by filename.
  • applyAutoLock() locks every week except the chronologically last one after load/clone/new-week; localStorage UI state is not restored for locked tabs.
  • localStorage keys: projuebersicht_v1 (department only — tab is never restored from storage; at startup the tab comes from the deep-link URL if present, otherwise board), projuebersicht-theme, header-collapse key. Do not store data in localStorage; it is UI state only.
  • Deep-link URLs (/<Dept>/<Tab>) are shared via the address bar: updateRouteUrl() rewrites the path via history.replaceState on tab and department switches; protected tabs keep the URL but open the password modal.

Auth / security quirk

  • The projects and people tabs, week deletion, and week unlock are gated by a password modal comparing CryptoJS.SHA256(input) to PROTECTED_PASSWORD_HASH (js/globals.js, modal in js/tabs-auth.js). Client-side only — not real security. CryptoJS and pdfmake (PDF export) are loaded from cdnjs.cloudflare.com in Test.html, so those features need internet access and won't work offline. project-plan.html loads neither.
  • User-provided status HTML is sanitized on render via a small whitelist (sanitizeStatusHtml in js/board/board.js) to mitigate XSS.

Docker

  • docker compose up --build from the repo root. Compose maps 3003:3003, bind-mounts ./sessions:/app/sessions, restart: unless-stopped.
  • Dockerfile: node:20-alpine, npm ci --production, runs as non-root node, HEALTHCHECK on /health. .dockerignore excludes Docker/* (except Docker/Dockerfile), node_modules/, .git, npm-debug.log.

Conventions & verification

  • Git repo; commits are German (e.g. "PDF Export von A4 auf A3 …"). Remote: http://192.168.3.187:3000/erik/projektuebersicht.git.
  • No linter, formatter, typecheck, or test suite. Manual verification only: start server, open http://localhost:3003, exercises the relevant tab, confirm CSV changes in sessions/.