# 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 L1–L4, BGP, peering, security, NMS, automatisering, dokumentasjon, incident, prosjekt. Hver oppgave har prioritet (P1–P4), 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` på `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.