Skip to content

hir-94: per-recipient queue table on campaign detail#21

Open
jaredzwick wants to merge 2 commits intopypesdev:mainfrom
jaredzwick:hir-94/campaign-queue-detail
Open

hir-94: per-recipient queue table on campaign detail#21
jaredzwick wants to merge 2 commits intopypesdev:mainfrom
jaredzwick:hir-94/campaign-queue-detail

Conversation

@jaredzwick
Copy link
Copy Markdown
Collaborator

The campaign detail page surfaced aggregate counts but a user could
not see which specific recipients were pending, sent, failed, or
bounced. The most common post-create question — "did my emails
actually go out?" — required a database query to answer.

  • GET /api/campaigns/:id/queue: paginated per-recipient queue rows.
    Owner-checked (loads the campaign first to verify userId), then
    delegates to the existing getQueueEntriesByCampaign helper. Limit
    defaults 100, max 500; offset honored. Returns the columns a UI
    table needs (status / attempts / scheduled / sent / errorMessage)
    and omits raw send_log internals.
  • /dashboard/campaigns/:id detail page: new "Recipients" section
    renders the queue rows in a table with status badge, attempts
    N/M, last-activity relative time, and a one-line error summary.
  • src/lib/queueRowSummary.ts: pure helpers (queue-status badge
    label/tone, summarizeError that trims to a single line + ellipsis,
    formatRelativeTime that takes a fixed now for testability).

29 vitest specs in tests/int/queueRowSummary.int.spec.ts cover every
known + unknown queue status, error truncation (empty / multi-line /
CRLF / over-max with rounded ellipsis / trailing-whitespace strip /
custom max), and relative-time formatting (just-now band, past +
future across m/h/d/w with rounding, ISO string input, NaN/null
handling).

Stacked on hir-94/campaign-list-detail (PR-pending). No schema, no
migrations, no send-pipeline changes. tsc clean. test:int 139
passed (only pre-existing PAYLOAD_SECRET api.int.spec.ts fails).

Co-Authored-By: Paperclip noreply@paperclip.ing

Note: built on top of #19 (campaign-list-detail). Will rebase / become diffable on its own once #19 lands.

🤖 Generated with Claude Code

jaredzwick and others added 2 commits May 3, 2026 02:14
A user could create a campaign but had no way to see it afterward —
no listing, no detail view, no status, no sent/open/click counts. The
GET /api/campaigns and GET /api/campaigns/:id endpoints already
existed; only the UI was missing.

- /dashboard/campaigns: lists campaigns with status badge, sent
  progress, reply count, created date. Empty-state CTA links to
  /dashboard/campaigns/new.
- /dashboard/campaigns/[id]: shows status, send progress, opens /
  clicks / replies / bounces / unsubscribes / total recipients, plus
  optional queue stats (pending / sent / failed / bounced) when the
  API returns them. Includes a delete action with confirm prompt.
- /dashboard/campaigns/new: on successful POST, router.push to the
  new campaign's detail page so the user lands somewhere meaningful.
- src/lib/campaignStatus.ts: pure helpers (status badge label/tone,
  sent progress formatter with clamp + percent rounding) so render
  logic is unit-testable without mounting React.

15 vitest specs in tests/int/campaignStatus.int.spec.ts cover all
known + unknown statuses, null/undefined/empty inputs, and the
formatter's clamp / NaN / zero-total / rounding paths.

No API route, schema, migration, or send-pipeline changes. tsc clean
(error count went from 10 pre-existing on main to 0 — earlier merged
PRs cleared them). Full test:int matches main baseline (only
api.int.spec.ts still fails on missing PAYLOAD_SECRET, pre-existing).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
The campaign detail page surfaced aggregate counts but a user could
not see which specific recipients were pending, sent, failed, or
bounced. The most common post-create question — "did my emails
actually go out?" — required a database query to answer.

- GET /api/campaigns/:id/queue: paginated per-recipient queue rows.
  Owner-checked (loads the campaign first to verify userId), then
  delegates to the existing getQueueEntriesByCampaign helper. Limit
  defaults 100, max 500; offset honored. Returns the columns a UI
  table needs (status / attempts / scheduled / sent / errorMessage)
  and omits raw send_log internals.
- /dashboard/campaigns/:id detail page: new "Recipients" section
  renders the queue rows in a table with status badge, attempts
  N/M, last-activity relative time, and a one-line error summary.
- src/lib/queueRowSummary.ts: pure helpers (queue-status badge
  label/tone, summarizeError that trims to a single line + ellipsis,
  formatRelativeTime that takes a fixed `now` for testability).

29 vitest specs in tests/int/queueRowSummary.int.spec.ts cover every
known + unknown queue status, error truncation (empty / multi-line /
CRLF / over-max with rounded ellipsis / trailing-whitespace strip /
custom max), and relative-time formatting (just-now band, past +
future across m/h/d/w with rounding, ISO string input, NaN/null
handling).

Stacked on hir-94/campaign-list-detail (PR-pending). No schema, no
migrations, no send-pipeline changes. tsc clean. test:int 139
passed (only pre-existing PAYLOAD_SECRET api.int.spec.ts fails).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant