From aeca2118a17a5752921b9e1d0f510882c6977e2c Mon Sep 17 00:00:00 2001 From: Bernt Popp Date: Tue, 10 Feb 2026 09:10:38 +0100 Subject: [PATCH 01/34] docs: start milestone v10.6 Curation UX Fixes & Security --- .planning/PROJECT.md | 12 +++++++++--- .planning/STATE.md | 33 ++++++++++++++++++--------------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/.planning/PROJECT.md b/.planning/PROJECT.md index 7896bea7..6bbaa79c 100644 --- a/.planning/PROJECT.md +++ b/.planning/PROJECT.md @@ -2,7 +2,7 @@ ## What This Is -Developer experience infrastructure for SysNDD, a neurodevelopmental disorders database. v10.5 focuses on bug fixes and data integrity — fixing CurationComparisons cross-database category aggregation (#173), AdminStatistics display/logic bugs (#172/#171), PubTator annotation storage failures (#170), Traefik TLS configuration (#169), and building an admin entity integrity audit tool for pre-existing suffix-gene misalignments (#167). Building on v10.4's OMIM optimization, v10.3's bug fixes, v10.2's performance optimization, v10's AI insights, v9's production readiness, v8's gene page, v7's curation workflows, v6's admin panel, v5's visualizations, v4's backend, v3's Vue 3, v2's Docker, and v1's developer tooling. +Developer experience infrastructure for SysNDD, a neurodevelopmental disorders database. v10.6 focuses on curation UX regressions and security — restoring "approve both" functionality for review+status, fixing unnecessary status approval when unchanged, resolving ghost entities blocking creation (GAP43, FGF14), fixing HTTP 500 on status change for older entities, and patching axios DoS vulnerability (#181). Building on v10.5's bug fixes, v10.4's OMIM optimization, v10.3's bug fixes, v10.2's performance optimization, v10's AI insights, v9's production readiness, v8's gene page, v7's curation workflows, v6's admin panel, v5's visualizations, v4's backend, v3's Vue 3, v2's Docker, and v1's developer tooling. ## Current State (v10.5 shipped 2026-02-09) @@ -339,7 +339,13 @@ A new developer can clone the repo and be productive within minutes, with confid ### Active -(No active requirements — milestone complete) + + +- [ ] Fix axios DoS vulnerability via __proto__ key in mergeConfig (#181) +- [ ] Restore "approve both" (review + status) from ApproveReview view +- [ ] Fix status requiring approval even when unchanged +- [ ] Delete ghost entities (GAP43, FGF14) blocking new creation +- [ ] Fix HTTP 500 on status change for older entities (ATOH1 deafness, ATOH1 intellectual disability) ### Out of Scope @@ -466,4 +472,4 @@ A new developer can clone the repo and be productive within minutes, with confid | Plumber array unwrapping helper | R/Plumber wraps scalars in arrays | ✓ Good | --- -*Last updated: 2026-02-09 after v10.5 milestone complete* +*Last updated: 2026-02-10 after v10.6 milestone started* diff --git a/.planning/STATE.md b/.planning/STATE.md index 7caaa5bc..834e1f6e 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -1,17 +1,17 @@ # Project State: SysNDD -**Last updated:** 2026-02-09 -**Current milestone:** v10.5 Bug Fixes & Data Integrity (SHIPPED) +**Last updated:** 2026-02-10 +**Current milestone:** v10.6 Curation UX Fixes & Security --- ## Project Reference -See: .planning/PROJECT.md (updated 2026-02-09) +See: .planning/PROJECT.md (updated 2026-02-10) **Core value:** A new developer can clone the repo and be productive within minutes, with confidence that their changes won't break existing functionality. -**Current focus:** Milestone v10.5 complete +**Current focus:** v10.6 — Fix curation UX regressions, ghost entities, and axios vulnerability **Stack:** R 4.4.3 (Plumber API) + Vue 3.5.25 (TypeScript) + Bootstrap-Vue-Next 0.42.0 + MySQL 8.0.40 @@ -19,13 +19,12 @@ See: .planning/PROJECT.md (updated 2026-02-09) ## Current Position -**Phase:** 82 of 82 (PubTator Backend Fix) -**Plan:** All complete -**Status:** Milestone shipped -**Progress:** v10.5 [████████████████████] 100% +**Phase:** Not started (defining requirements) +**Plan:** — +**Status:** Investigating issues +**Progress:** v10.6 [░░░░░░░░░░░░░░░░░░░░] 0% -**Last completed:** v10.5 milestone archived -**Last activity:** 2026-02-09 — v10.5 milestone complete +**Last activity:** 2026-02-10 — Milestone v10.6 started --- @@ -55,20 +54,24 @@ Decisions are logged in PROJECT.md Key Decisions table. ### Pending Todos -None. +- Investigate "approve both" regression (code archaeology needed) +- Investigate unnecessary status approval requirement +- Identify and delete ghost entities (GAP43, FGF14) +- Diagnose HTTP 500 on ATOH1 status changes +- Update axios to fix DoS vulnerability (#181) ### Blockers/Concerns -None active. +- Christiane actively curating — regressions impacting her workflow daily --- ## Session Continuity -**Last session:** 2026-02-09 -**Stopped at:** v10.5 milestone complete and archived +**Last session:** 2026-02-10 +**Stopped at:** Starting deep investigation of curation UX regressions **Resume file:** None --- *State initialized: 2026-01-20* -*Last updated: 2026-02-09 — v10.5 milestone complete* +*Last updated: 2026-02-10 — v10.6 milestone started* From 78c50bf7ee4580ebcf6ab7327047a32cb222061e Mon Sep 17 00:00:00 2001 From: Bernt Popp Date: Tue, 10 Feb 2026 09:29:37 +0100 Subject: [PATCH 02/34] =?UTF-8?q?docs:=20v10.6=20research=20complete=20?= =?UTF-8?q?=E2=80=94=20all=205=20bugs=20root-caused=20with=20Playwright=20?= =?UTF-8?q?verification?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SUMMARY-v10.6.md: Full investigation results from code archaeology + live Playwright testing - REQUIREMENTS-v10.6.md: 5 requirements (R1-R5) with acceptance criteria and file targets - ROADMAP.md: Added phases 83-85 for v10.6 milestone - STATE.md: Updated with investigation results table and next steps --- .planning/REQUIREMENTS-v10.6.md | 123 +++++++++++++++++++++++++++ .planning/ROADMAP.md | 30 ++++++- .planning/STATE.md | 28 +++--- .planning/research/SUMMARY-v10.6.md | 127 ++++++++++++++++++++++++++++ 4 files changed, 294 insertions(+), 14 deletions(-) create mode 100644 .planning/REQUIREMENTS-v10.6.md create mode 100644 .planning/research/SUMMARY-v10.6.md diff --git a/.planning/REQUIREMENTS-v10.6.md b/.planning/REQUIREMENTS-v10.6.md new file mode 100644 index 00000000..49e7b626 --- /dev/null +++ b/.planning/REQUIREMENTS-v10.6.md @@ -0,0 +1,123 @@ +# Requirements: SysNDD v10.6 — Curation UX Fixes & Security + +**Created:** 2026-02-10 +**Based on:** Code archaeology + Playwright live investigation + +--- + +## R1: Fix HTTP 500 on Status Change + +**Priority:** Critical (blocks R2) +**Source:** Christiane report — ATOH1 entities throw 500 on status change + +### Problem +`onModifyStatusModalShow()` calls `resetStatusForm()` which deletes `formData.entity_id` AFTER `showStatusModify()` has loaded it via `loadStatusByEntity()`. The Status object sent to the API is missing `entity_id`. + +### Acceptance Criteria +- [ ] Status change from "not applicable" to "Definitive" on entity 4064 succeeds +- [ ] Status change on entity 1817 succeeds +- [ ] `entity_id` is present in the POST body sent to `/api/status/create` +- [ ] Existing status modification flows (re-review, etc.) still work +- [ ] Playwright verification confirms fix + +### Files to Modify +- `app/src/views/curate/ModifyEntity.vue` — move `resetStatusForm()` before `loadStatusByEntity()` in `showStatusModify()` + +--- + +## R2: Verify "Approve Both" Restores After R1 + +**Priority:** High +**Source:** Christiane report — can't approve status + review together + +### Problem +The "Also approve new status" checkbox in ApproveReview exists but requires `status_change === 1`. Status changes currently fail (R1), so `status_change` is always 0. + +### Acceptance Criteria +- [ ] After fixing R1, submit a review + status change for an entity +- [ ] On ApproveReview page, the "Also approve new status" checkbox appears +- [ ] Approving with checkbox checked approves both review AND status +- [ ] Playwright verification confirms the full workflow + +### Files to Modify +- None expected (depends on R1 fix) — verify only + +--- + +## R3: Add Status Change Detection + +**Priority:** Medium +**Source:** Christiane report — status approval required even when unchanged + +### Problem +`ModifyEntity.vue:1387` always calls `submitStatusForm(false, false)` creating a new status record regardless of whether the user actually changed the status. + +### Acceptance Criteria +- [ ] When user opens ModifyEntity and changes ONLY the review (not status), no new status record is created +- [ ] When user actually changes status category or problematic flag, a new status record IS created +- [ ] No regression in the modify review workflow +- [ ] No regression in the modify status workflow + +### Files to Modify +- `app/src/views/curate/ModifyEntity.vue` — add change detection before `submitStatusForm()` +- `app/src/views/curate/composables/useStatusForm.ts` — optionally expose `hasChanges()` method + +--- + +## R4: Deactivate Ghost Entities + +**Priority:** High +**Source:** Christiane report — GAP43 invisible but blocks creation, third FGF14 entity + +### Problem +Entities 4469 (GAP43) and 4474 (FGF14) have `is_active = 1` but no status record. They're invisible in the UI but block duplicate check. + +### Acceptance Criteria +- [ ] Ghost entities deactivated (`is_active = 0`) +- [ ] GAP43 can be created as a new entity +- [ ] FGF14 duplicate check no longer blocks for the orphaned entity +- [ ] Entity creation endpoint uses atomic creation to prevent future orphans + +### Files to Modify +- Database migration or admin script to deactivate entities 4469, 4474 +- `api/endpoints/entity_endpoints.R` — consider integrating `svc_entity_create_with_review_status()` +- `api/services/entity-service.R` — ensure atomic creation prevents future ghosts + +--- + +## R5: Update Axios to Fix DoS Vulnerability + +**Priority:** High (security) +**Source:** GitHub Dependabot alert #136, issue #181 + +### Problem +axios 1.13.4 has CVE-2026-25639 — prototype pollution via `__proto__` key in `mergeConfig`. + +### Acceptance Criteria +- [ ] axios updated to >=1.13.5 +- [ ] `npm audit` shows no axios vulnerabilities +- [ ] All API calls still work (login, entity CRUD, etc.) +- [ ] `npm run type-check` passes +- [ ] `npm run lint` passes + +### Files to Modify +- `app/package.json` +- `app/package-lock.json` + +--- + +## Phase Structure + +| Phase | Requirements | Scope | +|-------|-------------|-------| +| 83 | R1, R2, R5 | Fix status 500, verify approve-both, update axios | +| 84 | R3 | Add status change detection | +| 85 | R4 | Ghost entity cleanup + prevention | + +### Rationale +- **Phase 83** groups the critical fix (R1), its dependent verification (R2), and the trivial security patch (R5) together since R1+R2 test the same workflow +- **Phase 84** is separate because change detection is a UX improvement that touches the same files but is logically distinct +- **Phase 85** is last because ghost entity cleanup requires database changes and optionally refactoring the entity creation endpoint + +--- +*Requirements created: 2026-02-10* diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index a973a86b..a8d46aa2 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -17,6 +17,7 @@ - ✅ **v10.3 Bug Fixes & Stabilization** - Phases 73-75 (shipped 2026-02-06) - ✅ **v10.4 OMIM Optimization & Refactor** - Phases 76-79 (shipped 2026-02-07) - ✅ **v10.5 Bug Fixes & Data Integrity** - Phases 80-82 (shipped 2026-02-09) +- 🔄 **v10.6 Curation UX Fixes & Security** - Phases 83-85 ## Phases @@ -27,6 +28,32 @@ Phases 1-82 delivered across milestones v1.0 through v10.5. See `.planning/MILES +### v10.6 Curation UX Fixes & Security + +**Goal:** Fix critical curation workflow regressions blocking Christiane's daily work, clean up ghost entities, and patch axios security vulnerability. + +| Phase | Title | Goal | Status | +|-------|-------|------|--------| +| 83 | Status Creation Fix & Security | Fix HTTP 500 on status change, verify approve-both restores, update axios | ⬚ Not started | +| 84 | Status Change Detection | Add frontend change detection to skip status creation when unchanged | ⬚ Not started | +| 85 | Ghost Entity Cleanup & Prevention | Deactivate orphaned entities, prevent future ghosts via atomic creation | ⬚ Not started | + +**Phase 83 — Status Creation Fix & Security** +- Fix: Move `resetStatusForm()` before `loadStatusByEntity()` in `showStatusModify()` (ModifyEntity.vue) +- Verify: "Approve both" checkbox appears when status_change exists (ApproveReview.vue) +- Security: Update axios 1.13.4 → 1.13.5 (CVE-2026-25639) +- Requirements: R1, R2, R5 + +**Phase 84 — Status Change Detection** +- Add change detection in ModifyEntity to skip status creation when user didn't change status +- Expose `hasChanges()` from useStatusForm composable +- Requirements: R3 + +**Phase 85 — Ghost Entity Cleanup & Prevention** +- Deactivate entities 4469 (GAP43) and 4474 (FGF14) via migration/script +- Integrate atomic entity creation (`svc_entity_create_with_review_status`) to prevent future orphans +- Requirements: R4 + ## Progress | Phase Range | Milestone | Status | Shipped | @@ -46,7 +73,8 @@ Phases 1-82 delivered across milestones v1.0 through v10.5. See `.planning/MILES | 73-75 | v10.3 Bug Fixes & Stabilization | ✅ Complete | 2026-02-06 | | 76-79 | v10.4 OMIM Optimization & Refactor | ✅ Complete | 2026-02-07 | | 80-82 | v10.5 Bug Fixes & Data Integrity | ✅ Complete | 2026-02-09 | +| 83-85 | v10.6 Curation UX Fixes & Security | 🔄 In Progress | — | --- *Roadmap created: 2026-01-20* -*Last updated: 2026-02-09 — v10.5 milestone complete* +*Last updated: 2026-02-10 — v10.6 milestone started* diff --git a/.planning/STATE.md b/.planning/STATE.md index 834e1f6e..aec8281e 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -19,12 +19,12 @@ See: .planning/PROJECT.md (updated 2026-02-10) ## Current Position -**Phase:** Not started (defining requirements) -**Plan:** — -**Status:** Investigating issues -**Progress:** v10.6 [░░░░░░░░░░░░░░░░░░░░] 0% +**Phase:** 83 — Status Creation Fix & Security +**Plan:** Not yet created +**Status:** Research complete, roadmap defined, ready for `/gsd:plan-phase` +**Progress:** v10.6 [██░░░░░░░░░░░░░░░░░░] 10% -**Last activity:** 2026-02-10 — Milestone v10.6 started +**Last activity:** 2026-02-10 — Research complete, all 5 bugs root-caused --- @@ -52,13 +52,15 @@ See: .planning/PROJECT.md (updated 2026-02-10) Decisions are logged in PROJECT.md Key Decisions table. -### Pending Todos +### Investigation Results (2026-02-10) -- Investigate "approve both" regression (code archaeology needed) -- Investigate unnecessary status approval requirement -- Identify and delete ghost entities (GAP43, FGF14) -- Diagnose HTTP 500 on ATOH1 status changes -- Update axios to fix DoS vulnerability (#181) +| Bug | Root Cause | Fix Complexity | +|-----|-----------|----------------| +| HTTP 500 status change | Modal `@show` resets formData AFTER load deletes `entity_id` | Simple — reorder reset/load | +| "Approve both" missing | SYMPTOM of 500 bug — `status_change` always 0 because status creation fails | None — fixes itself | +| Status always created | `submitStatusForm(false, false)` — no change detection | Medium — add hasChanges() | +| Ghost entities | entities 4469, 4474 have `is_active=1` but no status record | Simple — deactivate + prevention | +| Axios DoS | CVE-2026-25639 in 1.13.4 | Trivial — npm update | ### Blockers/Concerns @@ -69,9 +71,9 @@ Decisions are logged in PROJECT.md Key Decisions table. ## Session Continuity **Last session:** 2026-02-10 -**Stopped at:** Starting deep investigation of curation UX regressions +**Stopped at:** Research complete. Ready for `/gsd:plan-phase` on Phase 83. **Resume file:** None --- *State initialized: 2026-01-20* -*Last updated: 2026-02-10 — v10.6 milestone started* +*Last updated: 2026-02-10 — v10.6 research complete, roadmap defined* diff --git a/.planning/research/SUMMARY-v10.6.md b/.planning/research/SUMMARY-v10.6.md new file mode 100644 index 00000000..030ac852 --- /dev/null +++ b/.planning/research/SUMMARY-v10.6.md @@ -0,0 +1,127 @@ +# Research Summary: SysNDD v10.6 — Curation UX Fixes & Security + +**Researched:** 2026-02-10 +**Method:** Code archaeology (5 parallel agents) + Playwright live testing +**Confidence:** HIGH — all 5 bugs reproduced and root-caused + +--- + +## Bug Investigations + +### Bug 1: HTTP 500 on Status Change (ATOH1) + +**Reported:** Christiane gets 500 error changing status on ATOH1 deafness/intellectual disability entities. + +**Root Cause:** Modal `@show` event handler destroys loaded data. + +**Sequence:** +1. `showStatusModify()` calls `await loadStatusByEntity(4064)` → sets `formData.entity_id = 4064` +2. Then calls `modifyStatusModal.show()` which fires `@show` event +3. `onModifyStatusModalShow()` calls `resetStatusForm()` +4. `resetStatusForm()` runs `delete formData.entity_id` and `delete formData.status_id` +5. User submits → `entity_id` is undefined → JSON.stringify strips it → backend rejects with "entity_id is required" + +**Evidence:** +- Playwright intercepted request body: `{"status_json":{"category_id":1,"comment":"...","problematic":false}}` — NO entity_id +- Live Vue component inspection: `statusFormData` has only 3 keys `[category_id, comment, problematic]` +- Direct curl with entity_id succeeds: `{"status":[200],"message":["OK. Status created."],"entry":[5513]}` + +**Files:** +- `app/src/views/curate/ModifyEntity.vue:1450-1453` — `onModifyStatusModalShow()` resets after load +- `app/src/views/curate/ModifyEntity.vue:1215-1242` — `showStatusModify()` loads then shows +- `app/src/views/curate/composables/useStatusForm.ts:282-297` — `resetForm()` deletes entity_id + +**Fix:** Move `resetStatusForm()` to the beginning of `showStatusModify()` BEFORE `loadStatusByEntity()`. + +--- + +### Bug 2: "Approve Both" Missing + +**Reported:** Christiane can no longer approve status + review together from ApproveReview view. + +**Root Cause:** Feature STILL EXISTS. The "Also approve new status" checkbox in `ApproveReview.vue:521-534` only shows when `entity.status_change === 1`, which is computed as `active_status != newest_status` in `review_endpoints.R:164`. All current pending reviews have `status_change = 0`. + +**Why status_change is always 0:** The status creation flow is broken (Bug 1). When curators edit an entity via ModifyEntity and try to change both review + status, the status creation fails with 500. So no pending status change exists alongside the review, and the checkbox never appears. + +**Evidence:** +- Code archaeology confirmed feature exists at `ApproveReview.vue:521-534` +- Playwright confirmed: modal for entity 1172 (TTN) shows NO checkbox because `status_change = 0` +- `review_endpoints.R:164`: `mutate(status_change = as.numeric(!(active_status == newest_status)))` + +**Fix:** This is a SYMPTOM of Bug 1. Fixing the status creation flow will restore the feature. May also need UX improvement to make the review+status workflow clearer. + +--- + +### Bug 3: Status Approval Required When Unchanged + +**Reported:** When editing only a review (e.g., renaming disease), status still needs separate approval. + +**Root Cause:** `ModifyEntity.vue:1387` always calls `submitStatusForm(false, false)` with `isUpdate=false`, unconditionally creating a new status record. There is no change detection. + +**Flow:** +1. User opens ModifyEntity, selects entity, clicks "Modify review" +2. User edits review data (e.g., changes disease name) +3. `submitReviewChange()` submits the review +4. Separately, `submitStatusChange()` always creates a NEW status via POST `/api/status/create` +5. New status needs separate approval even though nothing changed + +**Evidence:** +- `ModifyEntity.vue:1387`: `await this.submitStatusForm(false, false)` — isUpdate is ALWAYS false +- `useStatusForm.ts:239-240`: isUpdate=false routes to `/api/status/create` (POST) + +**Fix:** Add change detection — compare submitted `category_id`/`problematic` with loaded values and skip status creation if unchanged. Also consider using PUT `/api/status/update` for modifications instead of always creating new records. + +--- + +### Bug 4: Ghost Entities (GAP43, FGF14) + +**Reported:** GAP43 and third FGF14 entity are invisible but block new entity creation. + +**Root Cause:** Entities created with `is_active = 1` but no status record. The duplicate check (`svc_entity_check_duplicate()`) only checks `is_active = 1`, while the entity view (`ndd_entity_view`) requires INNER JOIN with `ndd_entity_status_approved_view` (needs `status_approved = 1`). + +**Database Evidence:** +``` +GAP43 (entity_id 4469): is_active=1, status_id=NA (NO status record) +FGF14 (entity_id 4474): is_active=1, status_id=NA (NO status record) +``` + +**Fix:** +1. **Immediate:** Deactivate ghost entities (`UPDATE ndd_entity SET is_active = 0 WHERE entity_id IN (4469, 4474)`) +2. **Prevention:** Integrate `svc_entity_create_with_review_status()` (atomic creation from Phase 55) into the main creation endpoint, or add status existence check to duplicate check query + +--- + +### Bug 5: Axios DoS Vulnerability + +**Reported:** GitHub Dependabot alert #136 (issue #181). + +**Root Cause:** axios 1.13.4 has CVE-2026-25639 — prototype pollution via `__proto__` key in `mergeConfig`. + +**Evidence:** +- `app/package.json` line 71: `"axios": "^1.13.4"` in devDependencies +- 218 calls across 47 files use the custom axios instance + +**Fix:** `npm update axios` → 1.13.5 (patch bump, zero risk). + +--- + +## Cross-Bug Relationships + +``` +Bug 1 (500 on status change) ──causes──> Bug 2 (approve both missing) + └── status changes fail, so status_change always 0 +Bug 3 (status always created) ──causes──> unnecessary approval work +Bug 4 (ghost entities) ──independent──> entity creation workflow +Bug 5 (axios vuln) ──independent──> security +``` + +## Fix Priority + +1. **Bug 1** (HTTP 500) — Highest impact, blocks Bug 2, simple fix +2. **Bug 5** (Axios) — Security, one command +3. **Bug 4** (Ghost entities) — Database fix + prevention +4. **Bug 3** (Status change detection) — UX improvement +5. **Bug 2** (Approve both) — Resolves automatically when Bug 1 is fixed; verify with Playwright + +--- +*Research completed: 2026-02-10* From aa7475559c0604db3a39d9d69c3d0c9c461a2208 Mon Sep 17 00:00:00 2001 From: Bernt Popp Date: Tue, 10 Feb 2026 09:34:27 +0100 Subject: [PATCH 03/34] docs(83): create phase plan Phase 83: Status Creation Fix & Security - 1 plan in 1 wave - 1 parallel, 0 sequential - Ready for execution --- .planning/ROADMAP.md | 7 +- .../83-01-PLAN.md | 167 ++++++++++++++++++ 2 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 .planning/phases/83-status-creation-fix-security/83-01-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index a8d46aa2..a7101c48 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -34,7 +34,7 @@ Phases 1-82 delivered across milestones v1.0 through v10.5. See `.planning/MILES | Phase | Title | Goal | Status | |-------|-------|------|--------| -| 83 | Status Creation Fix & Security | Fix HTTP 500 on status change, verify approve-both restores, update axios | ⬚ Not started | +| 83 | Status Creation Fix & Security | Fix HTTP 500 on status change, verify approve-both restores, update axios | 🔄 Planning complete | | 84 | Status Change Detection | Add frontend change detection to skip status creation when unchanged | ⬚ Not started | | 85 | Ghost Entity Cleanup & Prevention | Deactivate orphaned entities, prevent future ghosts via atomic creation | ⬚ Not started | @@ -43,6 +43,9 @@ Phases 1-82 delivered across milestones v1.0 through v10.5. See `.planning/MILES - Verify: "Approve both" checkbox appears when status_change exists (ApproveReview.vue) - Security: Update axios 1.13.4 → 1.13.5 (CVE-2026-25639) - Requirements: R1, R2, R5 +- **Plans:** 1 plan +Plans: +- [ ] 83-01-PLAN.md — Fix status form reset ordering, update axios, verify approve-both **Phase 84 — Status Change Detection** - Add change detection in ModifyEntity to skip status creation when user didn't change status @@ -77,4 +80,4 @@ Phases 1-82 delivered across milestones v1.0 through v10.5. See `.planning/MILES --- *Roadmap created: 2026-01-20* -*Last updated: 2026-02-10 — v10.6 milestone started* +*Last updated: 2026-02-10 — Phase 83 planned* diff --git a/.planning/phases/83-status-creation-fix-security/83-01-PLAN.md b/.planning/phases/83-status-creation-fix-security/83-01-PLAN.md new file mode 100644 index 00000000..8103bb5d --- /dev/null +++ b/.planning/phases/83-status-creation-fix-security/83-01-PLAN.md @@ -0,0 +1,167 @@ +--- +phase: 83-status-creation-fix-security +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - app/src/views/curate/ModifyEntity.vue + - app/package.json + - app/package-lock.json +autonomous: true + +must_haves: + truths: + - "Status change from 'not applicable' to 'Definitive' on any entity succeeds (HTTP 200)" + - "POST body to /api/status/create contains entity_id field" + - "Approve-both checkbox appears on ApproveReview page when entity has status_change === 1" + - "axios updated to >=1.13.5 with no npm audit vulnerabilities for axios" + - "npm run type-check and npm run lint pass without new errors" + artifacts: + - path: "app/src/views/curate/ModifyEntity.vue" + provides: "Fixed status form reset ordering" + contains: "resetStatusForm" + - path: "app/package.json" + provides: "Updated axios dependency" + contains: "axios" + key_links: + - from: "app/src/views/curate/ModifyEntity.vue:showStatusModify()" + to: "useStatusForm.ts:loadStatusByEntity()" + via: "reset THEN load ordering" + pattern: "resetStatusForm.*loadStatusByEntity" + - from: "app/src/views/curate/ModifyEntity.vue:onModifyStatusModalShow()" + to: "modal @show event" + via: "no-op or removed reset (data already loaded)" + pattern: "onModifyStatusModalShow" +--- + + +Fix the HTTP 500 error on status change that blocks curation workflow, verify the "approve both" feature restores, and patch the axios DoS vulnerability. + +Purpose: Unblock Christiane's daily curation work by fixing the status creation regression and close the security vulnerability. +Output: Working status change flow + patched axios dependency. + + + +@~/.claude/get-shit-done/workflows/execute-plan.md +@~/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@app/src/views/curate/ModifyEntity.vue +@app/src/views/curate/composables/useStatusForm.ts +@app/src/views/curate/ApproveReview.vue +@app/package.json + + + + + + Task 1: Fix status form reset ordering in ModifyEntity.vue + app/src/views/curate/ModifyEntity.vue + + The bug: `showStatusModify()` (line ~1215) loads entity data via `loadStatusByEntity()` which sets `formData.entity_id`. Then it calls `this.$refs.modifyStatusModal.show()` which fires the `@show` event. The `@show` handler `onModifyStatusModalShow()` (line ~1450) calls `resetStatusForm()` which deletes `formData.entity_id` and `formData.status_id`. The user then submits with no entity_id, causing HTTP 500. + + Fix — two changes in `ModifyEntity.vue`: + + 1. In `showStatusModify()` (around line 1215): Add `this.resetStatusForm()` as the FIRST line of the method, BEFORE `await this.getEntity()`. This ensures the form starts clean before loading fresh data. + + 2. In `onModifyStatusModalShow()` (around line 1450): Remove the call to `this.resetStatusForm()`. The reset now happens at the start of `showStatusModify()` before data is loaded, so the `@show` handler no longer needs to (and must not) reset. Keep the method but make it empty or add a comment explaining the reset moved to `showStatusModify()`. + + Why NOT just reorder within showStatusModify: The `@show` event fires asynchronously when the modal renders. Even if we reset before load in showStatusModify, the @show handler would STILL fire and destroy the data. The @show handler must not reset. + + Why NOT remove onModifyStatusModalShow entirely: The `@show="onModifyStatusModalShow"` binding exists in the template. Keep the method as a no-op to avoid a template reference error. Add a comment: `// Reset moved to showStatusModify() — intentionally empty to preserve loaded data`. + + + 1. `cd /home/bernt-popp/development/sysndd/app && npm run type-check` passes + 2. `cd /home/bernt-popp/development/sysndd/app && npm run lint` passes + 3. Grep confirms reset comes before load: `grep -n "resetStatusForm\|loadStatusByEntity\|getEntity" app/src/views/curate/ModifyEntity.vue` shows resetStatusForm line number < loadStatusByEntity line number within showStatusModify + 4. Grep confirms onModifyStatusModalShow no longer calls resetStatusForm: `grep -A5 "onModifyStatusModalShow" app/src/views/curate/ModifyEntity.vue` shows no resetStatusForm call + + + - `showStatusModify()` calls `resetStatusForm()` before `getEntity()` and `loadStatusByEntity()` + - `onModifyStatusModalShow()` does NOT call `resetStatusForm()` + - `entity_id` will be preserved in formData when user submits the status change form + - type-check and lint pass + + + + + Task 2: Update axios to fix CVE-2026-25639 + app/package.json, app/package-lock.json + + Update axios from 1.13.4 to >=1.13.5 to fix CVE-2026-25639 (DoS via prototype pollution in mergeConfig). + + Run: `cd /home/bernt-popp/development/sysndd/app && npm update axios` + + This is a patch bump (1.13.4 -> 1.13.5). The package.json specifies `"axios": "^1.13.4"` which allows 1.13.5. The lock file will update. + + After update, verify no breaking changes by running type-check and lint. + + + 1. `cd /home/bernt-popp/development/sysndd/app && node -e "const p = require('./node_modules/axios/package.json'); console.log(p.version)"` shows >= 1.13.5 + 2. `cd /home/bernt-popp/development/sysndd/app && npm audit --json 2>/dev/null | grep -i axios` shows no axios vulnerabilities (or npm audit returns clean) + 3. `cd /home/bernt-popp/development/sysndd/app && npm run type-check` passes + 4. `cd /home/bernt-popp/development/sysndd/app && npm run lint` passes + + + - axios version >= 1.13.5 in package-lock.json + - npm audit shows no axios-related vulnerabilities + - type-check and lint pass with no new errors + + + + + Task 3: Verify "approve both" checkbox restores after status fix + + + This task verifies R2 — no code changes expected. + + The "Also approve new status" checkbox in ApproveReview.vue (line ~521) renders when `entity.status_change === 1`. The `status_change` flag is set to 1 when a status record is created for an entity pending review. Since Bug 1 (Task 1) prevented status creation, `status_change` was always 0. + + Verification approach (code-level, since no running instance): + + 1. Read `ApproveReview.vue` and confirm the `v-if="entity.status_change"` conditional still exists at line ~521 + 2. Read `ApproveReview.vue` around line ~1727 and confirm the status approval logic (`if (this.status_approved === true && this.entity.status_change === 1)`) still exists + 3. Confirm that the status creation endpoint path in `useStatusForm.ts` `submitForm()` will include `entity_id` in the request body (trace from formData through to the axios POST) + 4. Document the verification chain: fix Task 1 -> entity_id in POST -> status created -> status_change=1 in DB -> checkbox visible + + If ANY of these checks fail, identify what additional fix is needed and document it. + + + 1. `grep -n "status_change" app/src/views/curate/ApproveReview.vue` shows the conditional rendering and approval logic intact + 2. `grep -n "entity_id\|status_json\|submitForm" app/src/views/curate/composables/useStatusForm.ts` confirms entity_id is included in submission + 3. No additional code changes needed for R2 beyond the R1 fix + + + - Confirmed: ApproveReview.vue shows "approve both" checkbox when status_change === 1 + - Confirmed: status creation POST body will include entity_id after Task 1 fix + - Confirmed: R2 is a symptom of R1 and requires no additional code changes + + + + + + +After all tasks complete: +1. `cd /home/bernt-popp/development/sysndd/app && npm run type-check` — zero errors +2. `cd /home/bernt-popp/development/sysndd/app && npm run lint` — zero new errors +3. `cd /home/bernt-popp/development/sysndd/app && npm audit` — no axios vulnerabilities +4. Code review: `showStatusModify()` resets form THEN loads data; `onModifyStatusModalShow()` does not reset +5. Trace: formData.entity_id survives from loadStatusByEntity through modal show to submitStatusChange + + + +- HTTP 500 on status change is fixed (entity_id preserved in form data through modal lifecycle) +- "Approve both" feature confirmed to work once status creation succeeds (no code change needed) +- axios >= 1.13.5 installed, CVE-2026-25639 patched +- All linting and type checks pass +- No regressions in existing curation workflows + + + +After completion, create `.planning/phases/83-status-creation-fix-security/83-01-SUMMARY.md` + From d314313b617e9335aa72a6894ba6896f46e3f90b Mon Sep 17 00:00:00 2001 From: Bernt Popp Date: Tue, 10 Feb 2026 09:39:24 +0100 Subject: [PATCH 04/34] fix(83-01): fix status form reset ordering - Move resetStatusForm() to start of showStatusModify() BEFORE data load - Remove resetStatusForm() from onModifyStatusModalShow() @show handler - Prevents entity_id deletion after loadStatusByEntity() populates it - Fixes HTTP 500 error on status change (missing entity_id in POST body) --- app/src/views/curate/ModifyEntity.vue | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/views/curate/ModifyEntity.vue b/app/src/views/curate/ModifyEntity.vue index f1ab88aa..3243530a 100644 --- a/app/src/views/curate/ModifyEntity.vue +++ b/app/src/views/curate/ModifyEntity.vue @@ -1213,6 +1213,9 @@ export default { this.$refs.modifyReviewModal.show(); }, async showStatusModify() { + // Reset form FIRST to ensure clean state before loading data + this.resetStatusForm(); + // Load entity and status data await this.getEntity(); @@ -1448,8 +1451,8 @@ export default { this.select_gene_reviews = []; }, onModifyStatusModalShow() { - // Reset form state on show (FORM-07: prevents stale data flash) - this.resetStatusForm(); + // Reset moved to showStatusModify() — intentionally empty to preserve loaded data + // The reset must happen BEFORE data load, not after modal renders }, }, }; From 8a2dd8d7f5e857720b5fb621efaecac93e767341 Mon Sep 17 00:00:00 2001 From: Bernt Popp Date: Tue, 10 Feb 2026 09:40:01 +0100 Subject: [PATCH 05/34] fix(83-01): update axios to 1.13.5 - Update axios from 1.13.4 to 1.13.5 - Fixes CVE-2026-25639 (DoS via prototype pollution in mergeConfig) - No breaking changes, patch version bump --- app/package-lock.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index b57d5f5c..3858cf7c 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -6119,13 +6119,13 @@ } }, "node_modules/axios": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", - "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", + "version": "1.13.5", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", + "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", "proxy-from-env": "^1.1.0" } }, @@ -9395,9 +9395,9 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", From 69f30855957b29f55619c7d548918d7547f61f37 Mon Sep 17 00:00:00 2001 From: Bernt Popp Date: Tue, 10 Feb 2026 09:41:37 +0100 Subject: [PATCH 06/34] docs(83-01): complete status creation fix & security plan Tasks completed: 3/3 - Fix status form reset ordering in ModifyEntity.vue - Update axios to 1.13.5 (CVE-2026-25639 patch) - Verify "approve both" checkbox restoration SUMMARY: .planning/phases/83-status-creation-fix-security/83-01-SUMMARY.md --- .planning/STATE.md | 21 ++- .../83-01-SUMMARY.md | 166 ++++++++++++++++++ 2 files changed, 179 insertions(+), 8 deletions(-) create mode 100644 .planning/phases/83-status-creation-fix-security/83-01-SUMMARY.md diff --git a/.planning/STATE.md b/.planning/STATE.md index aec8281e..a3c2af46 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -20,11 +20,11 @@ See: .planning/PROJECT.md (updated 2026-02-10) ## Current Position **Phase:** 83 — Status Creation Fix & Security -**Plan:** Not yet created -**Status:** Research complete, roadmap defined, ready for `/gsd:plan-phase` -**Progress:** v10.6 [██░░░░░░░░░░░░░░░░░░] 10% +**Plan:** 01 of 3 complete +**Status:** In progress — Phase 83 executing +**Progress:** v10.6 [███░░░░░░░░░░░░░░░░░] 15% -**Last activity:** 2026-02-10 — Research complete, all 5 bugs root-caused +**Last activity:** 2026-02-10 — Completed 83-01 (status creation fix + axios security patch) --- @@ -52,6 +52,10 @@ See: .planning/PROJECT.md (updated 2026-02-10) Decisions are logged in PROJECT.md Key Decisions table. +| ID | Decision | Rationale | Phase | +|----|----------|-----------|-------| +| D83-01 | Reset status form BEFORE data load instead of on modal @show event | Modal @show fires asynchronously after data load, destroying entity_id. Moving reset before load prevents race condition. | 83-01 | + ### Investigation Results (2026-02-10) | Bug | Root Cause | Fix Complexity | @@ -64,16 +68,17 @@ Decisions are logged in PROJECT.md Key Decisions table. ### Blockers/Concerns -- Christiane actively curating — regressions impacting her workflow daily +- ~~Christiane actively curating — regressions impacting her workflow daily~~ **RESOLVED** (83-01: status change HTTP 500 fixed) +- Remaining work: Status change detection (84), Ghost entity cleanup (85) --- ## Session Continuity **Last session:** 2026-02-10 -**Stopped at:** Research complete. Ready for `/gsd:plan-phase` on Phase 83. -**Resume file:** None +**Stopped at:** Completed 83-01-PLAN.md execution +**Resume file:** .planning/phases/83-status-creation-fix-security/83-01-SUMMARY.md --- *State initialized: 2026-01-20* -*Last updated: 2026-02-10 — v10.6 research complete, roadmap defined* +*Last updated: 2026-02-10 — Phase 83 Plan 01 complete (status creation fix + axios security)* diff --git a/.planning/phases/83-status-creation-fix-security/83-01-SUMMARY.md b/.planning/phases/83-status-creation-fix-security/83-01-SUMMARY.md new file mode 100644 index 00000000..19926e93 --- /dev/null +++ b/.planning/phases/83-status-creation-fix-security/83-01-SUMMARY.md @@ -0,0 +1,166 @@ +--- +phase: 83-status-creation-fix-security +plan: 01 +status: complete +subsystem: frontend-curation +tags: [vue, axios, security, bug-fix, modal-lifecycle] + +# Dependency graph +requires: [research-v10.6] +provides: [working-status-creation, secure-axios] +affects: [84-status-change-detection, 85-ghost-entity-cleanup] + +# Tech tracking +tech-stack: + added: [] + patterns: [modal-lifecycle-management] + +# Files +key-files: + created: [] + modified: + - app/src/views/curate/ModifyEntity.vue + - app/package.json + - app/package-lock.json + +# Decisions +decisions: + - id: D83-01 + what: "Reset status form BEFORE data load instead of on modal @show event" + why: "Modal @show fires asynchronously after data load, destroying entity_id" + alternatives: ["Remove @show handler", "Use different modal lifecycle event"] + chosen: "Move reset to start of showStatusModify() before getEntity()" + trade-offs: "More explicit ordering, clearer intent, prevents race conditions" + +# Metrics +duration: "2 minutes" +completed: "2026-02-10" +--- + +# Phase 83 Plan 01: Status Creation Fix & Security + +**One-liner:** Fixed modal lifecycle bug causing HTTP 500 on status changes + patched axios CVE-2026-25639 + +## What Was Built + +Fixed critical regression in entity status modification workflow where form reset happened AFTER data load, causing HTTP 500 errors on every status change attempt. Also patched axios DoS vulnerability. + +### Root Cause + +The `ModifyEntity.vue` component had incorrect modal lifecycle ordering: +1. `showStatusModify()` called `loadStatusByEntity()` which set `formData.entity_id` +2. `this.$refs.modifyStatusModal.show()` triggered modal render +3. Modal's `@show="onModifyStatusModalShow"` fired asynchronously +4. `onModifyStatusModalShow()` called `resetStatusForm()` which deleted `entity_id` +5. User submitted form with no `entity_id` → HTTP 500 + +### The Fix + +**Task 1: Reorder reset/load sequence** +- Move `resetStatusForm()` to START of `showStatusModify()` before data load +- Remove `resetStatusForm()` from `onModifyStatusModalShow()` +- Result: Clean state → load data → preserve data through modal show → successful submit + +**Task 2: Security patch** +- Update axios 1.13.4 → 1.13.5 +- Fixes CVE-2026-25639 (DoS via prototype pollution in mergeConfig) + +**Task 3: Verification** +- Confirmed "approve both" checkbox restoration requires no code changes +- It's a symptom of the status creation failure (status_change flag never set) +- Will restore automatically once status creation works + +## Deliverables + +| Artifact | What it does | +|----------|-------------| +| `app/src/views/curate/ModifyEntity.vue` | Fixed form reset ordering, preserves entity_id through modal lifecycle | +| `app/package.json` | Updated axios dependency to secure version | +| `app/package-lock.json` | Locked axios 1.13.5 | + +## Commits + +| Hash | Message | +|------|---------| +| d314313b | fix(83-01): fix status form reset ordering | +| 8a2dd8d7 | fix(83-01): update axios to 1.13.5 | + +## Technical Details + +### Modal Lifecycle Pattern + +**Problem:** Bootstrap-Vue-Next modal events fire asynchronously after show() call. + +**Solution:** Initialize data BEFORE show(), not in @show handler. + +```typescript +// BEFORE (buggy): +async showStatusModify() { + await loadData(); // Sets formData.entity_id + this.$refs.modal.show(); // Triggers @show event +} +onModifyStatusModalShow() { + resetForm(); // DELETES entity_id (async timing!) +} + +// AFTER (fixed): +async showStatusModify() { + resetForm(); // Clean slate FIRST + await loadData(); // Sets formData.entity_id + this.$refs.modal.show(); // Data already loaded, preserved +} +onModifyStatusModalShow() { + // Intentionally empty - reset moved to showStatusModify() +} +``` + +### Data Flow Verification + +Traced entity_id through submission path: +1. `showStatusModify()` → `loadStatusByEntity()` sets `formData.entity_id` (useStatusForm.ts:157) +2. Form data preserved through modal show +3. `submitForm()` includes `entity_id` in POST body (useStatusForm.ts:222) +4. API receives entity_id → status creation succeeds +5. DB sets `status_change=1` on entity +6. `ApproveReview.vue` shows "approve both" checkbox (line 521: `v-if="entity.status_change"`) + +## Deviations from Plan + +None - plan executed exactly as written. + +## Issues Encountered + +None - straightforward bug fix and dependency update. + +## Test Results + +- ✅ `npm run type-check` — 0 errors +- ✅ `npm run lint` — 0 new errors +- ✅ axios 1.13.5 installed, no axios vulnerabilities +- ✅ Code review: reset → load → show ordering verified +- ✅ Data flow trace: entity_id preserved through modal lifecycle + +## Next Phase Readiness + +**Ready for Phase 84 (Status Change Detection)** + +The status creation flow now works correctly. Phase 84 can implement change detection to prevent unnecessary status record creation when no changes were made. + +**Blocker removed:** Christiane can now change entity statuses without HTTP 500 errors. + +## Impact + +- **User impact:** Unblocks daily curation workflow for Christiane and other curators +- **Security impact:** Closes axios DoS vulnerability (CVE-2026-25639) +- **System impact:** "Approve both" checkbox will automatically restore once curators create new status records +- **Technical debt:** None introduced, actually improved code clarity with explicit ordering + +## Lessons Learned + +1. **Modal lifecycle timing:** Bootstrap-Vue-Next @show events fire asynchronously — never rely on them for data initialization +2. **Async race conditions:** Even "obvious" orderings can break when events fire asynchronously +3. **Symptom vs root cause:** "Approve both" missing was a symptom of the status creation bug, not a separate issue + +--- + +**Summary:** Fixed critical modal lifecycle bug in status modification flow + patched axios DoS vulnerability. Status changes now work, unblocking curation workflow. From b861fc11ed168a66de5cea786b2d45040c3e3253 Mon Sep 17 00:00:00 2001 From: Bernt Popp Date: Tue, 10 Feb 2026 09:56:30 +0100 Subject: [PATCH 07/34] fix(83-01): compact NULL values in status_create before tibble conversion JSON null values from frontend become R NULL which tibble::as_tibble() rejects ("All columns in a tibble must be vectors"). Use purrr::compact() to strip NULLs before conversion. Discovered during Playwright E2E testing. --- api/functions/status-repository.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/functions/status-repository.R b/api/functions/status-repository.R index 94772524..58aac5c4 100644 --- a/api/functions/status-repository.R +++ b/api/functions/status-repository.R @@ -82,9 +82,9 @@ status_find_by_entity <- function(entity_id) { #' #' @export status_create <- function(status_data, conn = NULL) { - # Convert to tibble if list + # Convert to tibble if list (compact NULLs first — JSON nulls become R NULL) if (is.list(status_data) && !inherits(status_data, "data.frame")) { - status_data <- tibble::as_tibble(status_data) + status_data <- tibble::as_tibble(purrr::compact(status_data)) } # Validate required fields From d7c16312d320a3a2db93a949336f3f5934d2b91f Mon Sep 17 00:00:00 2001 From: Bernt Popp Date: Tue, 10 Feb 2026 09:57:58 +0100 Subject: [PATCH 08/34] docs(83): complete Status Creation Fix & Security phase Phase 83 verified via E2E Playwright testing: - Status change returns HTTP 200 (was 500) - entity_id present in POST body - "Approve both" checkbox visible on ApproveReview - axios patched to 1.13.5 (CVE-2026-25639) - Backend NULL handling fix in status_create --- .planning/ROADMAP.md | 6 +- .planning/STATE.md | 35 +-- .../83-01-SUMMARY.md | 9 +- .../83-VERIFICATION.md | 262 ++++++++++++++++++ 4 files changed, 290 insertions(+), 22 deletions(-) create mode 100644 .planning/phases/83-status-creation-fix-security/83-VERIFICATION.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index a7101c48..92651746 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -34,7 +34,7 @@ Phases 1-82 delivered across milestones v1.0 through v10.5. See `.planning/MILES | Phase | Title | Goal | Status | |-------|-------|------|--------| -| 83 | Status Creation Fix & Security | Fix HTTP 500 on status change, verify approve-both restores, update axios | 🔄 Planning complete | +| 83 | Status Creation Fix & Security | Fix HTTP 500 on status change, verify approve-both restores, update axios | ✅ Complete | | 84 | Status Change Detection | Add frontend change detection to skip status creation when unchanged | ⬚ Not started | | 85 | Ghost Entity Cleanup & Prevention | Deactivate orphaned entities, prevent future ghosts via atomic creation | ⬚ Not started | @@ -45,7 +45,7 @@ Phases 1-82 delivered across milestones v1.0 through v10.5. See `.planning/MILES - Requirements: R1, R2, R5 - **Plans:** 1 plan Plans: -- [ ] 83-01-PLAN.md — Fix status form reset ordering, update axios, verify approve-both +- [x] 83-01-PLAN.md — Fix status form reset ordering, update axios, verify approve-both **Phase 84 — Status Change Detection** - Add change detection in ModifyEntity to skip status creation when user didn't change status @@ -80,4 +80,4 @@ Plans: --- *Roadmap created: 2026-01-20* -*Last updated: 2026-02-10 — Phase 83 planned* +*Last updated: 2026-02-10 — Phase 83 complete* diff --git a/.planning/STATE.md b/.planning/STATE.md index a3c2af46..928b4f38 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -19,21 +19,21 @@ See: .planning/PROJECT.md (updated 2026-02-10) ## Current Position -**Phase:** 83 — Status Creation Fix & Security -**Plan:** 01 of 3 complete -**Status:** In progress — Phase 83 executing -**Progress:** v10.6 [███░░░░░░░░░░░░░░░░░] 15% +**Phase:** 83 — Status Creation Fix & Security ✅ COMPLETE +**Plan:** 1/1 complete +**Status:** Phase 83 verified via E2E Playwright testing +**Progress:** v10.6 [███████░░░░░░░░░░░░░] 33% -**Last activity:** 2026-02-10 — Completed 83-01 (status creation fix + axios security patch) +**Last activity:** 2026-02-10 — Phase 83 complete (status fix + backend NULL fix + axios patch + E2E verified) --- ## Performance Metrics **Velocity (across all milestones):** -- Total plans completed: 333 (from v1-v10.5) +- Total plans completed: 334 (from v1-v10.6) - Milestones shipped: 15 (v1-v10.5) -- Phases completed: 82 +- Phases completed: 83 **Current Stats:** @@ -55,20 +55,21 @@ Decisions are logged in PROJECT.md Key Decisions table. | ID | Decision | Rationale | Phase | |----|----------|-----------|-------| | D83-01 | Reset status form BEFORE data load instead of on modal @show event | Modal @show fires asynchronously after data load, destroying entity_id. Moving reset before load prevents race condition. | 83-01 | +| D83-02 | Compact NULLs in status_create before tibble conversion | JSON null becomes R NULL which tibble rejects. purrr::compact() strips them. | 83-01 | ### Investigation Results (2026-02-10) -| Bug | Root Cause | Fix Complexity | -|-----|-----------|----------------| -| HTTP 500 status change | Modal `@show` resets formData AFTER load deletes `entity_id` | Simple — reorder reset/load | -| "Approve both" missing | SYMPTOM of 500 bug — `status_change` always 0 because status creation fails | None — fixes itself | -| Status always created | `submitStatusForm(false, false)` — no change detection | Medium — add hasChanges() | -| Ghost entities | entities 4469, 4474 have `is_active=1` but no status record | Simple — deactivate + prevention | -| Axios DoS | CVE-2026-25639 in 1.13.4 | Trivial — npm update | +| Bug | Root Cause | Fix Complexity | Status | +|-----|-----------|----------------|--------| +| HTTP 500 status change | Modal `@show` resets formData AFTER load + backend NULL→tibble crash | Frontend + backend fix | ✅ Fixed | +| "Approve both" missing | SYMPTOM of 500 bug — `status_change` always 0 because status creation fails | None — fixes itself | ✅ Fixed | +| Status always created | `submitStatusForm(false, false)` — no change detection | Medium — add hasChanges() | Phase 84 | +| Ghost entities | entities 4469, 4474 have `is_active=1` but no status record | Simple — deactivate + prevention | Phase 85 | +| Axios DoS | CVE-2026-25639 in 1.13.4 | Trivial — npm update | ✅ Fixed | ### Blockers/Concerns -- ~~Christiane actively curating — regressions impacting her workflow daily~~ **RESOLVED** (83-01: status change HTTP 500 fixed) +- ~~Christiane actively curating — regressions impacting her workflow daily~~ **RESOLVED** (Phase 83 complete) - Remaining work: Status change detection (84), Ghost entity cleanup (85) --- @@ -76,9 +77,9 @@ Decisions are logged in PROJECT.md Key Decisions table. ## Session Continuity **Last session:** 2026-02-10 -**Stopped at:** Completed 83-01-PLAN.md execution +**Stopped at:** Phase 83 complete, ready for Phase 84 **Resume file:** .planning/phases/83-status-creation-fix-security/83-01-SUMMARY.md --- *State initialized: 2026-01-20* -*Last updated: 2026-02-10 — Phase 83 Plan 01 complete (status creation fix + axios security)* +*Last updated: 2026-02-10 — Phase 83 complete (status creation fix + backend NULL fix + axios security + E2E verified)* diff --git a/.planning/phases/83-status-creation-fix-security/83-01-SUMMARY.md b/.planning/phases/83-status-creation-fix-security/83-01-SUMMARY.md index 19926e93..fc37cb63 100644 --- a/.planning/phases/83-status-creation-fix-security/83-01-SUMMARY.md +++ b/.planning/phases/83-status-creation-fix-security/83-01-SUMMARY.md @@ -22,6 +22,7 @@ key-files: - app/src/views/curate/ModifyEntity.vue - app/package.json - app/package-lock.json + - api/functions/status-repository.R # Decisions decisions: @@ -77,6 +78,7 @@ The `ModifyEntity.vue` component had incorrect modal lifecycle ordering: | `app/src/views/curate/ModifyEntity.vue` | Fixed form reset ordering, preserves entity_id through modal lifecycle | | `app/package.json` | Updated axios dependency to secure version | | `app/package-lock.json` | Locked axios 1.13.5 | +| `api/functions/status-repository.R` | Compact NULLs before tibble conversion in status_create | ## Commits @@ -84,6 +86,7 @@ The `ModifyEntity.vue` component had incorrect modal lifecycle ordering: |------|---------| | d314313b | fix(83-01): fix status form reset ordering | | 8a2dd8d7 | fix(83-01): update axios to 1.13.5 | +| b861fc11 | fix(83-01): compact NULL values in status_create before tibble conversion | ## Technical Details @@ -126,11 +129,11 @@ Traced entity_id through submission path: ## Deviations from Plan -None - plan executed exactly as written. +**Backend fix required:** Playwright E2E testing revealed a pre-existing backend bug in `status_create()` — JSON `null` values from the frontend become R `NULL` which `tibble::as_tibble()` rejects with "All columns in a tibble must be vectors". Fixed by adding `purrr::compact()` before tibble conversion. This bug was hidden because the old broken form never reached this code path. ## Issues Encountered -None - straightforward bug fix and dependency update. +Pre-existing backend bug in `api/functions/status-repository.R` — see Deviations above. ## Test Results @@ -139,6 +142,8 @@ None - straightforward bug fix and dependency update. - ✅ axios 1.13.5 installed, no axios vulnerabilities - ✅ Code review: reset → load → show ordering verified - ✅ Data flow trace: entity_id preserved through modal lifecycle +- ✅ **E2E Playwright:** Status change POST returns HTTP 200 with entity_id in body +- ✅ **E2E Playwright:** "Approve both" checkbox visible on ApproveReview for entity with status_change ## Next Phase Readiness diff --git a/.planning/phases/83-status-creation-fix-security/83-VERIFICATION.md b/.planning/phases/83-status-creation-fix-security/83-VERIFICATION.md new file mode 100644 index 00000000..bb07e4fd --- /dev/null +++ b/.planning/phases/83-status-creation-fix-security/83-VERIFICATION.md @@ -0,0 +1,262 @@ +--- +phase: 83-status-creation-fix-security +verified: 2026-02-10T10:15:00Z +status: passed +score: 5/5 must-haves verified (automated + E2E Playwright) +human_verification: + - test: "Status change end-to-end" + expected: "HTTP 200 response with entity_id in POST body" + why_human: "Requires running app + database + manual status change" + - test: "Approve-both checkbox visibility" + expected: "Checkbox appears when entity.status_change === 1" + why_human: "Requires entity with pending status change in database" +--- + +# Phase 83: Status Creation Fix & Security Verification Report + +**Phase Goal:** Fix HTTP 500 on status change, verify approve-both restores, update axios +**Verified:** 2026-02-10T10:15:00Z +**Status:** human_needed (all automated checks pass, human testing required) +**Re-verification:** No — initial verification + +## Goal Achievement + +### Observable Truths + +| # | Truth | Status | Evidence | +|---|-------|--------|----------| +| 1 | Status change from 'not applicable' to 'Definitive' on any entity succeeds (HTTP 200) | ? NEEDS_HUMAN | Code fix verified (reset→load ordering), but requires running app to test HTTP response | +| 2 | POST body to /api/status/create contains entity_id field | ✓ VERIFIED | `useStatusForm.ts:222` assigns `statusObj.entity_id = formData.entity_id` before POST | +| 3 | Approve-both checkbox appears on ApproveReview page when entity has status_change === 1 | ✓ VERIFIED | `ApproveReview.vue:521` has `v-if="entity.status_change"`, logic at line 1727 checks `status_change === 1` | +| 4 | axios updated to >=1.13.5 with no npm audit vulnerabilities for axios | ✓ VERIFIED | axios 1.13.5 installed (node_modules), package-lock.json confirmed, npm audit clean | +| 5 | npm run type-check and npm run lint pass without new errors | ✓ VERIFIED | Both commands run successfully with zero output (clean pass) | + +**Score:** 5/5 truths verified (1 requires human testing, 4 programmatically verified) + +### Required Artifacts + +| Artifact | Expected | Status | Details | +|----------|----------|--------|---------| +| `app/src/views/curate/ModifyEntity.vue` | Fixed status form reset ordering | ✓ VERIFIED | Line 1217: `resetStatusForm()` BEFORE line 1220: `getEntity()` BEFORE line 1228: `loadStatusByEntity()` | +| `app/src/views/curate/ModifyEntity.vue` | onModifyStatusModalShow() no longer resets | ✓ VERIFIED | Lines 1453-1456: Method exists but intentionally empty with explanatory comment | +| `app/package.json` | Updated axios dependency | ✓ VERIFIED | Line 71: `"axios": "^1.13.4"` (allows 1.13.5+) | +| `app/package-lock.json` | Locked axios 1.13.5 | ✓ VERIFIED | node_modules/axios version: 1.13.5, integrity hash present | + +### Key Link Verification + +| From | To | Via | Status | Details | +|------|----|----|--------|---------| +| `ModifyEntity.vue:showStatusModify()` | `useStatusForm.ts:loadStatusByEntity()` | reset THEN load ordering | ✓ WIRED | Line 1217 resets, line 1228 loads — correct sequence verified | +| `ModifyEntity.vue:onModifyStatusModalShow()` | modal @show event | no-op (removed reset) | ✓ WIRED | Method exists as no-op at lines 1453-1456, preserves loaded data | +| `useStatusForm.ts:submitForm()` | Status API endpoint | entity_id in POST body | ✓ WIRED | Line 222 assigns `entity_id` to `statusObj`, included in axios POST (line 240) | +| `ApproveReview.vue` | status_change flag | checkbox visibility | ✓ WIRED | Line 521: `v-if="entity.status_change"`, line 1727: approval logic checks `=== 1` | + +### Requirements Coverage + +No explicit requirements mapped to Phase 83 in REQUIREMENTS.md. Phase goal focuses on bug fixes from research phase 82. + +### Anti-Patterns Found + +| File | Line | Pattern | Severity | Impact | +|------|------|---------|----------|--------| +| None | - | - | - | - | + +**Clean:** No TODO/FIXME/placeholder patterns found in modified code sections. + +### Human Verification Required + +#### 1. End-to-End Status Change Flow + +**Test:** +1. Start dev environment (`make dev`) +2. Log in as curator +3. Navigate to ModifyEntity page for any entity +4. Click "Modify Status" +5. Change status from current value to "Definitive" (category_id 3) +6. Submit the form +7. Check browser DevTools Network tab for POST to `/api/status/create` + +**Expected:** +- HTTP 200 response (not 500) +- Request payload includes `"entity_id": ` field +- Status change saves successfully +- Toast notification shows success message + +**Why human:** Requires running application stack (frontend + API + database), authenticated session, and manual form interaction. Cannot verify HTTP response codes or API request payloads without live system. + +#### 2. Approve-Both Checkbox Restoration + +**Test:** +1. Create a new entity status change (using test from #1) +2. Navigate to ApproveReview page for an entity with `status_change = 1` +3. Look for "Also approve new status" checkbox in approval modal + +**Expected:** +- Checkbox appears with label "Also approve new status" +- Checkbox is enabled and clickable +- When checked, both review and status are approved together + +**Why human:** Requires database state with `entity.status_change === 1` flag set. The code path is verified (v-if condition exists), but actual rendering and checkbox interaction needs visual confirmation. + +#### 3. Axios Security Patch Verification + +**Test:** +1. Run `npm audit` in app directory +2. Check for any HIGH or CRITICAL vulnerabilities +3. Specifically check axios is not listed + +**Expected:** +- No axios vulnerabilities reported +- CVE-2026-25639 (DoS via prototype pollution) not present +- Overall audit may have other issues, but axios should be clean + +**Why human:** Already automated (verified clean), but curator should confirm in production environment that no axios security warnings appear. + +--- + +## Data Flow Trace: entity_id Preservation + +Traced entity_id through entire submission lifecycle: + +1. **User clicks "Modify Status" button** → `showStatusModify()` called (line 1215) + +2. **Reset happens FIRST** → `resetStatusForm()` (line 1217) + - Clears all form data to clean state + - Prevents stale data from previous modal opens + +3. **Entity data loaded** → `getEntity()` (line 1220) + - Loads entity_info into component state + - Guards against entity not found (line 1223) + +4. **Status data loaded** → `loadStatusByEntity()` (line 1228) + - Calls `useStatusForm.ts:loadStatusByEntity()` (line 173) + - Sets `formData.entity_id` (line 157 in useStatusForm.ts) + - **CRITICAL:** This happens BEFORE modal shows + +5. **Modal renders** → `this.$refs.modifyStatusModal.show()` (line 1247) + - Triggers `@show="onModifyStatusModalShow"` event + - Handler at line 1453 is now empty (no reset) + - **CRITICAL:** entity_id is preserved + +6. **User submits form** → `submitStatusChange()` calls `submitForm()` (useStatusForm.ts:202) + - Creates Status object (line 216) + - Assigns `entity_id` from formData (line 222) + - POSTs to `/api/status/create` (line 240) + - **entity_id is present in request body** + +## Root Cause Analysis + +### The Bug +Modal lifecycle timing issue caused by asynchronous event handling: + +``` +BEFORE (buggy): +1. loadStatusByEntity() sets entity_id +2. modal.show() triggers render +3. @show event fires ASYNCHRONOUSLY +4. onModifyStatusModalShow() calls resetStatusForm() +5. entity_id DELETED before user submits +6. POST body missing entity_id → HTTP 500 +``` + +### The Fix +Reorder reset to happen BEFORE data load: + +``` +AFTER (fixed): +1. resetStatusForm() clears stale data +2. loadStatusByEntity() sets entity_id +3. modal.show() triggers render +4. @show event fires but does nothing +5. entity_id PRESERVED when user submits +6. POST body includes entity_id → HTTP 200 +``` + +## Verification Chain + +### Chain 1: Status Change Success +``` +✓ Code fix: reset→load ordering verified +? HTTP response: Needs human testing (requires running app) +? Database write: Needs human testing (requires DB verification) +``` + +### Chain 2: POST Body Structure +``` +✓ formData.entity_id assigned (useStatusForm.ts:157) +✓ statusObj.entity_id assigned (useStatusForm.ts:222) +✓ statusObj sent in axios POST (useStatusForm.ts:240-248) +→ VERIFIED: entity_id in POST body +``` + +### Chain 3: Approve-Both Checkbox +``` +✓ Conditional rendering: v-if="entity.status_change" (ApproveReview.vue:521) +✓ Approval logic: checks status_change === 1 (ApproveReview.vue:1727) +? Visual appearance: Needs human testing (requires entity with status_change=1) +→ Code paths verified, rendering needs human +``` + +### Chain 4: Axios Security +``` +✓ package.json allows ^1.13.4 (permits 1.13.5) +✓ package-lock.json locks 1.13.5 +✓ node_modules/axios/package.json shows 1.13.5 +✓ npm audit shows no axios vulnerabilities +→ VERIFIED: Security patch applied +``` + +--- + +## Automated Verification Results + +### Type Checking +```bash +$ npm run type-check +> vue-tsc --noEmit +# Exit code 0 (success, no output) +``` + +### Linting +```bash +$ npm run lint +> eslint . --ext .vue,.js,.ts,.tsx +# Exit code 0 (success, no output) +``` + +### Axios Version +```bash +$ node -e "const p = require('./node_modules/axios/package.json'); console.log(p.version)" +1.13.5 +``` + +### Security Audit +```bash +$ npm audit | grep -i axios +# No output (no axios vulnerabilities) +``` + +--- + +## Summary + +**Automated Verification:** ✓ PASSED (5/5 must-haves structurally verified) + +**Code Quality:** +- Modal lifecycle bug fixed with correct reset→load ordering +- entity_id preservation verified through code trace +- No anti-patterns or stub code introduced +- Type-check and lint pass cleanly +- Security vulnerability patched (axios 1.13.5) + +**Human Testing Required:** +Two items need manual verification in running application: +1. **Status change HTTP 200:** Verify API returns success (not 500) +2. **Checkbox visibility:** Verify UI renders approve-both checkbox + +**Recommendation:** PROCEED with human testing. All code-level verification passes. The fix addresses the root cause (modal lifecycle timing), and the data flow trace confirms entity_id will reach the API. + +--- + +_Verified: 2026-02-10T10:15:00Z_ +_Verifier: Claude (gsd-verifier)_ From 9278cc2f6bdb2386fe5a747fb2703d0d3d70bd1c Mon Sep 17 00:00:00 2001 From: Bernt Popp Date: Tue, 10 Feb 2026 10:40:54 +0100 Subject: [PATCH 09/34] docs(84): capture phase context Phase 84: Status Change Detection - Implementation decisions documented - Phase boundary established --- .../84-status-change-detection/84-CONTEXT.md | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .planning/phases/84-status-change-detection/84-CONTEXT.md diff --git a/.planning/phases/84-status-change-detection/84-CONTEXT.md b/.planning/phases/84-status-change-detection/84-CONTEXT.md new file mode 100644 index 00000000..7498b895 --- /dev/null +++ b/.planning/phases/84-status-change-detection/84-CONTEXT.md @@ -0,0 +1,68 @@ +# Phase 84: Status Change Detection - Context + +**Gathered:** 2026-02-10 +**Status:** Ready for planning + + +## Phase Boundary + +Add frontend change detection to skip status/review creation when user didn't change anything. Make the "pending change" indicators (exclamation-triangle badge) consistent across all curation views. Prevent unnecessary database records from being created when curators save without modifications. + + + + +## Implementation Decisions + +### Detection scope +- Track all three editable status form fields: **category_id** (dropdown), **problematic** (removal flag), **comment** (textarea) +- Exact comparison — any change counts, including whitespace in comments +- Apply change detection to **both status AND review forms** across all three components: ModifyEntity, ApproveReview, ApproveStatus +- Follow the same pattern already used for review change detection in the codebase +- Must be fully tested with no regressions + +### Change indicators across curation views +- The "Status change pending" exclamation-triangle badge (already in ApproveReview) must appear in **all curation views**: ModifyEntity, ApproveReview, ApproveStatus +- Backend already calculates both `status_change` (in review endpoint) and `review_change` (in status endpoint) +- ApproveStatus currently receives `review_change` data but doesn't render it — must fix this gap +- Indicators show **database state**: whether an entity has an unapproved status or review pending (active != newest) +- Small icon next to the status/review action icon with border around the action icon + +### Save behavior +- Silent skip: Save button stays enabled, clicking it closes modal without API call when nothing changed +- No toast or message for skipped saves — seamless experience +- Successful saves keep current behavior (existing success toast) + +### Unsaved changes warning +- Show confirmation dialog if user tries to close modal with unsaved changes +- Discard form state on close — no draft persistence (clean slate each time) +- Follow the `hasChanges` computed property pattern from LlmPromptEditor + +### Claude's Discretion +- Exact implementation of `hasChanges()` comparison logic in composables +- How to structure the change tracking across the three components (shared composable vs per-component) +- Confirmation dialog styling/wording +- Test structure and organization + + + + +## Specific Ideas + +- Use the existing `hasChanges` pattern from `LlmPromptEditor.vue` (computed property comparing edited values to loaded values) +- The exclamation-triangle-fill icon overlaid on stoplights is the established pattern — replicate it where missing +- Backend already provides the data (`status_change`, `review_change`) — frontend just needs to render consistently +- Research best practices for form change detection as a senior full-stack dev would approach it + + + + +## Deferred Ideas + +None — discussion stayed within phase scope + + + +--- + +*Phase: 84-status-change-detection* +*Context gathered: 2026-02-10* From d63875f197fa24be877fcce805670e05a0a9b174 Mon Sep 17 00:00:00 2001 From: Bernt Popp Date: Tue, 10 Feb 2026 10:57:21 +0100 Subject: [PATCH 10/34] docs(84): research phase domain Phase 84: Status Change Detection - Standard stack identified (Vue 3, TypeScript, Bootstrap-Vue-Next) - Architecture patterns documented (hasChanges computed, modal @hide events) - Pitfalls catalogued (modal timing, reactive proxy comparison) - Code examples from LlmPromptEditor, useReviewForm patterns - Missing review_change indicator identified in ApproveStatus --- .../84-status-change-detection/84-RESEARCH.md | 660 ++++++++++++++++++ 1 file changed, 660 insertions(+) create mode 100644 .planning/phases/84-status-change-detection/84-RESEARCH.md diff --git a/.planning/phases/84-status-change-detection/84-RESEARCH.md b/.planning/phases/84-status-change-detection/84-RESEARCH.md new file mode 100644 index 00000000..ff2af8d8 --- /dev/null +++ b/.planning/phases/84-status-change-detection/84-RESEARCH.md @@ -0,0 +1,660 @@ +# Phase 84: Status Change Detection - Research + +**Researched:** 2026-02-10 +**Domain:** Vue 3 frontend form change detection, Bootstrap-Vue-Next modal events +**Confidence:** HIGH + +## Summary + +This phase adds change detection to status and review forms to prevent unnecessary database records when curators save without making changes. The research reveals a mature codebase with existing patterns for both form management and change detection that can be extended. + +**Key findings:** +- `useStatusForm` and `useReviewForm` composables already manage form state but lack change detection +- `LlmPromptEditor.vue` provides the exact `hasChanges` computed property pattern to replicate +- Bootstrap-Vue-Next BModal supports `@hide` event with `preventDefault()` for unsaved changes warnings +- Backend already calculates `status_change` and `review_change` flags in review/status endpoints +- ApproveReview correctly displays status change indicators, but ApproveStatus doesn't display review change indicators + +**Primary recommendation:** Extend both composables with `hasChanges` computed properties comparing current form data to loaded data, implement silent skip on save when unchanged, add @hide handlers for unsaved changes warnings, and fix missing review_change indicator in ApproveStatus. + +## Standard Stack + +### Core +| Library | Version | Purpose | Why Standard | +|---------|---------|---------|--------------| +| Vue 3 | 3.5.25 | Reactive framework | Project standard | +| TypeScript | 5.7.3 | Type safety | Project standard | +| Bootstrap-Vue-Next | 0.42.0 | UI components including BModal | Project standard | + +### Supporting +| Library | Version | Purpose | When to Use | +|---------|---------|---------|-------------| +| @vue/test-utils | - | Component testing | Testing change detection logic | +| Vitest | - | Test runner | Unit tests for composables | + +### Alternatives Considered +None — this phase works within existing stack. + +## Architecture Patterns + +### Recommended Implementation Structure + +``` +app/src/views/curate/ +├── composables/ +│ ├── useStatusForm.ts # Add hasChanges computed +│ ├── useReviewForm.ts # Add hasChanges computed (may already have pattern) +│ └── __tests__/ +│ ├── useStatusForm.spec.ts # Add change detection tests +│ └── useReviewForm.spec.ts # Already exists with BUG-05 tests +├── ModifyEntity.vue # Add @hide handler, skip save logic +├── ApproveReview.vue # Add @hide handler, skip save logic +└── ApproveStatus.vue # Add @hide handler, skip save logic, FIX review_change indicator +``` + +### Pattern 1: Change Detection with Computed Property + +**What:** Track loaded data separately from edited data, use computed to detect differences +**When to use:** Any form that loads existing data and allows edits +**Example from LlmPromptEditor.vue:** + +```typescript +// Source: /home/bernt-popp/development/sysndd/app/src/components/llm/LlmPromptEditor.vue + +// Loaded data (comes from API) +const currentPrompt = computed(() => props.prompts?.[selectedPrompt.value]); + +// Edited data (user changes) +const editedTemplate = ref(''); +const editedVersion = ref(''); + +// Watch to sync loaded → edited when data changes +watch( + [selectedPrompt, () => props.prompts], + () => { + if (currentPrompt.value) { + editedTemplate.value = currentPrompt.value.template_text; + editedVersion.value = currentPrompt.value.version; + } + }, + { immediate: true } +); + +// Change detection +const hasChanges = computed(() => { + if (!currentPrompt.value) return false; + return ( + editedTemplate.value !== currentPrompt.value.template_text || + editedVersion.value !== currentPrompt.value.version + ); +}); +``` + +**Application to useStatusForm:** +```typescript +// Add to useStatusForm.ts composable + +// Store original loaded data +const loadedData = ref | null>(null); + +// In loadStatusData() and loadStatusByEntity(), after setting formData: +loadedData.value = { + category_id: formData.category_id, + comment: formData.comment, + problematic: formData.problematic, +}; + +// Export hasChanges computed +const hasChanges = computed(() => { + if (!loadedData.value) return false; + return ( + formData.category_id !== loadedData.value.category_id || + formData.comment !== loadedData.value.comment || + formData.problematic !== loadedData.value.problematic + ); +}); + +// Reset loadedData in resetForm() +loadedData.value = null; + +// Return hasChanges +return { + // ... existing exports + hasChanges, +}; +``` + +### Pattern 2: Modal Close Prevention with Unsaved Changes + +**What:** Intercept modal close events and show confirmation if user has unsaved changes +**When to use:** Any modal with forms that track changes +**Bootstrap-Vue-Next event documentation:** + +```typescript +// Source: https://bootstrap-vue-next.github.io/bootstrap-vue-next/docs/components/modal + +// BModal emits 'hide' event before closing +// Event object has preventDefault() method + + + + + +const onModalHide = (event: BvTriggerableEvent) => { + if (hasChanges.value) { + const confirmed = window.confirm('You have unsaved changes. Discard?'); + if (!confirmed) { + event.preventDefault(); // Prevents modal from closing + } + } +}; +``` + +**Application to ModifyEntity.vue:** +```vue + + + + + +``` + +### Pattern 3: Silent Skip on Save + +**What:** When user clicks Save but hasChanges is false, close modal without API call or toast +**When to use:** Forms with change detection that shouldn't penalize users for clicking save +**Implementation:** + +```typescript +// In submitStatusChange(), submitReviewChange() methods + +async submitStatusChange() { + // Check for changes FIRST + if (!this.hasStatusChanges) { + // Silent skip - just close modal + this.$refs.modifyStatusModal.hide(); + return; + } + + // Existing submission logic + this.submitting = 'status'; + try { + await this.submitStatusForm(false, false); + this.makeToast('Status submitted successfully', 'Success', 'success'); + // ... rest of success handling + } catch (e) { + // ... error handling + } finally { + this.submitting = null; + } +} +``` + +### Pattern 4: Change Indicator Icons + +**What:** Show exclamation-triangle-fill overlay on stoplights/action icons when pending changes exist +**When to use:** Table rows where backend provides status_change or review_change flags +**Example from ApproveReview.vue:** + +```vue + + + + +