diff --git a/.planning/phases/14-pax8-ui-surface/14-CONTEXT.md b/.planning/phases/14-pax8-ui-surface/14-CONTEXT.md new file mode 100644 index 0000000..1d45f1e --- /dev/null +++ b/.planning/phases/14-pax8-ui-surface/14-CONTEXT.md @@ -0,0 +1,205 @@ +# Phase 14: /pax8 UI Surface - Context + +**Gathered:** 2026-07-11 +**Status:** Ready for planning + + +## Phase Boundary + +A new `/pax8` page where any authenticated manager can see PAX8 companies with +their subscriptions and a cost breakdown, and an admin can resolve flagged or +ambiguous company matches (populated by Phase 12's fuzzy matcher) directly +from that same page — no `psql` required. No new sync logic (Phases 11-12 +already built and schedule it — Phase 13), no changes to the matching +algorithm itself, no PAX8 write access (read-only view + resolve-to-Autotask +metadata only). + + + + +## Implementation Decisions + +User deferred to Claude's judgment on all four gray areas identified. Decisions below are grounded in existing Pulse conventions and Phase 12/13 schema, documented as concrete direction for research/planning — not open questions. + +### Company List & Cost Breakdown Layout +- **D-01:** Main company list uses `components/admin/DataTable.tsx` (the + established `@tanstack/react-table` wrapper used throughout Pulse) rather + than a bespoke card grid — consistent with every other tabular list in the + app, gives sorting/search/pagination for free. +- **D-02:** Per-company cost breakdown is a drill-down via + `components/admin/DetailModal.tsx` (formatted/raw tab pattern), not inline + in the table row — keeps the main list scannable. The formatted tab shows + subscriptions (product name, quantity, billing term) and a cost summary; + raw tab shows the underlying JSON for support/debugging, matching every + other DetailModal usage in the codebase. +- **D-03:** Cost breakdown is grouped **by subscription/product**, not a + single total — matches `pax8_subscriptions`/`pax8_order_items` granularity + and gives managers the "what am I paying for" answer, not just "how much." + Note the schema quirk from Phase 12 research: `pax8_orders.pax8_company_id` + is always NULL (PAX8's `/invoices` API never populates it) — actual + per-company cost data must be joined from `pax8_order_items.pax8_company_id` + and `start_period`/`end_period`, not from `pax8_orders` directly. + +### Review/Resolution Section & Flow +- **D-04:** The flagged/ambiguous review queue is a **section/tab within + `/pax8`** (e.g., "Companies" / "Needs Review" tabs on the same page), not a + separate top-level route — matches the roadmap's explicit "directly from + that page" wording (SC#4), even though the closest existing precedent + (`app/admin/device-link-conflicts/page.tsx`) is a standalone page. Reuse + that page's card-list-with-resolve-action UI pattern, just embedded as a + tab rather than routed separately. +- **D-05:** Resolution UI offers **both**: pick from the top-3 stored + candidates (`pax8_company_match_review.candidate_company_ids` + + `match_confidences`, already computed by Phase 12 — zero extra query cost) + as the primary path, with a manual company search/picker as a fallback — + required regardless for Phase 12's D-03 zero-candidate case (empty + `candidate_company_ids` array), and useful even when candidates exist but + none are correct. +- **D-06:** Resolving writes `resolved_at`, `resolved_by_user_id`, + `resolved_to_company_id`, optional `resolution_note` — mirrors + `device_link_review`'s resolve shape exactly (same columns exist on + `pax8_company_match_review` per Phase 12's schema). This is what makes the + resolution "persist and be respected by future syncs" (SC#4) — Phase 12's + D-05 already scopes re-matching to `resolved_at IS NULL` rows only. + +### Access & Permissions Split +- **D-07:** Viewing `/pax8` (company list + cost breakdown) requires only + `requireAuth()` — any authenticated user, matching the roadmap's "a manager + can open /pax8" framing and how other read-heavy pages (dashboard, tickets) + work today. +- **D-08:** The resolve action (mutating a match) requires + `requirePermission('admin', 'access')` — matching + `/api/admin/device-link-conflicts/[id]/resolve`'s existing pattern exactly. + This intentionally differs from that precedent's GET route (which is + admin-gated end-to-end) because Phase 14's roadmap explicitly splits + "manager views" from "admin resolves" — the read/write permission split is + a deliberate phase-14 decision, not an oversight. +- **Note for the planner:** Phase 13's code review (`13-REVIEW.md`, CR-02) + flagged that the adjacent `POST /api/pax8/sync` route has no role check at + all — a pre-existing gap, out of Phase 13's scope. Phase 14 should NOT + repeat that gap: every new API route this phase adds must have an explicit + `requireAuth()`/`requirePermission()` call, not an implicit "whatever + middleware allows." + +### No-Candidate / Edge-Case Handling +- **D-09:** A review row with an empty `candidate_company_ids` array shows + the same review-section UI with an explicit empty state ("No suggested + matches — search manually") rather than a separate flow or page — the + manual-search fallback from D-05 handles this case naturally, no special- + casing needed beyond the empty-state copy. + +### Claude's Discretion +- Exact tab/section labels, table column set, and DetailModal field layout — + left to the planner/researcher; no specific mockup or wording was given. +- Whether "Needs Review" tab shows a count badge, and sort/filter options on + the main company list — standard implementation details, not discussed. + + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +### Project scope & requirements +- `.planning/PROJECT.md` — Current Milestone: v2.0 PAX8 Integration section +- `.planning/REQUIREMENTS.md` — PAX8-12, PAX8-13, PAX8-14 (this phase's + requirement IDs) +- `.planning/ROADMAP.md` — Phase 14 section (goal, 4 success criteria, + depends on Phase 12) + +### Prior phase foundation this phase builds on +- `migrations/091_pax8_tables.sql` — `pax8_companies`, `pax8_products`, + `pax8_subscriptions`, `pax8_orders`, `pax8_order_items`, + `pax8_company_match_review` — full schema this page reads from +- `migrations/093_pax8_orders_company_matching.sql` — `pax8_companies` + auto-match columns (`autotask_company_id`, `match_confidence`, + `match_method`, `matched_at`) and the critical note that + `pax8_orders.pax8_company_id` is always NULL — cost-per-company data comes + from `pax8_order_items` instead +- `.planning/phases/12-orders-invoices-company-matching/12-CONTEXT.md` — + D-01 through D-05 (match confidence, ambiguous handling, empty-candidate + flagging, top-3 candidate cap, re-scoring rules) — the review queue this + phase surfaces +- `.planning/phases/13-scheduler-admin-toggle/13-REVIEW.md` — CR-02 finding + (missing permission check on `/api/pax8/sync`) — this phase's new routes + must not repeat that gap (see D-08 note above) + +### Existing patterns to follow +- `app/admin/device-link-conflicts/page.tsx` — direct precedent for the + review/resolve UI (list + resolve action, card-based); this phase embeds + the same pattern as a tab/section rather than a standalone route +- `app/api/admin/device-link-conflicts/route.ts` and + `app/api/admin/device-link-conflicts/[id]/resolve/route.ts` — API shape + (list with filters, resolve with `requirePermission('admin', 'access')`) + to mirror for `pax8_company_match_review` +- `components/admin/DataTable.tsx` — table wrapper for the main company list +- `components/admin/DetailModal.tsx` — formatted/raw tab pattern for the + per-company cost breakdown drill-down +- `lib/auth-utils.ts` — `requireAuth()` / `requirePermission()` helpers this + phase's routes must use explicitly (D-07/D-08) + + + + +## Existing Code Insights + +### Reusable Assets +- `components/admin/DataTable.tsx` — sortable/searchable/paginated table, + used as-is for the company list +- `components/admin/DetailModal.tsx` — formatted/raw tabs, used as-is for + the cost-breakdown drill-down +- `postgresClient` singleton — parameterized queries, no ORM, joins across + `pax8_companies` / `pax8_subscriptions` / `pax8_order_items` / + `pax8_company_match_review` + +### Established Patterns +- Admin CRUD pages live at `app/admin//page.tsx` with a matching + `app/api/admin//route.ts` — but this page is NOT admin-only + (D-07/D-08 split), so its route likely lives at `app/pax8/page.tsx` (top + level, alongside `/dashboard`, `/mobile`), not under `/admin` +- `*_match_review` tables (both `device_link_review` and + `pax8_company_match_review`) follow list-with-filter + single-item-resolve + API shape: `GET /api/.../conflicts?source=X` + `POST /api/.../[id]/resolve` +- Toasts via `sonner`, icons via `lucide-react` — standard for the resolve + action's success/error feedback + +### Integration Points +- New `app/pax8/page.tsx` — main page with Companies / Needs Review tabs +- New API routes for: company list + cost data, match-review list, and + resolve action (exact paths left to planner — likely + `app/api/pax8/companies/route.ts`, + `app/api/pax8/company-matches/route.ts` + + `app/api/pax8/company-matches/[id]/resolve/route.ts`) +- `components/navigation/app-navigation.tsx` — needs a new top-level nav + entry for `/pax8` (not nested under Admin, given D-07's broader view + access) + + + + +## Specific Ideas + +No UI mockups or specific behavioral scripts were provided — the nine +numbered decisions above (D-01 through D-09) are the concrete specifics: +DataTable + DetailModal-drill-down layout, embedded review tab (not a +separate page) with dual pick-from-candidates/search resolution, an explicit +manager-view/admin-resolve permission split, and empty-state handling for +the zero-candidate case. + + + + +## Deferred Ideas + +None — discussion stayed within Phase 14's scope. (PAX8 write access, +seat-adjustment actions, and any general natural-language assistant over +this data are already explicitly out of scope per PROJECT.md, not deferred +from this discussion.) + + + +--- + +*Phase: 14-pax8-ui-surface* +*Context gathered: 2026-07-11* diff --git a/.planning/phases/14-pax8-ui-surface/14-DISCUSSION-LOG.md b/.planning/phases/14-pax8-ui-surface/14-DISCUSSION-LOG.md new file mode 100644 index 0000000..06f47ae --- /dev/null +++ b/.planning/phases/14-pax8-ui-surface/14-DISCUSSION-LOG.md @@ -0,0 +1,59 @@ +# Phase 14: /pax8 UI Surface - Discussion Log + +> **Audit trail only.** Do not use as input to planning, research, or execution agents. +> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered. + +**Date:** 2026-07-11 +**Phase:** 14-pax8-ui-surface +**Areas discussed:** Company list & cost breakdown layout, Review/resolution section & flow, Access & permissions split, No-candidate / edge-case handling + +--- + +All four gray areas were presented via a single multiSelect question. The user responded "Use your best judgement" rather than selecting specific areas or answering individually — deferring all four decisions to Claude, grounded in existing codebase precedent. + +## Company List & Cost Breakdown Layout + +| Option | Description | Selected | +|--------|-------------|----------| +| DataTable (list) + DetailModal (cost drill-down) | Reuses `components/admin/DataTable.tsx` + `components/admin/DetailModal.tsx`, matches every other tabular list in Pulse | ✓ (Claude's judgment) | +| Custom card grid | Would be a new visual pattern, not currently used elsewhere | | +| Inline cost breakdown in table rows | Keeps everything in one view but risks cluttering the row | | + +**Notes:** Cost breakdown grouped by subscription/product (D-03), not a single total — matches schema granularity. Flagged the `pax8_orders.pax8_company_id` always-NULL quirk from Phase 12 research; cost data must join through `pax8_order_items` instead. + +## Review/Resolution Section & Flow + +| Option | Description | Selected | +|--------|-------------|----------| +| Embedded tab/section within `/pax8` | Matches roadmap's "directly from that page" wording | ✓ (Claude's judgment) | +| Separate standalone page (device-link-conflicts precedent) | Existing precedent's actual shape, but roadmap explicitly wants it on the same page | | + +**Notes:** Resolution UI offers both pick-from-top-3-candidates (already computed by Phase 12) and manual search fallback (required anyway for the zero-candidate case). + +## Access & Permissions Split + +| Option | Description | Selected | +|--------|-------------|----------| +| View = any authenticated user, Resolve = admin-only | Matches roadmap's explicit "manager views / admin resolves" framing | ✓ (Claude's judgment) | +| Entire page admin-gated | Matches the closest existing precedent (device-link-conflicts) exactly, but contradicts roadmap wording | | + +**Notes:** Explicitly flagged Phase 13's CR-02 code-review finding (missing permission check on `/api/pax8/sync`) as a gap this phase's new routes must not repeat. + +## No-Candidate / Edge-Case Handling + +| Option | Description | Selected | +|--------|-------------|----------| +| Same review UI with an empty state | No special-casing needed beyond copy — manual search fallback already covers it | ✓ (Claude's judgment) | +| Separate flow/page for zero-candidate companies | Unnecessary complexity for a copy-level difference | | + +--- + +## Claude's Discretion + +- Exact tab/section labels, table column set, DetailModal field layout +- Whether a count badge appears on the "Needs Review" tab +- Sort/filter options on the main company list + +## Deferred Ideas + +None — discussion stayed within Phase 14's scope.