New specs: archer-template-library, ccp-metrics-view-restructure, compliance-list-stale-after-sidebar-edit, compliance-metric-estimated-resolution-date, compliance-remediation-display-fix, flexible-jira-ticket-creation, forecast-burndown-chart, granite-loader-export, ivanti-queue-clear-completed-fix, multi-item-jira-ticket, queue-collapsible-sections, vendor-issue-type-dropdown New steering: archer-template-gen.md Updated: migration-registration-check hook, remediation-plan-history spec, gitlab-workflow, tech, versioning steering files
13 KiB
Implementation Plan: Compliance List Stale After Sidebar Edit
Overview
Fix the frontend state-refresh defect described in GitLab issue #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
-
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
CompliancePagelist 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" (mirrorsbackend/__tests__/compliance-remediation-display-fix.exploration.property.test.jstagging 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),onCloseonly clearsselectedHost(1.2), and the row shows the stale value (1.3) - Scoped PBT Approach: use
fast-checkto 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: buildYYYY-MM-DDstrings from integer tuples (do NOT calltoISOStringon 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
useAuthfrom../../contexts/AuthContext(e.g.jest.mock) to return a stub:canWrite: () => true,isAdmin: () => false,getAvailableTeams: () => ['STEAM'],adminScope: null - Mock
global.fetchwith 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 buttonPATCH /compliance/items/:hostname/metadata→{ ok: true }success response
- Render
<CompliancePage onNavigate={() => {}} />, wait for the device row to appear, click the row to openComplianceDetailPanel - Set the Resolution Date input and/or Remediation Plan textarea to the generated value(s) and click Save (use
@testing-library/user-eventorfireEvent, wrapped inact/waitFor) - Primary assertion (encodes Property 1):
await waitFor(() => expect(within(row).getByText(<generated value, sliced to 10 for the date>)).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
- Mock
- 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, sofetchDevicesis 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
-
2. Fix the stale list after sidebar metadata save
-
2.1 Invoke a parent refresh callback from
handleSaveMetadata()after a successful PATCH- In
frontend/src/components/pages/ComplianceDetailPanel.js, addonMetadataSavedto the component props:export default function ComplianceDetailPanel({ hostname, onClose, onNoteAdded, onMetadataSaved, onNavigate }) - In
handleSaveMetadata(), after thePATCHsucceeds andawait fetchDetail()completes (inside thetry, after the existing success handling), add:if (onMetadataSaved) onMetadataSaved(); - Place the call so it only runs on success (not in the
catch), mirroring howhandleAddNote()callsif (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
catchpath that setsmetaErroris unchanged (3.3);fetchDetail()still runs (3.6) - Requirements: 2.1, 3.3, 3.6
- In
-
2.2 Wire
onMetadataSavedtorefreshinCompliancePage- In
frontend/src/components/pages/CompliancePage.js, at the<ComplianceDetailPanel>render site (around lines 696–702), add the proponMetadataSaved={refresh}alongside the existingonNoteAdded={refresh} - Reuse the existing
refreshfunction (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 ESLintno-unused-varswarning 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
- In
-
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
-
-
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
useAuthmock and URL-routingglobal.fetchmock approach as Task 1; usefast-checkto 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/notessucceeds), the existingonNoteAddedcallback still triggers a list re-fetch — assertGET /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/metadatareturns a non-OK response, assert the panel surfacesmetaErrorAND 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)
- Note-add refresh still works: when a note is added in the sidebar (
- 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
-
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/buildstage fails above 25) — prefix any intentionally-unused variables with_if needed - Ensure all tests pass; ask the user if questions arise.
- Run both new tests together:
Task Dependency Graph
{
"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 returnsresolution_dateandremediation_plan(predecessor speccompliance-remediation-display-fix), andDeviceRowinCompliancePage.js(lines 883–890) already renders them correctly when present. - The fix is intentionally minimal and mirrors the existing
onNoteAdded→refreshpattern rather than introducing a new refresh mechanism, satisfying the Regression Prevention clauses (3.1–3.6). - Both tests render
CompliancePagewith a mockeduseAuthand a URL-routingglobal.fetchmock; 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-checkis already allowed throughjest.transformIgnorePatternsinfrontend/package.json; generate dates asYYYY-MM-DDstrings from integer tuples (avoidtoISOStringon shrunk values) as in the predecessor exploration test.