# Implementation Plan: Compliance List Stale After Sidebar Edit ## Overview Fix the frontend state-refresh defect described in GitLab issue [#23](http://steam-gitlab.charterlab.com/steam/cve-dashboard/-/issues/23). After a successful metadata save in `ComplianceDetailPanel` (`PATCH /api/compliance/items/:hostname/metadata`), the host list on `CompliancePage` keeps showing the pre-edit Resolution Date / Remediation Plan until an unrelated refresh trigger fires. The backend list endpoint already returns the correct data (fixed in the predecessor spec `compliance-remediation-display-fix`); the defect is purely that the sidebar never notifies the parent after a metadata save, so `fetchDevices` is never re-issued. The fix mirrors the existing `onNoteAdded` → `refresh` pattern: `handleSaveMetadata()` invokes a new parent callback (`onMetadataSaved`) after a successful `PATCH`, and `CompliancePage` wires that callback to `refresh` (`fetchSummary` + `fetchDevices`). The backend is out of scope. All tasks below are coding/test tasks. Tests live in the frontend and run via `react-scripts test` (`CI=true npm test`), using `@testing-library/react`, `@testing-library/jest-dom`, and `fast-check` (already permitted by `jest.transformIgnorePatterns` in `frontend/package.json`). ## Tasks - [x] 1. Write bug condition exploration property test - **Property 1: Bug Condition** - List Row Stays Stale After Sidebar Metadata Save - **CRITICAL**: This test MUST FAIL on the current unfixed code — failure confirms the bug exists - **DO NOT attempt to fix the test or the code when it fails** — the failure is the expected, desired outcome at this step - **NOTE**: This test encodes the expected behavior (Property 1 below) — it will validate the fix when it passes after implementation - **GOAL**: Surface counterexamples demonstrating that after a successful sidebar metadata save the parent `CompliancePage` list does NOT reflect the saved value without a manual refresh - **Test file**: `frontend/src/components/pages/__tests__/compliance-list-stale-after-sidebar-edit.exploration.property.test.js` - **Header comment**: reference spec `.kiro/specs/compliance-list-stale-after-sidebar-edit/` and issue #23, and state "EXPECTED TO FAIL on unfixed code — failure confirms the bug" (mirrors `backend/__tests__/compliance-remediation-display-fix.exploration.property.test.js` tagging convention) - **Bug Condition** (from bugfix.md Current Behavior): `isBugCondition(input) = a successful PATCH /api/compliance/items/:hostname/metadata occurred from the sidebar` — under this condition the parent list is never re-fetched (1.1), `onClose` only clears `selectedHost` (1.2), and the row shows the stale value (1.3) - **Scoped PBT Approach**: use `fast-check` to generate varied saved-metadata values across runs and assert the universal property holds for each: - `fc.assert(fc.asyncProperty(arbResolutionDate, arbRemediationPlan, async (resolutionDate, remediationPlan) => { ... }), { numRuns: ... })` - `arbResolutionDate`: build `YYYY-MM-DD` strings from integer tuples (do NOT call `toISOString` on shrunk values — follow the date-generator pattern in the predecessor exploration test) - `arbRemediationPlan`: non-empty trimmed strings, length-bounded (e.g. 1–200) - **Property under test**: _for any_ saved metadata value, after a successful sidebar save the parent list row for that hostname displays the saved value without a manual filter/team/tab change or manual refresh click - **Test structure**: - Mock `useAuth` from `../../contexts/AuthContext` (e.g. `jest.mock`) to return a stub: `canWrite: () => true`, `isAdmin: () => false`, `getAvailableTeams: () => ['STEAM']`, `adminScope: null` - Mock `global.fetch` with a URL-routing implementation responding to: - `GET /compliance/summary?team=STEAM` → minimal summary `{ entries: [], overall_scores: {}, upload: { report_date: '2025-01-01' } }` - `GET /compliance/items?team=STEAM&status=active` → `{ devices: [oneDevice] }` where the device starts with a stale value (e.g. `resolution_date: null`, `remediation_plan: null`) on the FIRST call, and returns the generated saved value on SUBSEQUENT calls (so a re-fetch is observable) - `GET /compliance/items/:hostname` → detail object with one active metric so the panel renders the Resolution Date / Remediation Plan inputs and Save button - `PATCH /compliance/items/:hostname/metadata` → `{ ok: true }` success response - Render ` {}} />`, wait for the device row to appear, click the row to open `ComplianceDetailPanel` - Set the Resolution Date input and/or Remediation Plan textarea to the generated value(s) and click **Save** (use `@testing-library/user-event` or `fireEvent`, wrapped in `act`/`waitFor`) - **Primary assertion (encodes Property 1)**: `await waitFor(() => expect(within(row).getByText()).toBeInTheDocument())` — the list row reflects the saved value - **Supporting assertion**: the list endpoint `GET /compliance/items?...` is called more than once (initial load + post-save refresh); on unfixed code it is called exactly once - Run on UNFIXED code: `cd frontend && CI=true npm test -- --testPathPattern compliance-list-stale-after-sidebar-edit.exploration` - **EXPECTED OUTCOME**: Test FAILS — `handleSaveMetadata()` never invokes a parent callback, so `fetchDevices` is not re-issued and the row keeps the stale value (the list GET is called only once) - Document the counterexample found (e.g. "after saving resolution_date='2026-03-04', the list row still shows '—'; GET /compliance/items called 1 time, expected ≥ 2") - Mark this task complete when the test is written, run, and the failure is documented - _Requirements: 1.1, 1.2, 1.3, 2.1, 2.2_ - [x] 2. Fix the stale list after sidebar metadata save - [x] 2.1 Invoke a parent refresh callback from `handleSaveMetadata()` after a successful PATCH - In `frontend/src/components/pages/ComplianceDetailPanel.js`, add `onMetadataSaved` to the component props: `export default function ComplianceDetailPanel({ hostname, onClose, onNoteAdded, onMetadataSaved, onNavigate })` - In `handleSaveMetadata()`, after the `PATCH` succeeds and `await fetchDetail()` completes (inside the `try`, after the existing success handling), add: `if (onMetadataSaved) onMetadataSaved();` - Place the call so it only runs on success (not in the `catch`), mirroring how `handleAddNote()` calls `if (onNoteAdded) onNoteAdded();` only after a successful note save - Keep the existing `await fetchDetail()` call so the panel's own view/history still refreshes (do not replace it) - _Bug_Condition: isBugCondition(input) = successful PATCH /api/compliance/items/:hostname/metadata from the sidebar_ - _Expected_Behavior: a parent refresh callback is invoked on save success so the parent list re-fetches (bugfix.md 2.1)_ - _Preservation: only the success path is touched; the `catch` path that sets `metaError` is unchanged (3.3); `fetchDetail()` still runs (3.6)_ - _Requirements: 2.1, 3.3, 3.6_ - [x] 2.2 Wire `onMetadataSaved` to `refresh` in `CompliancePage` - In `frontend/src/components/pages/CompliancePage.js`, at the `` render site (around lines 696–702), add the prop `onMetadataSaved={refresh}` alongside the existing `onNoteAdded={refresh}` - Reuse the existing `refresh` function (`fetchSummary(activeTeam)` + `fetchDevices(activeTeam, activeTab)`) — do not introduce a new refresh path - Leave `onClose={() => setSelectedHost(null)}` unchanged (close still just clears selection) - Do not leave any unused variables; if a value is intentionally unused prefix it with `_` per the ESLint `no-unused-vars` warning budget - _Bug_Condition: isBugCondition(input) = successful sidebar metadata save while the panel is open from CompliancePage_ - _Expected_Behavior: the parent list re-fetches via fetchDevices so the updated Resolution Date / Remediation Plan appears in the row without a manual refresh (bugfix.md 2.1, 2.2)_ - _Preservation: onNoteAdded still maps to refresh (3.1); team/tab/upload/manual-refresh triggers unchanged (3.2); onClose unchanged (3.4); other row fields unchanged (3.5)_ - _Requirements: 2.1, 2.2_ - [x] 2.3 Verify the bug condition exploration test now passes - **Property 1: Expected Behavior** - List Row Reflects Saved Value Without Manual Refresh - **IMPORTANT**: Re-run the SAME test from Task 1 — do NOT write a new test - The Task 1 test encodes the expected behavior; when it passes it confirms the fix satisfies Property 1 - Run: `cd frontend && CI=true npm test -- --testPathPattern compliance-list-stale-after-sidebar-edit.exploration` - **EXPECTED OUTCOME**: Test PASSES — after a successful save the list re-fetches (`GET /compliance/items?...` called ≥ 2 times) and the row displays the saved value - _Requirements: 2.1, 2.2_ - [x] 3. Write preservation / regression property test - **Property 2: Preservation** - Unchanged Behaviors Hold After the Fix - **IMPORTANT**: Follow observation-first methodology — the unchanged behaviors below are observed to hold on UNFIXED code and must continue to hold after the fix; only the new "list updates after save" guard is expected to flip from failing to passing - **Test file**: `frontend/src/components/pages/__tests__/compliance-list-stale-after-sidebar-edit.preservation.property.test.js` - **Header comment**: reference spec `.kiro/specs/compliance-list-stale-after-sidebar-edit/` and issue #23 (mirrors the predecessor preservation test tagging convention) - **Test structure**: reuse the same `useAuth` mock and URL-routing `global.fetch` mock approach as Task 1; use `fast-check` to generate varied note text and metadata values across runs - **Regression guard (new behavior, post-fix)**: _for any_ saved metadata value, after a successful save the parent list row displays the saved value (same assertion family as Property 1, kept as a standing regression guard) - **Preservation properties (must hold before and after the fix)**: - **Note-add refresh still works**: when a note is added in the sidebar (`POST /compliance/notes` succeeds), the existing `onNoteAdded` callback still triggers a list re-fetch — assert `GET /compliance/items?...` is re-issued after a note add (bugfix.md 3.1) - **Failed save shows error and does NOT falsely update the list**: when `PATCH /compliance/items/:hostname/metadata` returns a non-OK response, assert the panel surfaces `metaError` AND the list row value is unchanged / no spurious list re-fetch displays a value that was never persisted (bugfix.md 3.3) - **Close-without-change just clears selection**: clicking the close (`X`) / backdrop without saving removes the detail panel (selection cleared) and does not error; no metadata save occurs (bugfix.md 3.4) - **Other row fields render correctly**: _for any_ generated device, the row still displays hostname, IP address, device type, failing metrics, and seen count unchanged (bugfix.md 3.5) - Run before the fix to confirm the preservation properties pass on unfixed code; run after the fix to confirm they still pass and the regression guard now passes - Command: `cd frontend && CI=true npm test -- --testPathPattern compliance-list-stale-after-sidebar-edit.preservation` - **EXPECTED OUTCOME (post-fix)**: All preservation properties PASS (no regressions) and the regression guard PASSES - _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6_ - [x] 4. Checkpoint - Run tests and confirm the production build - Run both new tests together: `cd frontend && CI=true npm test -- --testPathPattern compliance-list-stale-after-sidebar-edit` - **EXPECTED OUTCOME**: exploration test PASSES (bug fixed), preservation test PASSES (no regressions) - Run the production build to confirm it compiles and stays within the ESLint 25-warning budget: `cd frontend && npm run build` - **EXPECTED OUTCOME**: build succeeds and the warning count remains at or below 25 (the CI `lint`/`build` stage fails above 25) — prefix any intentionally-unused variables with `_` if needed - Ensure all tests pass; ask the user if questions arise. ## Task Dependency Graph ```json { "waves": [ { "id": 0, "tasks": ["1"] }, { "id": 1, "tasks": ["2.1", "2.2"] }, { "id": 2, "tasks": ["2.3", "3"] }, { "id": 3, "tasks": ["4"] } ] } ``` ## Notes - This is a frontend-only fix. The backend list endpoint (`GET /api/compliance/items`) already returns `resolution_date` and `remediation_plan` (predecessor spec `compliance-remediation-display-fix`), and `DeviceRow` in `CompliancePage.js` (lines 883–890) already renders them correctly when present. - The fix is intentionally minimal and mirrors the existing `onNoteAdded` → `refresh` pattern rather than introducing a new refresh mechanism, satisfying the Regression Prevention clauses (3.1–3.6). - Both tests render `CompliancePage` with a mocked `useAuth` and a URL-routing `global.fetch` mock; the list GET mock returns the stale value on first call and the saved value on later calls so a post-save re-fetch is observable in the rendered row. - `fast-check` is already allowed through `jest.transformIgnorePatterns` in `frontend/package.json`; generate dates as `YYYY-MM-DD` strings from integer tuples (avoid `toISOString` on shrunk values) as in the predecessor exploration test.