netops/netops-todo-node/CLAUDE.md
Jon Vanvik bdeb6254b9 Dagsplan: flytt/navn/deling + plan-badge, kategorier, feltmodus
Dagsplan kan navngis og flyttes til annen dato (slår sammen uten datatap).
Delbar som hovedregel (shared-flagg default true) med toggle for privat;
andres delte planer kan ses skrivebeskyttet via "Plan for"-velger.
getPlanFor håndhever tilgang. Per-oppgave plan-badge (dag + person) via
planIndex filtrert per viewer.

Egendefinerte kategorier (categories.json) i tillegg til de 12 innebygde;
dynamisk validering, deles via WS, addes fra kategori-nedtrekket.

Feltmodus (body.field-mode): større berøringsmål for mobil, auto på <640px.

plans.json migreres array -> {name,items,shared} ved oppstart (bakoverkompat).
26 integrasjonstester (6 nye), alle grønne. README + CLAUDE.md oppdatert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-15 13:59:19 +02:00

185 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# NetOps To-Do (Node) — Handoff / Project context
> Egnet som `CLAUDE.md` for fremtidige Claude-sesjoner. Dette er
> videreutviklingen av PHP-versjonen i `../netops-todo-server/` — samme formål
> og datamodell, men ombygd til Node/npm med sanntidssynk og flere
> samarbeidsfunksjoner. PHP-versjonen ligger urørt ved siden av.
## Hva er dette
To-do-applikasjon for en network engineer / et lite driftsteam. Kategorier
L1L4, BGP, peering, security, NMS, automatisering, dokumentasjon, incident,
prosjekt. Hver oppgave har prioritet (P1P4), status, estimat, lokasjon, frist,
tags, eier, kommentarer og audit-felter.
Nytt mot PHP-versjonen:
- **WebSocket-sanntidssynk** (erstatter 60s-polling) + presence + "redigerer nå"
- **Kanban-tavle** med drag & drop, i tillegg til tabellvisning
- **Dagsplan** (tredje visning) — per bruker, per dato; ordnet liste man bygger
ved å plukke fra backlog. Drag-sortering, kapasitet i timer, navn, flytt hele
planen til en annen dato, og delbar (default delt; toggle for privat). Andres
delte planer kan ses skrivebeskyttet. Plan-membership vises som badge på
oppgavekort/-rader (dag + person).
- **Egendefinerte kategorier** — i tillegg til de 12 innebygde; lagres i
categories.json, deles via WS, valideres dynamisk.
- **Feltmodus** — `body.field-mode`-toggle for større berøringsmål på mobil.
- **Kommentar ved statusbytte** — valgfri `comment``PUT /api/tasks/:id`
festes som kommentar med `kind:'status'` og legges i aktivitets-detaljen.
- **Kommentarer** per oppgave
- **Aktivitetslogg** (hvem gjorde hva, server-side, capped til 400 entries)
- **Optimistisk låsing** — `v`-felt per oppgave, 409 ved konflikt
- **Hurtig-registrering** med token-parsing (`#kat @eier p1 !frist ~timer loc:sted`)
- **Angre sletting**, tastatursnarveier, persistente filtre (localStorage)
## Filer
```
netops-todo-node/
├── server.js Express + ws. REST-API, WebSocket-hub, auth-oppslag.
├── lib/store.js Datalager: normalisering, validering, aktivitetslogg,
│ atomisk JSON-persistens. Kilden til sannhet for skjema.
├── public/
│ ├── index.html Markup. Ingen templating — app.js fyller alt.
│ ├── app.js All frontend-logikk (vanilla JS, ingen rammeverk).
│ └── style.css Mørkt tema, samme visuelle identitet som PHP-versjonen.
├── seed.json 16 eksempel-oppgaver.
├── setup-debian13.sh Engangs-oppsett på Debian 13 LXC (apt + npm ci + systemd).
├── test/api.test.js Integrasjonstester (node:test) — REST + WS ende-til-ende.
├── data/ Opprettes ved kjøring: tasks.json, activity.json,
│ users.json, plans.json, categories.json.
└── README.md Installasjon, proxy-oppsett, API-referanse.
```
## Arkitektur
- **Alle mutasjoner via REST** (lett å curl-e/feilsøke). WebSocket (`/ws`)
brukes kun til push: endringer, presence, editing-signaler.
- Én node-prosess eier datakatalogen. Lagring i minnet, skriv til disk er
atomisk (tmp + rename) og serialisert gjennom en promise-kjede i `store.js`.
- Auth ligger utenfor appen: `X-Remote-User`-header > Basic Auth > cookie
(`netops_user`, kan skrus av med `DISABLE_COOKIE_AUTH=1`) > `anon`.
Samme oppslag for HTTP og WS-upgrade (`userFromRequest` i server.js).
### WebSocket-protokoll (server → klient)
| type | Innhold | Når |
|---|---|---|
| `hello` | `user`, `online[]`, `editing{}` | Ved tilkobling |
| `sync` | `tasks[]`, `activity[]`, evt. `by` | Ved tilkobling + etter replace/seed |
| `task` | `op: create/update/delete`, `task`/`id`, `by`, `activity` | Etter hver mutasjon |
| `presence` | `online[]` | Når noen kobler til/fra |
| `editing` | `editing{}` = `{taskId: [brukere]}` | Når noen åpner/lukker modal |
| `plan` | `user`, `date`, `name`, `items[]`, `shared` | Etter plan-endring. Delte planer → alle; private → kun eieren (andre får tom `items` for å fjerne badge) |
| `categories` | `categories[]`, `by` | Etter `POST /api/categories` — til alle |
Klient → server: kun `{ type: 'editing', id, active }`.
### Task-skjema (kanonisk form — `normalizeTask()` i lib/store.js)
Som PHP-versjonen, pluss:
```json
{
"v": 3,
"comments": [{ "id": "c_…", "by": "jon", "at": "ISO", "text": "≤1000",
"kind": "status", "status": "done" }]
}
```
`v` bumpes ved hver felt-oppdatering (også når en `comment` følger med et
statusbytte via `update()`, siden det ER en oppdatering — men IKKE via det
separate `POST .../comments`-endepunktet, så en ren kommentar ikke gir falsk
konflikt for noen som redigerer felter samtidig). `kind`/`status` settes kun på
kommentarer som kommer fra et statusbytte; vanlige kommentarer mangler dem.
Klienten sender `v` den redigerte fra; mismatch → 409 med serverens task i
svaret. Frontend håndterer det i `handleSaveError()`: oppdaterer visningen,
viser varsel, og lar brukeren lagre på nytt (som da overskriver bevisst).
### Dagsplan-skjema (`plans.json`)
```json
{ "jon": { "2026-06-15": { "name": "Feltdag Bergen", "items": ["t_abc", "t_def"], "shared": true } } }
```
Per bruker, per dato. `shared` default `true` (planer er synlige for alle som
hovedregel). `_planEntry()` normaliserer både gammelt array-format og nytt
objektformat (migrering skjer i `_prunePlans()` ved oppstart). `getPlan()`
filtrerer bort slettede oppgaver; `getPlanFor(viewer, owner, date)` håndhever
tilgang (egen, eller delt → ellers null/403); `setPlan/setPlanName/setPlanShared`
muterer; `movePlan()` slår sammen til måldato uten datatap; `_scrubFromPlans()`
rydder ved sletting; `_prunePlans()` dropper datoer eldre enn 30 dager.
`planIndex(viewer)` returnerer `{taskId: [{user,date,name}]}` filtrert per viewer
(delte + egne private) — driver plan-badgene på kort/rader.
### Kategorier (`categories.json`)
`DEFAULT_CATEGORIES` (12, i koden) + egendefinerte fra `categories.json`
(`[{id,name,color,desc,custom:true}]`). `store.categories()`/`categoryIds()` gir
det effektive settet; `normalizeTask` validerer mot `opts.validCategories =
this.categoryIds()`. `addCategory()` genererer en unik id fra navnet og tildeler
farge fra en palett. Frontend får lista fra `/api/state` og via `categories`-WS;
`state.categories` + `catColor/catName/catById`-hjelperne erstatter den gamle
hardkodede `CATEGORIES`/`CAT_COLORS`.
## Designvalg verdt å vite
- **Ingen database, ingen frontend-rammeverk, to npm-avhengigheter**
(express, ws). Samme "lett å patche for neste vakthavende"-filosofi som før.
- **Broadcast går til alle, inkludert avsender.** Klienten upserter idempotent
på id, så dobbel oppdatering er harmløs. REST-svaret brukes også, slik at
UI-et fungerer selv om WS er nede (da poller frontend hvert 30s).
- **Delvis update er lov:** `PUT /api/tasks/:id` med bare `{v, status}`
normalisering merger mot eksisterende. Quick-actions bruker dette.
- **Statuskommentar går gjennom samme update-kall** (transient `comment`-felt),
ikke et eget endepunkt — så statusbytte + kommentar + aktivitetslinje blir
atomisk og ett enkelt WS-broadcast. `changeStatusWithComment()` i app.js viser
dialogen for ferdig/blokkert; tavle-drag og tabell-✓ ruter gjennom den.
- **Dagsplanen er optimistisk i frontend:** `addToPlan`/`removeFromPlan`/drag
muterer `state.plan.items` og kaller `savePlan()` (PUT) som reconciler mot
serverens validerte svar. Container-nivå drag-lyttere bindes én gang
(`bindPlanContainer`); per-element bindes ved hver render (`bindPlanDnD`).
- **Kommentar-sletting er begrenset til egen bruker** (eneste
autorisasjonsregel i appen — alt annet er åpent for alle innloggede).
- Chart.js fra CDN; frontend degraderer pent (skjuler grafer) uten nett.
## Sikkerhetsstatus
Som PHP-versjonen (XSS-escaping i alle render-paths, whitelist-enums,
strenglengde-klamping, brukernavn-regex `[A-Za-z0-9._@-]{1,64}`), pluss:
- `/api/login` avviser ugyldige navn i stedet for å sanitere stille.
- Cookie settes med `SameSite=Lax` — gir grunnleggende CSRF-vern for
cookie-auth-modus. Bak proxy-auth gjelder samme vurdering som før
(intern bruk OK; offentlig nett → legg på token).
- `express.json({ limit: '2mb' })` begrenser payload (import av store lister).
## Vanlige utvidelser
- **Nytt felt per oppgave:** input i modal (index.html) → `saveForm()` og
`parseQuickAdd()` om ønskelig (app.js) → `normalizeTask()` (store.js) →
`diffDetail()` for aktivitetsloggen → `renderCard()`/`renderTable()`.
- **Ny kategori:** `CATEGORIES`/`CAT_COLORS` i app.js + `VALID_CATEGORIES`
i store.js.
- **SQLite:** bytt persistens i `Store` (load/_persist) mot `node:sqlite`
resten av appen er uberørt siden alt går gjennom Store-metodene.
- **Varsler ved frist:** server har allerede deadline per task; en
`setInterval` i server.js som broadcaster `{type:'due', …}` + Notification
API i frontend er den naturlige veien.
## Testing-status
- `npm test`: 26 integrasjonstester (node:test), booter ekte server på
tilfeldig port med temp-datakatalog. Dekker normalisering, versjonskonflikt,
kommentar-autorisasjon, statuskommentar-på-update, dagsplan (CRUD, per-bruker,
validering, scrub ved sletting, dato-fallback, navn, flytt/merge, deling +
tilgang, planIndex per viewer), egendefinerte kategorier, aktivitetslogg,
WS-broadcast (delt vs. privat plan), persistens, cookie-auth. Alle grønne per
2026-06-15 (Node 25).
- Manuell røyk-test over HTTP: kategori-opprettelse (auto-id + farge), navngitt
delt plan, annen brukers tilgang (delt 200 / privat 403), flytt med bevart
navn+delingsstatus, og planIndex verifisert.
- Ikke testet i nettleser med flere samtidige brukere — første ekte test bør
være to nettleservinduer mot `npm start`: del en dagsplan og se den hos den
andre brukeren + badge på oppgaven, flytt planen til en annen dag, legg til en
egendefinert kategori, og slå på feltmodus på en smal skjerm.