Files
projektuebersicht/AGENTS.md
T

13 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 in a SQLite database (node:sqlite, needs Node >= 22.13) at sessions/projektuebersicht.db.

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, server, gantt) plus the plan modules under js/plan/ — NOT the full main module set.

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

File structure

.
├── server.js                 # Express server + SQLite API
├── package.json             # deps: express, cors only
├── docker-compose.yml       # at repo ROOT; build context ., dockerfile Docker/Dockerfile
├── Docker/Dockerfile        # node:22-alpine (nötig für node:sqlite)
├── .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
│   │   ├── ui.js            # LocalStorage-UI-State, Header-Collapse, Theme
│   │   ├── date-picker.js   # Datums-/Bereichspicker-Overlays
│   │   ├── server.js        # baseUrl, sendWeekToServer, sendPeopleToServer, 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/                # SQLite-DB; bind-mounted into Docker container
    └── projektuebersicht.db # node:sqlite database (WAL mode)

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= → {weeks:[weekObj], people:[personObj]} with the global people list in people; weeks are full JSON week objects (id, label, date, locked, people, attendance, projects), sorted by label.
  • POST /save-week {department, week} → upserts one week (JSON object, keyed by department + label). Used by sendWeekToServer().
  • POST /save-people {department, people} → replaces the department's global people list (JSON array).
  • POST /delete-week {department, label} → deletes the week; missing label yields 400, missing week tolerates with deleted:false.
  • 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.
  • Static assets get Cache-Control: no-store.
  • JSON body limit is 2 MB.

Persistence (SQLite)

  • Data lives in sessions/projektuebersicht.db (node:sqlite DatabaseSync, WAL mode, PRAGMA foreign_keys = ON), created automatically. The schema is fully relational (18 tables) instead of one big JSON document per week:
    • weeks (department, label, week_id, date, locked; PK (department,label))
    • week_people / week_people_roles — person copies per week (array_order keeps the list order, role_order the role array order)
    • attendance — présence per week/person
    • projects (array_order = position in the JSON array, order_value = the p.order field, which is only unique within a group, not globally!)
    • project_lph, project_assign (person_id|role keys split into columns), project_aufwand (LP effort per role), project_aktuelle_lp
    • project_plan, plan_people, plan_todos / plan_todo_assignees / plan_todo_deps, plan_milestones (position column!) / plan_milestone_deps
    • people / person_roles / person_absences — global people per department
  • The HTTP API stays JSON-shaped: serializeWeek() / serializeGlobalPeople() split incoming week/people objects across the tables inside a transaction, loadWeeks() / loadGlobalPeople() (re)assemble them on read. saveWeekRelational() deletes the week's rows first (deleteWeekData(), note weeks uses label, all child tables week_label).
  • Migration on every start (idempotent): migrateLegacySchema() converts a DB from the old single-JSON-document format (detected via a data column in weeks): it reads the documents, drops all tables, recreates the relational schema and inserts everything in one transaction.
  • The DB file sits in sessions/, so the existing Docker bind-mount ./sessions:/app/sessions keeps it persistent. The path is overridable via the DB_PATH environment variable (used for tests), production default is sessions/projektuebersicht.db.
  • Project/person IDs are stable across reloads; only week-level fields are queryable columns.

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 → 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 → 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/person IDs are stable across reloads because the server splits and reassembles week objects deterministically (JSON IDs are kept in the DB columns); 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 follow personIdForName(name) → p_<lowercased alnum>.
  • save() debounces 500 ms then syncAllToServer() writes all weeks (via sendWeekToServer()) plus the people list (via sendPeopleToServer()) — not just the changed week. Exception: js/plan/plan-state.js savePlan() bypasses save() and writes only the current week directly via sendWeekToServer().
  • 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:22-alpine (nötig für node:sqlite), 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 SQLite changes (the DB file sessions/projektuebersicht.db grows).