docs(14): capture phase context

This commit is contained in:
lorentz 2026-07-11 11:55:17 -04:00
parent 80771a4d2c
commit 4378fa5ebc
2 changed files with 264 additions and 0 deletions

View file

@ -0,0 +1,205 @@
# Phase 14: /pax8 UI Surface - Context
**Gathered:** 2026-07-11
**Status:** Ready for planning
<domain>
## 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).
</domain>
<decisions>
## 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.
</decisions>
<canonical_refs>
## 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)
</canonical_refs>
<code_context>
## 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/<name>/page.tsx` with a matching
`app/api/admin/<name>/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)
</code_context>
<specifics>
## 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.
</specifics>
<deferred>
## 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.)
</deferred>
---
*Phase: 14-pax8-ui-surface*
*Context gathered: 2026-07-11*

View file

@ -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.