docs(14-05): record plan 05 execution summary

Needs Review tab (review cards, candidate resolve, manual-search
combobox, count badge) + companies-list requireAuth hardening.
This commit is contained in:
lorentz 2026-07-11 14:44:57 -04:00
parent 13272f9a6f
commit e80a816ec6

View file

@ -0,0 +1,112 @@
---
phase: 14-pax8-ui-surface
plan: 05
subsystem: ui
tags: [next.js, react, shadcn, command, popover, sonner, requireAuth]
# Dependency graph
requires:
- phase: 14-pax8-ui-surface
plan: 02
provides: "GET /api/pax8/company-matches, POST /api/pax8/company-matches/[id]/resolve"
- phase: 14-pax8-ui-surface
plan: 04
provides: "app/pax8/page.tsx page shell with Companies/Needs Review Tabs and the Needs Review placeholder"
provides:
- "app/pax8/page.tsx — Needs Review tab fully implemented: amber review cards, per-candidate resolve buttons, manual company-search combobox (D-05/D-09), count badge"
- "app/api/data/companies-list/route.ts — hardened with requireAuth(), now safe as a data source for authenticated UI"
affects: ["14-06 (manual verification of the Needs Review tab end-to-end)"]
# Tech tracking
tech-stack:
added: []
patterns:
- "Manual-search combobox: shadcn Command inside Popover, fetched once via a guarded ensureCompaniesLoaded() (checks allCompanies !== null / companiesLoading before fetching) and shared across every review card via per-review-id selection state (Record<reviewId, ManualCompanyOption>)"
- "Reused the device-link-conflicts amber-card + Alert/Skeleton/empty-state trio + optimistic-removal resolve() pattern verbatim, extended with a second manual-resolution path calling the same resolve() handler"
key-files:
created: []
modified:
- app/pax8/page.tsx
- app/api/data/companies-list/route.ts
key-decisions:
- "Fetch reviews + companies-list on first activation of the needs-review tab (useEffect keyed on activeTab, guarded by reviews === null / companiesLoading), not on page mount, to avoid an unnecessary request when a manager only visits the Companies tab"
- "Confidence values (raw trigram similarity strings like \"0.850\") are formatted client-side as a rounded percentage (e.g. \"85%\") for the candidate Badge, since the plan left exact formatting to discretion and the numeric convention (font-mono) benefits from a human-readable percentage over a raw decimal string"
- "Count badge on the Needs Review TabsTrigger reuses DetailModal's existing blue-500/20 count-badge style verbatim (per UI-SPEC's explicit callout), not the amber status hue or the --primary accent"
requirements-completed: [PAX8-14, PAX8-12]
# Metrics
duration: ~25min
completed: 2026-07-11
---
# Phase 14 Plan 05: Needs Review Tab — Review Cards, Resolve, Manual Search Summary
**Filled in the `/pax8` Needs Review tab with amber-bordered review cards (candidate "Link company" buttons + a Command/Popover manual company-search fallback), wired to the admin-gated resolve route with optimistic removal and toasts, plus a live count badge; also added `requireAuth()` to `/api/data/companies-list` now that it backs this authenticated UI.**
## Performance
- **Duration:** ~25 min
- **Tasks:** 3 completed
- **Files modified:** 2
## Accomplishments
- `/api/data/companies-list` now calls `requireAuth()` before querying — closes the previously-unauthenticated gap RESEARCH.md flagged, response shape unchanged (`[{ id, company_name }]`).
- Needs Review tab fetches `GET /api/pax8/company-matches?limit=100` on first tab activation and renders one `Card className="border-amber-200"` per unresolved review, with an `AlertTriangle` icon header showing the PAX8 company name.
- Each stored candidate renders as a row (name + confidence `Badge`, formatted as a rounded percentage) with a "Link company" button that POSTs `{ companyId }` to `/api/pax8/company-matches/[id]/resolve`; on success the card is optimistically removed, `toast.success("Linked to {companyName}")` fires, and the count badge decrements; on failure `toast.error` fires with the server's message.
- Zero-candidate reviews (D-09) render the "No suggested matches" empty state inline and show only the manual picker — no candidate buttons.
- Every review card also gets a manual company-search combobox (shadcn `Command` inside `Popover`), backed by `/api/data/companies-list` fetched exactly once via a guarded `ensureCompaniesLoaded()` and shared across all cards. Selecting a company enables a "Link to selected company" button that calls the same `resolve()` handler used by candidate buttons — so a zero-candidate review resolves purely through manual search, and a review with candidates offers both paths.
- The Needs Review `TabsTrigger` shows a count badge (mirroring `DetailModal`'s existing Time/Notes tab count-badge style) whenever `reviewTotal > 0`.
- Error/loading/empty states reuse the exact `device-link-conflicts` `Alert`(destructive)/`Skeleton`-trio/`Alert`(empty, `CheckCircle2`) pattern with the UI-SPEC copy verbatim.
## Task Commits
Each task was committed atomically:
1. **Task 1: Harden /api/data/companies-list with requireAuth()** - `564be52` (feat)
2. **Task 2: Needs Review tab — review cards + candidate resolve + count badge** - `40efa1d` (feat)
3. **Task 3: Manual company-search combobox fallback (D-05/D-09)** - `13272f9` (feat)
## Files Created/Modified
- `app/api/data/companies-list/route.ts` — added `requireAuth()` gate as the first statement of `GET`
- `app/pax8/page.tsx` — replaced the Plan 04 Needs Review placeholder with the full review-card UI: fetch/state/resolve handlers, amber cards with candidate resolve rows, count badge, manual-search Command/Popover combobox, D-09 zero-candidate empty state
## Decisions Made
- Reviews + companies-list are fetched lazily on first Needs Review tab activation (not on page mount), keeping the Companies-tab-only visit free of an extra request — matches the plan's "(or on mount)" parenthetical loosely but favors the lazier of the two allowed options since it's strictly better for the common case.
- Confidence formatting: raw `TEXT[]` trigram-similarity strings (e.g. `"0.850"`) are parsed and rendered as a rounded percentage badge (`"85%"`) rather than the raw decimal — the plan didn't mandate a specific format and this reads more naturally next to the "Link company" CTA.
- Combobox implemented inline in the review-card `.map()` rather than extracted to a separate component — matches the codebase's low-abstraction convention (CLAUDE.md: "keep functions focused... avoid unnecessary abstraction") and the plan's own phrasing ("In each card render a Popover...").
## Deviations from Plan
None - plan executed exactly as written. All three tasks matched their `<action>` and `<acceptance_criteria>` blocks without needing any Rule 1-4 deviation.
## Issues Encountered
None.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- The Needs Review tab is fully wired: fetch, amber cards, candidate resolve, manual-search fallback, D-09 empty state, count badge, and the auth-hardened `companies-list` data source it depends on.
- Ready for Plan 06's manual verification: resolve an ambiguous review via a candidate button and a zero-candidate review via manual search; confirm cards vanish, toasts fire, and the count badge decrements.
- No blockers for Plan 06.
---
*Phase: 14-pax8-ui-surface*
*Completed: 2026-07-11*
## Self-Check: PASSED
- FOUND: app/pax8/page.tsx
- FOUND: app/api/data/companies-list/route.ts
- FOUND commit: 564be52
- FOUND commit: 40efa1d
- FOUND commit: 13272f9