diff --git a/docs/superpowers/specs/2026-08-06-buyer-tasks-resend-button-design.md b/docs/superpowers/specs/2026-08-06-buyer-tasks-resend-button-design.md new file mode 100644 index 0000000..f4fe79a --- /dev/null +++ b/docs/superpowers/specs/2026-08-06-buyer-tasks-resend-button-design.md @@ -0,0 +1,166 @@ +# Re-Send button on PO Sent tab of buyer-tasks page + +**Date:** 2026-08-06 +**Status:** Approved (brainstorming complete) +**Scope:** Two component files (`buyer-task-group-card.tsx`, `buyer-tasks-page.tsx`) + their test files. No backend change. No new types. + +## Context + +Today the PO Sent tab of `src/app/(app)/orders/tasks/page.tsx` (rendered via `BuyerTasksPage` → `BuyerTaskGroupCard`) shows a single action button per card: + +- **Default**: purple "Mark as ready" — opens the `MarkReadyDialog` to flag the PO group as ready for collection. +- **`poStatus === "mail_failed"`**: red "Resend" — **buggy**: opens the same `MarkReadyDialog` even though the label says "Resend". The operator can't actually resend from the buyer-tasks page today; they have to navigate to the order detail page. + +The user has asked for two things: + +1. Add a **Re-Send** button next to "Mark as ready" on the PO Sent tab. Clicking it opens the existing resend modal so the operator can resend the PO to the same or a different email. +2. Match the Mark-as-ready button's shape but in a different color. Use a **theme token** (the app's `info` token) so the color works across all themes. + +Bundled in the same change: fix the mail-failed "Resend" label to actually open the resend modal instead of the Mark Ready dialog. + +## Approach (chosen) + +**Reuse the existing `SendPoModal`.** It already supports a resend flow: + +- For a PO with `status === "sent"` or `"mail_failed"`, the modal pre-fills `to / cc / bcc / subject / body` from the previously-persisted `email_payload` and shows the previous `email_error` inline. +- The title flips from "Send" to "Resend" and the submit button reads "Resend". +- Submitting POSTs to the same `/api/v1/purchase-orders/[id]/send-email` route the order detail page uses — no new endpoint needed. + +The buyer-tasks page already opens this modal for the `po_draft` step. Wiring it for `po_sent` is a single handler + a prop on the card. + +The Mark-as-ready purple hardcoded color is replaced with the `primary` theme token so both buttons follow the same theme discipline. + +Approaches considered and rejected: + +- *Build a new "Re-Send" modal.* Redundant — the existing one already handles resend. +- *Use a hardcoded teal/Tailwind color.* Violates the theme-token rule the user has set; won't work in dark mode or future themes. +- *Add the button to all three tabs.* Only `po_sent` (and `po_draft` already) make sense — there's no PO to resend in `needs_po`. + +## Architecture + +``` +BuyerTasksPage +├─ state: markReadyFor: BuyerTaskGroup | null (existing — Mark Ready dialog) +├─ state: sendPoFor: BuyerTaskGroup | null (existing — SendPoModal; wired for po_draft) +└─ handleAction(group) + ├─ step === "needs_po" → setBusyGroupId → POST generate-po (existing) + ├─ step === "po_draft" → setSendPoFor(group) (existing) + └─ step === "po_sent" → setMarkReadyFor(group) (existing) + [NEW] handleResend(group) + └─ step === "po_sent" → setSendPoFor(group) (NEW — reuses po_draft flow) +``` + +``` +BuyerTaskGroupCard +└─ For step === "po_sent" only: render TWO buttons + ├─ Left: "Re-Send" → onResend(group) + │ - default: bg-info text-info-foreground hover:bg-info/90 + │ - mail_failed: bg-destructive text-destructive-foreground hover:bg-destructive/90 + └─ Right: "Mark as ready" → onAction(group) + - bg-primary text-primary-foreground hover:bg-primary/90 (was: bg-purple-600) + + For step === "needs_po" / "po_draft": single button (unchanged) +``` + +## Components + +### `BuyerTaskGroupCard` — `src/components/buyer-tasks/buyer-task-group-card.tsx` + +**Prop changes:** +- Add `onResend?: (group: BuyerTaskGroup) => void`. Optional, only used by `po_sent` step. + +**Render change for `po_sent`:** +- Wrap the two buttons in a flex container (`flex items-center gap-2`) so they sit side-by-side, right-aligned next to the supplier info. +- Re-Send button: + - Label: `"Re-Send"` (with hyphen to match the user's exact wording). + - Default style: `bg-info text-info-foreground hover:bg-info/90`. + - When `poStatus === "mail_failed"`: override to `bg-destructive text-destructive-foreground hover:bg-destructive/90` and label becomes `"Resend"` (no hyphen — matches the existing error-state style elsewhere in the app). +- Mark-as-ready button: + - Label: `"Mark as ready"` (existing). + - Style: migrate from hardcoded `bg-purple-600 hover:bg-purple-700 text-white` to themed `bg-primary text-primary-foreground hover:bg-primary/90`. + +**Render change for other steps:** unchanged. + +### `BuyerTasksPage` — `src/components/buyer-tasks/buyer-tasks-page.tsx` + +**New handler `handleResend`:** +```ts +function handleResend(group: BuyerTaskGroup) { + if (group.step !== "po_sent") return; + setSendPoFor(group); +} +``` + +The existing `SendPoModal` render block (`sendPoFor && `) is already wired for both Send (po_draft) and Resend (po_sent) modes — `poStatus` on the group sets the modal's prefill behavior via the stub `purchaseOrder` prop. No change needed to the JSX; we just trigger the same state from a new entry point. + +**Pass `onResend` to `BuyerTaskGroupCard`:** +```tsx + +``` + +### Files NOT changing + +- `src/components/purchase-orders/send-po-modal.tsx` — already handles resend mode; no change. +- `src/app/api/v1/purchase-orders/[id]/send-email/route.ts` — already accepts resends; no change. +- `src/lib/server/buyer-tasks-service.ts` — no change. The `poStatus` field is already on `BuyerTaskGroup`. +- Any PocketBase schema — no change. +- Any backend route — no change. + +## Data flow + +Unchanged. The card → page → existing SendPoModal → existing API route pipeline is reused as-is. The only new path is: + +``` +BuyerTaskGroupCard [Re-Send click] + → BuyerTasksPage.handleResend(group) + → setSendPoFor(group) + → + → POST /api/v1/purchase-orders/[id]/send-email + → (existing pipeline) +``` + +## Error handling + +- `SendPoModal` already surfaces pre-send validation failures (`!res.ok`, `webhookFired === false`) as error toasts. No new error paths. +- The existing `mail_failed` inline banner (`role="alert"`) stays above the items list, visible whether the operator clicks Re-Send or Mark as ready. +- Re-Send doesn't gate on `poStatus === "sent"` — the operator can re-trigger an in-flight send if they want to. The route handles concurrent sends the same way the order-detail flow does. + +## Testing + +### Existing tests to update + +- `src/components/buyer-tasks/buyer-task-group-card.test.tsx` + - Add: `po_sent` renders two buttons (Re-Send + Mark as ready). + - Add: Re-Send button uses `bg-info`; Mark as ready uses `bg-primary` (no more hardcoded purple). + - Add: Re-Send click fires `onResend`; Mark as ready click still fires `onAction`. + - Add: `poStatus === "mail_failed"` Re-Send button uses `bg-destructive` and label becomes `"Resend"`. + - Existing: `po_draft` still renders a single "Send PO" button (no regression). + +- `src/components/buyer-tasks/buyer-tasks-page.test.tsx` + - Add: clicking Re-Send on a `po_sent` card opens `SendPoModal` with the group's PO stub. + - Existing: clicking Mark as ready still opens `MarkReadyDialog` (no regression). + +### Not changing + +- Service-layer / API route tests — the API surface didn't change. + +## Out of scope + +- Migrating other hardcoded colors elsewhere in the app to theme tokens. Only the Mark as ready button is in scope here. +- Adding a Re-Send button to the `needs_po` tab (no PO exists yet). +- A "send to multiple addresses" feature. +- A "schedule send for later" feature. +- A bulk-resend flow. + +## Implementation order + +1. Update `BuyerTaskGroupCard` to accept `onResend` and render two buttons for `po_sent`; migrate the Mark-as-ready color. +2. Update `BuyerTasksPage` to add `handleResend` and pass it to the card. +3. Update the two test files with the new assertions. +4. Run the full test suite + lint + type-check.