From 271b3fc8048f23bae7826c8d7b5fbb25f366e253 Mon Sep 17 00:00:00 2001 From: alexpolo1 Date: Tue, 18 Aug 2026 13:45:34 +0200 Subject: [PATCH] docs: plan carpenter walkthrough fixes --- ...8-18_133633-carpenter-walkthrough-fixes.md | 597 ++++++++++++++++++ 1 file changed, 597 insertions(+) create mode 100644 .hermes/plans/2026-08-18_133633-carpenter-walkthrough-fixes.md diff --git a/.hermes/plans/2026-08-18_133633-carpenter-walkthrough-fixes.md b/.hermes/plans/2026-08-18_133633-carpenter-walkthrough-fixes.md new file mode 100644 index 0000000..65257a8 --- /dev/null +++ b/.hermes/plans/2026-08-18_133633-carpenter-walkthrough-fixes.md @@ -0,0 +1,597 @@ +# Tømrer-gennemgang Fixes Implementation Plan + +> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task. + +**Goal:** Gør det månedlige mobile tømrerflow stabilt, statusmæssigt entydigt og reelt klar til PDF/Ordrestyring, mens opdateringen af STARK-priser behandles som et separat dataleverance-spor med Jannick. + +**Architecture:** Ret først de deterministiske UI-state-fejl (PDF freshness og fælles readiness-model), derefter performance i projektindlæsningen og til sidst materialekoblingsflowet. Materiale-id'er må kun komme fra den eksisterende masterdatabase og kræver brugerbekræftelse ved tvetydige matches. STARK-import udføres først, når Jannick leverer en frisk fil; kode- og datarisiko blandes ikke sammen. + +**Tech Stack:** React 18, Jest/React Testing Library, Node/Express, MySQL, Playwright, eksisterende `smartPackageMaterialMatchService`, PM2/GitHub Actions. + +--- + +## Nuværende baseline + +Produktionstesten på projekt 392 viste: + +- Mobilflowet virker uden vandret overflow. +- Geometri, økonomi og PDF genereres korrekt. +- Kontrolleret total: `177.781,60 kr. inkl. moms`. +- PDF-preview: 2 A4-sider, 109.141 bytes, gyldigt `%PDF-`. +- Loading-overlayet brugte op til ca. 5,7 sekunder i en kold gennemgang. +- En ny PDF blev straks markeret som forældet. +- Final Review blokeres af 11 materialer uden `material_id`. +- ProjectFlow viste 12 ukoblede materialer, mens Final Review viste 11. +- Topstatus viste `4/4 trin gennemført`, samtidig med at afsendelse var blokeret. +- STARK-prislisten viste seneste dato `26.11.2025`; Alex afklarer nye priser med Jannick. + +QA-rapport og evidens: + +- `/home/alex/dogfood-output/tilbudsgivern-2026-08-18/report.md` +- `/home/alex/dogfood-output/tilbudsgivern-2026-08-18/carpenter-audit.pdf` +- `/home/alex/dogfood-output/tilbudsgivern-2026-08-18/screenshots/pdf-state-after-generation.png` +- `/home/alex/dogfood-output/tilbudsgivern-2026-08-18/screenshots/loading-overlay-after-5s.png` + +--- + +## Spor A — Kodefixes, som kan udføres nu + +### Task 1: Lås regressionsbaselinen med målrettede tests + +**Objective:** Gør de fire observerede kode-/UX-fejl reproducerbare, før produktionskode ændres. + +**Files:** +- Modify: `frontend/src/components/FinalReview.test.js` +- Create: `frontend/src/utils/projectReadiness.test.js` +- Create: `frontend/src/components/ProjectFlow.test.js` +- Modify: `tests/monthly-carpenter-audit.spec.js` + +**Step 1: Skriv en fejlende PDF freshness-test** + +Test at en PDF er `fresh` umiddelbart efter generering, og først bliver `stale`, når et input ændres efter genereringen. + +**Step 2: Kør testen og bekræft RED** + +Run: + +```bash +cd frontend +CI=true npm test -- --watchAll=false src/components/FinalReview.test.js +``` + +Expected: FAIL — PDF bliver markeret stale på grund af `pdfUrl`-effekten. + +**Step 3: Skriv fejlende readiness-tests** + +Dæk: + +- samme antal ukoblede materialer i ProjectFlow og Final Review +- forskellen mellem `flowComplete`, `readyForReview` og `readyToSend` +- `4/4 gennemført` må ikke betyde `klar til afsendelse`, når blokeringer findes + +**Step 4: Skriv fejlende loading/performance-test** + +Mock seks uafhængige ressourcekald med forsinkelse og bekræft, at de ikke må afvikles serielt. + +**Step 5: Commit tests alene** + +```bash +git add frontend/src/components/FinalReview.test.js \ + frontend/src/utils/projectReadiness.test.js \ + frontend/src/components/ProjectFlow.test.js \ + tests/monthly-carpenter-audit.spec.js +git commit -m "test: lock carpenter walkthrough regressions" +``` + +--- + +### Task 2: Ret PDF freshness med en deterministisk input-signatur + +**Objective:** En netop genereret PDF skal være frisk, og kun reelle inputændringer må gøre den stale. + +**Files:** +- Create: `frontend/src/utils/pdfSourceSignature.js` +- Create: `frontend/src/utils/pdfSourceSignature.test.js` +- Modify: `frontend/src/components/FinalReview.js:367-371,1198-1280,1348-1369` +- Modify: `frontend/src/components/FinalReview.test.js` + +**Step 1: Skriv en ren signaturfunktion** + +`buildPdfSourceSignature` skal serialisere de normaliserede værdier, som faktisk sendes til `/api/pdf/generate`: + +- tilbudstekst og kundebeskrivelse +- normaliseret geometri +- materialer: id, navn, antal, enhed og pris +- udlejning +- arbejdsopgaver og timer +- gemt økonomimodel + +Ignorér React object identity, tidsstempler og UI-only state. + +**Step 2: Verificér RED** + +Run: + +```bash +cd frontend +CI=true npm test -- --watchAll=false src/utils/pdfSourceSignature.test.js +``` + +Expected: FAIL — modulet findes endnu ikke. + +**Step 3: Implementér minimal signatur** + +Gem `generatedPdfSignature` sammen med `pdfUrl` efter et succesfuldt response. Beregn: + +```js +const isPdfStale = Boolean(pdfUrl && generatedPdfSignature !== currentPdfSignature); +``` + +Fjern effekten på `FinalReview.js:1357-1369`, som inkluderer `pdfUrl` i dependencies og derfor straks kalder `setIsPdfStale(true)` efter generering. + +**Step 4: Kør fokuserede tests** + +```bash +cd frontend +CI=true npm test -- --watchAll=false \ + src/utils/pdfSourceSignature.test.js \ + src/components/FinalReview.test.js +``` + +Expected: PASS. + +**Step 5: Commit** + +```bash +git add frontend/src/utils/pdfSourceSignature.js \ + frontend/src/utils/pdfSourceSignature.test.js \ + frontend/src/components/FinalReview.js \ + frontend/src/components/FinalReview.test.js +git commit -m "fix: keep newly generated PDF preview fresh" +``` + +--- + +### Task 3: Parallelisér og mål projektindlæsningen + +**Objective:** Fjern unødvendig seriel ventetid og hold loading-overlayet under en dokumenteret SLA. + +**Files:** +- Create: `frontend/src/services/projectHydrationService.js` +- Create: `frontend/src/services/projectHydrationService.test.js` +- Modify: `frontend/src/components/ProjectFlow.js:222-265,311-595,807-839` +- Modify: `frontend/src/components/ProjectFlow.test.js` +- Modify: `frontend/src/utils/quoteFlowTelemetry.js` +- Modify: `tests/monthly-carpenter-audit.spec.js` + +**Step 1: Skriv en timing-test** + +Mock geometri, arbejdsløn, materialer, udlejning og pakker med kendte delays. Bekræft at samlet tid svarer omtrent til det langsomste kald, ikke summen. + +**Step 2: Kør RED** + +```bash +cd frontend +CI=true npm test -- --watchAll=false src/services/projectHydrationService.test.js +``` + +**Step 3: Implementér parallel hydration** + +Efter ét projekt-check hentes følgende med `Promise.allSettled` og fælles `AbortController`: + +- `/api/customer-projects/:id/geometry` +- `/api/customer-projects/:id/labor` +- `/api/customer-projects/:id/materials` +- `/api/customer-projects/projects/:id/rentals` +- `/api/customer-projects/:id/packages` + +Bevar nuværende session fallbacks. Ignorér resultater fra et tidligere projekt, hvis brugeren skifter projekt under indlæsningen. + +**Step 4: Fjern dobbelt projekt-verificering** + +`selectExistingProject` har allerede hentet projektet før `loadProjectData`; send det verificerede payload videre i stedet for at hente samme projekt igen. + +**Step 5: Tilføj performance telemetry** + +Registrér: + +- `project_hydration_started` +- `project_hydration_ready` +- varighed og langsomste ressource + +Ingen kunde-fritekst må logges. + +**Step 6: Acceptance** + +- Kold mobilindlæsning: mål p95 over fem runs. +- Mål: overlay væk på `< 5.000 ms`; stretch `< 3.000 ms`. +- Ved overskridelse skal UI vise hvilken ressource, der stadig indlæses, i stedet for et generisk overlay. + +**Step 7: Commit** + +```bash +git add frontend/src/services/projectHydrationService.js \ + frontend/src/services/projectHydrationService.test.js \ + frontend/src/components/ProjectFlow.js \ + frontend/src/components/ProjectFlow.test.js \ + frontend/src/utils/quoteFlowTelemetry.js \ + tests/monthly-carpenter-audit.spec.js +git commit -m "perf: parallelize project hydration" +``` + +--- + +### Task 4: Indfør én fælles readiness-model + +**Objective:** ProjectFlow og Final Review skal vise samme antal, samme blokeringer og tydelige niveauer for færdighed. + +**Files:** +- Create: `frontend/src/utils/projectReadiness.js` +- Create: `frontend/src/utils/projectReadiness.test.js` +- Modify: `frontend/src/components/ProjectFlow.js:118-191,807-812` +- Modify: `frontend/src/components/FinalReview.js:220-290` +- Modify: `frontend/src/components/FinalReview.test.js` + +**Step 1: Definér eksplicitte tilstande** + +Returnér mindst: + +```js +{ + completedSteps, + totalSteps, + flowComplete, + readyForReview, + readyToSend, + blockingChecks, + attentionChecks, + unlinkedMaterialCount +} +``` + +**Step 2: Brug samme materialekilde** + +ProjectFlow må ikke tælle fra et gammelt `packageData.materials`, mens Final Review tæller fra persisted/editable materials. Normalisér én gang og brug samme projektlinjer som kilde. + +**Step 3: Ret teksten** + +Eksempel: + +- `4/4 trin gennemført` +- `Klar til review — ikke klar til afsendelse` +- `11 materialer kræver kobling` + +Vis aldrig `Alt er klar`, når `readyToSend === false`. + +**Step 4: Verificér tests** + +```bash +cd frontend +CI=true npm test -- --watchAll=false \ + src/utils/projectReadiness.test.js \ + src/components/FinalReview.test.js \ + src/components/ProjectFlow.test.js +``` + +**Step 5: Commit** + +```bash +git add frontend/src/utils/projectReadiness.js \ + frontend/src/utils/projectReadiness.test.js \ + frontend/src/components/ProjectFlow.js \ + frontend/src/components/FinalReview.js \ + frontend/src/components/FinalReview.test.js \ + frontend/src/components/ProjectFlow.test.js +git commit -m "fix: unify carpenter flow readiness status" +``` + +--- + +### Task 5: Byg et sikkert materiale-koblingsflow + +**Objective:** Tømreren skal kunne løse ukoblede materialer uden at opfinde eller automatisk godkende forkerte masterdata-id'er. + +**Files:** +- Modify: `backend/src/services/smartPackageMaterialMatchService.js` +- Modify: `backend/__tests__/smartPackageMaterialMatchService.test.js` +- Modify: `backend/src/services/projectMaterialService.js` +- Modify: `backend/src/__tests__/projectMaterialService.test.js` +- Modify: `backend/src/routes/customerProjects.js` +- Create: `backend/__tests__/projectMaterialMatchingRoute.test.js` +- Create: `frontend/src/components/MaterialLinkReview.js` +- Create: `frontend/src/components/MaterialLinkReview.test.js` +- Modify: `frontend/src/components/FinalReview.js:268-274,1733-1743` + +**Step 1: Tilføj read-only match-preview endpoint** + +Forslag: + +```text +POST /api/customer-projects/projects/:projectId/materials/match-preview +``` + +Input: konkrete projektmateriale-id'er. Output pr. linje: + +- kandidatens master `material_id` +- SKU, navn, enhed, leverandør og pris +- score og status: `matched_sku`, `matched_name`, `ambiguous`, `unmatched` + +Ingen writes i preview-kaldet. + +**Step 2: Test sikkerhedskontrakten** + +- SKU exact match må foreslås med score 1. +- Tvetydige navne må ikke auto-godkendes. +- Ukendte materialer forbliver ukoblede. +- Kun aktive materialer med brugbar pris må foreslås. + +**Step 3: Udvid projektmateriale-update sikkert** + +`ProjectMaterialService.updateProjectMaterial` skal kunne gemme: + +- `material_id` +- `price_source` +- `price_source_updated_at` + +Brug parameteriseret SQL og verify, at id'et findes i mastertabellen før update. + +**Step 4: Byg review-UI** + +Fra blokeringen i Final Review åbner `Ret koblinger` en mobilvenlig liste: + +- original projektlinje +- bedste kandidat og score +- søg/ændr kandidat +- `Bekræft` pr. linje +- batch-knap kun for eksplicit valgte linjer + +High-confidence forslag er forslag, ikke skjulte automatiske writes. + +**Step 5: Genberegn readiness efter save** + +Efter en bekræftet kobling genindlæses projektmaterialerne, og både ProjectFlow og Final Review bruger den fælles readiness-model. + +**Step 6: Verificér** + +```bash +cd backend +npm test -- --runInBand \ + __tests__/smartPackageMaterialMatchService.test.js \ + src/__tests__/projectMaterialService.test.js \ + __tests__/projectMaterialMatchingRoute.test.js + +cd ../frontend +CI=true npm test -- --watchAll=false \ + src/components/MaterialLinkReview.test.js \ + src/components/FinalReview.test.js +``` + +**Step 7: Commit** + +```bash +git add backend/src/services/smartPackageMaterialMatchService.js \ + backend/__tests__/smartPackageMaterialMatchService.test.js \ + backend/src/services/projectMaterialService.js \ + backend/src/__tests__/projectMaterialService.test.js \ + backend/src/routes/customerProjects.js \ + backend/__tests__/projectMaterialMatchingRoute.test.js \ + frontend/src/components/MaterialLinkReview.js \ + frontend/src/components/MaterialLinkReview.test.js \ + frontend/src/components/FinalReview.js +git commit -m "feat: add reviewed project material linking flow" +``` + +--- + +## Spor B — Nye STARK-priser med Jannick + +### Task 6: Modtag, valider og importer ny STARK-prisliste + +**Objective:** Opdatér prisgrundlaget uden at blande en ekstern dataleverance ind i kodefixene. + +**Owner:** Alex + Jannick for dataleverancen; implementer/operatør for validering og import. + +**Existing Files/Endpoints:** +- `backend/src/routes/starkImport.js` +- `backend/src/services/starkImportService.js` +- `POST /api/stark/upload` +- `GET /api/stark/status` +- `GET /api/stark/import-history` + +**Input fra Jannick:** + +- Frisk STARK CSV +- Prisens gyldighedsdato +- Bekræftelse af netto/brutto-fortolkning +- Enhedsdefinitioner +- Eventuelle udgåede produkter + +**Step 1: Preflight uden produktionswrite** + +Kopiér filen til et isoleret testmiljø og verificér: + +- mindst fem semikolonseparerede kolonner +- unik SKU/produktnummer +- gyldige positive priser +- kendte enheder +- ingen uventet massiv produktdeaktivering + +**Step 2: Tag database-backup** + +Backup mindst: + +- `stark_materials_cache` +- aktive `material_prices` for STARK +- importhistorik + +**Step 3: Importér med eksisterende endpoint/service** + +Upload filen via `POST /api/stark/upload`. Gem `batchId`, statistik, errors og warnings. + +**Step 4: Post-import gates** + +`GET /api/stark/status` skal vise: + +- `latest_import` svarende til den nye levering +- forventet produktantal og kategorier +- ingen nul-/negative priser i aktive produkter + +Sammenlign 20 repræsentative tagmaterialer før/efter. Afvigelser over en aftalt procent kræver manuel godkendelse. + +**Step 5: Opdatér readiness-advarslen** + +Final Review skal læse samme statusendpoint og vise en konkret freshness-policy, fx: + +- grøn: ≤ 30 dage +- gul: 31–60 dage +- blokerende/rød: > 60 dage, hvis fast pris skal sendes + +Den endelige grænse aftales med Alex/Jannick. + +**Step 6: Dokumentér importen** + +Gem batch-id, filhash, gyldighedsdato, rækkeantal og godkender uden at committe selve prisfilen, hvis den er fortrolig. + +--- + +## Spor C — Samlet regression, rollout og drift + +### Task 7: Gør månedlig tømrer-audit til release-gate + +**Objective:** Bevis at det observerede produktionsflow er stabilt efter rettelserne. + +**Files:** +- Modify: `tests/monthly-carpenter-audit.spec.js` +- Modify: `.github/workflows/ci.yml` +- Create: `docs/qa/CARPENTER_WALKTHROUGH_RELEASE_GATE.md` + +**Step 1: Opdatér testen uden at skjule performanceproblemer** + +Testen skal logge faktisk hydration-tid og fejle på en eksplicit SLA i stedet for Playwrights implicitte 5-sekunders expect-timeout. + +**Step 2: Tilføj acceptance checks** + +- ingen 5xx API-responses +- ingen console errors +- én fælles unlinked-material count +- ny PDF viser `PDF er opdateret` +- PDF preview starter med `%PDF-` +- PDF er højst 3 sider +- ingen vandret mobil-overflow +- ingen afsendelse, hvis readiness er blokeret +- afsendelse kan aktiveres, når alle materiale-id'er og prisfreshness er i orden + +**Step 3: Kør fem gange** + +```bash +cd tests +for i in 1 2 3 4 5; do + PLAYWRIGHT_BASE_URL=http://127.0.0.1:4032 \ + npx playwright test monthly-carpenter-audit.spec.js --project=chromium --reporter=list || exit 1 +done +``` + +Expected: 5/5 PASS og dokumenteret p95 hydration-tid. + +**Step 4: CI-gate** + +Gør tømrer-audit blokerende for relevante PR'er. Fjern `continue-on-error` for denne gate. Artifact-upload må fortsat være best-effort. + +**Step 5: Commit** + +```bash +git add tests/monthly-carpenter-audit.spec.js \ + .github/workflows/ci.yml \ + docs/qa/CARPENTER_WALKTHROUGH_RELEASE_GATE.md +git commit -m "ci: gate releases on carpenter walkthrough" +``` + +--- + +### Task 8: Samlet kvalitetssikring og produktion + +**Objective:** Verificér hele systemet og deploy uden at miste rollback-muligheden. + +**Step 1: Fuld lokal gate** + +```bash +npm run lint +npm run build +npm run test-minimal +cd frontend && CI=true npm test -- --watchAll=false +cd ../tests && npx playwright test monthly-carpenter-audit.spec.js --project=chromium --reporter=list +``` + +Expected: + +- backend: alle suites grønne +- frontend: alle suites grønne +- tømrer-audit: PASS +- PDF: frisk status, gyldig og ≤ 3 sider + +**Step 2: Uafhængigt code review** + +Kør security scan og independent staged-diff review. Ingen blocking security- eller logic findings må være åbne. + +**Step 3: PR og CI** + +Opret én kode-PR for Spor A/C. Hold STARK-dataimportens driftslog separat. CI skal være grøn før merge. + +**Step 4: Deploy** + +- backup database +- merge til `main` +- byg frontend +- restart PM2 uden `--update-env` +- verificér `/api/health` og produktion HTTP 200 + +**Step 5: Produktionstest** + +Genkør auditprojekt 392. Kontrollér: + +- samme material count overalt +- ingen ukoblede linjer efter review +- frisk STARK-status efter Jannicks fil +- PDF frisk efter generering +- Ordrestyring-knap kun aktiv ved `readyToSend` + +**Step 6: Rollback-kriterier** + +Rollback ved: + +- 5xx i tømrerflow +- ændret økonomisk total uden godkendt datagrundlag +- mistede projektmaterialer +- PDF uden `%PDF-` eller > 3 sider +- falsk `readyToSend` + +--- + +## Prioriteret rækkefølge + +1. **PDF freshness** — lille, deterministisk og direkte testfejl. +2. **Fælles readiness-model** — fjerner 11/12- og “klar/blokeret”-forvirringen. +3. **Projektindlæsning/performance** — parallelisering og måling. +4. **Materiale-koblingsflow** — løser den reelle Ordrestyring-blokering sikkert. +5. **Jannicks STARK-fil** — valider/importér som separat datagave. +6. **Release-gate og samlet produktionstest**. + +## Risici og beslutninger + +- **Ingen automatisk materiale-id-gætning:** Tvetydige matches kræver menneskelig bekræftelse. +- **Ingen skjult prisopdatering:** Jannicks fil importeres først efter backup, validering og afvigelseskontrol. +- **Performance måles koldt og varmt:** En enkelt hurtig cachet gennemgang er ikke bevis. +- **Readiness skal være fail-closed:** Ukoblede materialer eller forældet prisgrundlag må ikke fremstilles som afsendelsesklar. +- **PDF-signatur må være stabil:** Brug normaliserede forretningsværdier, ikke React object references. + +## Definition of Done + +- Månedlig tømrer-audit passerer 5/5. +- Ingen API-/konsolfejl. +- Hydration p95 er under aftalt SLA. +- PDF er frisk efter generering og stale først efter en reel ændring. +- ProjectFlow og Final Review viser samme unlinked-material count. +- Projekt 392 kan få alle relevante materialer koblet via bekræftet review. +- Ny STARK-status er verificeret efter Jannicks leverance. +- Ordrestyring-afsendelse er kun mulig, når `readyToSend === true`. +- Fuld lint, build, backend, frontend og Playwright gate er grøn.