docs(22-04): complete ClassificationCard + TimelineCard plan
- Add 22-04-SUMMARY.md documenting the two components delivered - Log pre-existing unrelated itglue-search.test.ts failures to deferred-items.md (out of scope for this plan) - Mark REVIEW-02, REVIEW-04 complete in REQUIREMENTS.md
This commit is contained in:
parent
e990a320b2
commit
fdc6a52dde
3 changed files with 117 additions and 4 deletions
|
|
@ -117,14 +117,14 @@ destructive remediation gated behind explicit human approval.
|
||||||
(LiveLink supplies the ticket ID as dynamic content, not the internal
|
(LiveLink supplies the ticket ID as dynamic content, not the internal
|
||||||
campaign UUID), authenticated via the existing Better Auth session only —
|
campaign UUID), authenticated via the existing Better Auth session only —
|
||||||
no separate token or query-param auth scheme
|
no separate token or query-param auth scheme
|
||||||
- [ ] **REVIEW-02**: The page displays the campaign's timeline — linked
|
- [x] **REVIEW-02**: The page displays the campaign's timeline — linked
|
||||||
reports, classification history, and audit events (classify/approve/
|
reports, classification history, and audit events (classify/approve/
|
||||||
remediate/mark-false-positive) — in chronological order
|
remediate/mark-false-positive) — in chronological order
|
||||||
- [ ] **REVIEW-03**: The page displays the gathered evidence — parsed EML
|
- [ ] **REVIEW-03**: The page displays the gathered evidence — parsed EML
|
||||||
headers/URLs/attachments, sanitized body preview, and Mimecast blast-radius
|
headers/URLs/attachments, sanitized body preview, and Mimecast blast-radius
|
||||||
data (including an explicit `unavailable` state when Mimecast isn't
|
data (including an explicit `unavailable` state when Mimecast isn't
|
||||||
configured) — never rendering a raw/unsanitized body or unredacted secrets
|
configured) — never rendering a raw/unsanitized body or unredacted secrets
|
||||||
- [ ] **REVIEW-04**: The page displays the current classification (SPAM/
|
- [x] **REVIEW-04**: The page displays the current classification (SPAM/
|
||||||
UNWANTED/THREAT), confidence, reasons, and recommended remediation
|
UNWANTED/THREAT), confidence, reasons, and recommended remediation
|
||||||
action(s)
|
action(s)
|
||||||
- [ ] **REVIEW-05**: An operator can approve, remediate, or mark a campaign as
|
- [ ] **REVIEW-05**: An operator can approve, remediate, or mark a campaign as
|
||||||
|
|
@ -204,9 +204,9 @@ Populated during roadmap creation.
|
||||||
| NOTE-01 | Phase 21 | Complete |
|
| NOTE-01 | Phase 21 | Complete |
|
||||||
| ACCESS-01 | Phase 18 | Complete |
|
| ACCESS-01 | Phase 18 | Complete |
|
||||||
| REVIEW-01 | Phase 22 | Pending |
|
| REVIEW-01 | Phase 22 | Pending |
|
||||||
| REVIEW-02 | Phase 22 | Pending |
|
| REVIEW-02 | Phase 22 | Complete |
|
||||||
| REVIEW-03 | Phase 22 | Pending |
|
| REVIEW-03 | Phase 22 | Pending |
|
||||||
| REVIEW-04 | Phase 22 | Pending |
|
| REVIEW-04 | Phase 22 | Complete |
|
||||||
| REVIEW-05 | Phase 22 | Pending |
|
| REVIEW-05 | Phase 22 | Pending |
|
||||||
| REVIEW-06 | Phase 22 | Pending |
|
| REVIEW-06 | Phase 22 | Pending |
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
---
|
||||||
|
phase: 22-approval-ui-livelink-addressable-campaign-review-and-approve
|
||||||
|
plan: 04
|
||||||
|
subsystem: ui
|
||||||
|
tags: [react, nextjs, shadcn, phishing, tailwind, lucide]
|
||||||
|
|
||||||
|
# Dependency graph
|
||||||
|
requires:
|
||||||
|
- phase: 22-01/02/03
|
||||||
|
provides: campaign detail data shape (classification + timeline arrays) these components render
|
||||||
|
provides:
|
||||||
|
- "ClassificationCard — read-only latest-verdict display with Reclassify action (REVIEW-04)"
|
||||||
|
- "TimelineCard — chronological merged-event renderer (REVIEW-02)"
|
||||||
|
affects: [22-05, 22-06]
|
||||||
|
|
||||||
|
# Tech tracking
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- "CardTitle className=\"font-bold\" override for the 700-weight card heading (UI-SPEC Typography contract)"
|
||||||
|
- "Client-side hasPermission(role, 'phishing', <action>) gate via useSession(), hiding (not disabling) the affordance"
|
||||||
|
- "Null-classification early-return guard — page-level empty states (plan 06) own the replacement UI, not the card"
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- components/phishing/classification-card.tsx
|
||||||
|
- components/phishing/timeline-card.tsx
|
||||||
|
modified: []
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "TimelineCard defines its own local TimelineEntry union (per plan's fallback instruction) rather than importing from plan 02, since plan 04 has no depends_on and plan 02's extended detail route may not exist in this worktree yet."
|
||||||
|
- "Audit event_type 'campaign_classified' is rendered with the same Sparkles/verdict-tinted treatment as 'classification' kind entries (per the plan's <interfaces> note: \"campaign_classified → classification entries\"), distinct from the generic humanized fallback used for any other/future event_type."
|
||||||
|
- "remediation_approved action count derived defensively from payload.actionIds.length, falling back to payload.actions.length, then 0 — matches the two audit-payload shapes actually written by lib/services/remediation-service.ts."
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "Timeline rail: 8px h-2 w-2 rounded-full dot + 1px border-l connector, built with plain divs/Tailwind (UI-SPEC explicitly calls this a one-off, not a new primitive)."
|
||||||
|
|
||||||
|
requirements-completed: [REVIEW-02, REVIEW-04]
|
||||||
|
|
||||||
|
# Metrics
|
||||||
|
duration: 12min
|
||||||
|
completed: 2026-07-16
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 22 Plan 04: ClassificationCard + TimelineCard Summary
|
||||||
|
|
||||||
|
**Two read-only presentational components — ClassificationCard (verdict/confidence/reasons/action-chips + approval warning, Reclassify gated on `phishing:analyze`) and TimelineCard (chronological reports+classifications+audit-events merge) — built per the UI-SPEC color/typography contract with zero new dependencies.**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** ~12 min
|
||||||
|
- **Started:** 2026-07-16T18:18:00Z
|
||||||
|
- **Completed:** 2026-07-16T18:29:48Z
|
||||||
|
- **Tasks:** 2/2 completed
|
||||||
|
- **Files modified:** 2 created
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
- `ClassificationCard` renders the latest classification's verdict badge, confidence, summary, reasons list, and informational recommended-action chips; shows an amber `Alert` when `requiresApproval` is true; hides (not disables) the Reclassify button unless the session role has `phishing:analyze`; returns `null` when `classification` is missing so the review page's empty states (plan 06) own that surface.
|
||||||
|
- `TimelineCard` merges `report` / `classification` / `audit` timeline entries into a single ascending vertical list (relies on server ordering, never calls `.sort()`), with a per-kind icon/label/tint mapping including the full `audit_events.event_type` table from the UI-SPEC (`remediation_approved`, `remediation_completed`, `campaign_marked_false_positive`, `campaign_classified`, and a humanized fallback for anything else).
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
1. **Task 1: ClassificationCard (REVIEW-04)** - `14adddf` (feat)
|
||||||
|
2. **Task 2: TimelineCard (REVIEW-02)** - `e990a32` (feat)
|
||||||
|
|
||||||
|
**Plan metadata:** commit pending (this SUMMARY + REQUIREMENTS)
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
- `components/phishing/classification-card.tsx` - `ClassificationCard({ campaignId, classification, onReclassified })` — verdict `StatusBadge`, confidence, summary, reasons `<ul>`, recommended-action chips, requires-approval `Alert`, permission-gated Reclassify button that POSTs `/api/phishing/campaigns/{id}/classify`.
|
||||||
|
- `components/phishing/timeline-card.tsx` - `TimelineCard({ timeline })` — locally-defined `TimelineEntry` union, per-kind rendering, 8px rail-dot + `border-l` layout, `date-fns` `formatDistanceToNow` for relative timestamps with the absolute value in a `title` attribute.
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
- Defined `TimelineEntry` locally in `timeline-card.tsx` rather than importing a shared type, since this plan has `depends_on: []` and plan 02's extended detail route (which shapes this array) is not guaranteed to exist yet in a parallel-wave worktree. The union mirrors the plan's `<interfaces>` block exactly (`kind: 'report' | 'classification' | 'audit'`).
|
||||||
|
- Treated audit `event_type: 'campaign_classified'` as its own case (Sparkles icon, verdict-tinted per `payload.verdict`) rather than falling through to the generic "any other" fallback, per the plan's explicit `<interfaces>` note ("campaign_classified → classification entries").
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None — plan executed exactly as written. One out-of-scope, pre-existing test failure was discovered while running the plan's `npm test` verification step and logged (not fixed) per the executor's scope-boundary rule — see `## Deferred Issues` below.
|
||||||
|
|
||||||
|
## Deferred Issues
|
||||||
|
|
||||||
|
- `lib/services/analyzer/itglue-search.test.ts` has 2 failing assertions (`docs.length` mismatches in "tolerates per-call failures" tests). This file is unrelated to either component built in this plan and was not modified by any phase-22 plan. Logged to `.planning/phases/22-approval-ui-livelink-addressable-campaign-review-and-approve/deferred-items.md`; not fixed (out of scope). All other 394 tests pass.
|
||||||
|
|
||||||
|
## Verification Results
|
||||||
|
|
||||||
|
- `npx tsc --noEmit --pretty` — clean, no errors anywhere in the repo (including both new files).
|
||||||
|
- `npm test` — 394/396 passing; the 2 pre-existing failures are unrelated to this plan (see Deferred Issues above).
|
||||||
|
- Task-level acceptance criteria (grep checks for `hasPermission`, `font-bold`, `requiresApproval`, zero `Checkbox` occurrences, `h-2 w-2`, `remediation_completed`, `border-l`, zero `.sort(` occurrences) all passed as specified in the plan.
|
||||||
|
|
||||||
|
## Known Stubs
|
||||||
|
|
||||||
|
None — both components are fully wired to the props/interfaces specified in the plan; no hardcoded empty values or placeholder text.
|
||||||
|
|
||||||
|
## Threat Flags
|
||||||
|
|
||||||
|
None — both components render all classification/audit content as JSX text (React auto-escaping, no `dangerouslySetInnerHTML`), matching threat register items T-22-10 (XSS mitigation) and T-22-11 (Reclassify button gated client-side on `hasPermission`, with the server route independently enforcing the same permission per the plan's threat model).
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Deferred Items — Phase 22
|
||||||
|
|
||||||
|
Items discovered during execution that are out of scope for the current plan
|
||||||
|
(pre-existing, unrelated to the files this plan touches). Logged, not fixed,
|
||||||
|
per the executor's scope-boundary rule.
|
||||||
|
|
||||||
|
## 22-04: Pre-existing `itglue-search.test.ts` failures
|
||||||
|
|
||||||
|
- **Discovered during:** Plan 22-04 (`ClassificationCard` + `TimelineCard`), running `npm test` for the plan's verification step.
|
||||||
|
- **File:** `lib/services/analyzer/itglue-search.test.ts`
|
||||||
|
- **Symptom:** 2 failing assertions —
|
||||||
|
- `itglueSearch > tolerates per-call failures (flex asset errors, configurations still returns)`: `expect(result.docs.length).toBe(2)` received `1`
|
||||||
|
- `itglueSearch > tolerates per-call failures (configurations errors, flex still returns)`: `expect(result.docs.length).toBe(1)` received `0`
|
||||||
|
- **Scope:** Unrelated to this plan's files (`components/phishing/classification-card.tsx`, `components/phishing/timeline-card.tsx`). Not touched by plan 22-04 or any prior phase-22 plan.
|
||||||
|
- **Action:** Not fixed — out of scope per executor scope-boundary rules. All other 394 tests pass; `npx tsc --noEmit --pretty` is clean.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue