--- phase: 07-audit-trail-and-download plan: "02" type: execute wave: 2 depends_on: - "07-01" files_modified: - teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/page.tsx - teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx - teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx - teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx autonomous: true requirements: - SIGN-07 - LEGAL-03 must_haves: truths: - "Agent sees a Download button on the document detail page when document status is Signed" - "Clicking the Download button triggers browser PDF download dialog (no login prompt, no 404)" - "Download button is absent when document status is Draft, Sent, or Viewed" - "Dashboard table shows a Date Signed column populated for Signed documents" - "Dashboard StatusBadge shows Signed for documents that have completed signing" artifacts: - path: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/page.tsx" provides: "Server component that generates agentDownloadUrl and passes it to PreparePanel" contains: "createAgentDownloadToken" - path: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx" provides: "Download button rendered only when currentStatus === Signed and agentDownloadUrl is non-null" contains: "agentDownloadUrl" - path: "teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx" provides: "DocumentRow type with signedAt field + Date Signed column in table" contains: "signedAt" - path: "teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx" provides: "Select includes signedAt from documents table" contains: "signedAt: documents.signedAt" key_links: - from: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/page.tsx" to: "teressa-copeland-homes/src/lib/signing/token.ts" via: "import { createAgentDownloadToken } from '@/lib/signing/token'" pattern: "createAgentDownloadToken" - from: "teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/page.tsx" to: "PreparePanel" via: "agentDownloadUrl prop" pattern: "agentDownloadUrl" - from: "teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx" to: "DocumentsTable" via: "rows prop including signedAt field" pattern: "signedAt" --- Wire the agent-facing download UI: generate a presigned download URL in the document detail server component and pass it to PreparePanel for Signed documents; add signedAt to the dashboard table. Purpose: Complete SIGN-07 (agent can download signed PDF) by surfacing the API from Plan 01 in the portal UI. Satisfies LEGAL-03 by ensuring the only download path is the presigned token route — no direct file URLs anywhere in the UI. Output: Four modified files — document detail page (token generation), PreparePanel (Download button), DocumentsTable (signedAt type + column), dashboard page (signedAt select). @/Users/ccopeland/.claude/get-shit-done/workflows/execute-plan.md @/Users/ccopeland/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/07-audit-trail-and-download/07-RESEARCH.md @.planning/phases/07-audit-trail-and-download/07-01-SUMMARY.md ```typescript // Current: fetches doc + docClient, renders PdfViewerWrapper + PreparePanel // Phase 7 change: generate agentDownloadUrl server-side for Signed docs and pass as prop export default async function DocumentPage({ params }: { params: Promise<{ docId: string }> }) { // ...existing auth, db fetch... // ADD after fetching doc: const agentDownloadUrl = doc.signedFilePath ? `/api/documents/${docId}/download?adt=${await createAgentDownloadToken(docId)}` : null; // MODIFY PreparePanel call to pass new props: } ``` ```typescript // Current interface: interface PreparePanelProps { docId: string; defaultEmail: string; clientName: string; currentStatus: string; } // Phase 7 change: extend interface, add Download button section for Signed status // CRITICAL: Token generation must NOT happen in PreparePanel — PreparePanel is 'use client' // PreparePanel only renders an anchor — it does not call createAgentDownloadToken // Current non-Draft status return (replace with status-aware rendering): if (currentStatus !== 'Draft') { return (
Document status is {currentStatus} — preparation is only available for Draft documents.
); } // Phase 7: When currentStatus === 'Signed' and agentDownloadUrl !== null, show download section instead // When currentStatus === 'Sent' or 'Viewed', keep the read-only message (no download button) ``` ```typescript type DocumentRow = { id: string; name: string; clientName: string | null; status: "Draft" | "Sent" | "Viewed" | "Signed"; sentAt: Date | null; clientId: string; // ADD: signedAt: Date | null; }; ``` ```typescript const allRows = await db .select({ id: documents.id, name: documents.name, status: documents.status, sentAt: documents.sentAt, clientName: clients.name, clientId: documents.clientId, // ADD: signedAt: documents.signedAt, }) .from(documents) // ... ``` ```typescript status: documentStatusEnum("status").notNull().default("Draft"), signedFilePath: text("signed_file_path"), // null until signed signedAt: timestamp("signed_at"), // null until signed ``` ```typescript // From teressa-copeland-homes/src/lib/signing/token.ts (after Plan 01): export async function createAgentDownloadToken(documentId: string): Promise // Returns a JWT with purpose:'agent-download', 5-min TTL // Used in server component only — import forbidden in 'use client' files ```
Task 1: Update PreparePanel props interface and add Download button for Signed status teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/_components/PreparePanel.tsx Two changes to PreparePanel.tsx: **1. Extend PreparePanelProps interface:** ```typescript interface PreparePanelProps { docId: string; defaultEmail: string; clientName: string; currentStatus: string; agentDownloadUrl?: string | null; // ADD signedAt?: Date | null; // ADD } ``` **2. Replace the non-Draft early return block with status-aware rendering.** Currently the function returns a generic message for any non-Draft status. Replace with: ```typescript // For Signed status: show download section with optional date if (currentStatus === 'Signed') { return (

Document Signed

{signedAt && (

Signed on{' '} {new Date(signedAt).toLocaleString('en-US', { timeZone: 'America/Denver', month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: '2-digit', })}

)} {agentDownloadUrl ? (
Download Signed PDF ) : (

Signed PDF not available.

)}
); } // For Sent/Viewed: keep existing read-only message if (currentStatus !== 'Draft') { return (
Document status is {currentStatus} — preparation is only available for Draft documents.
); } ``` Important: The Download button is a plain `` anchor — no fetch(), no onClick handler. The browser follows the link directly, which triggers the Content-Disposition: attachment response from the API route. Do not destructure `agentDownloadUrl` or `signedAt` from props in the function signature until you have updated the interface. Update interface first. cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20 PreparePanel accepts agentDownloadUrl and signedAt props; TypeScript passes; Signed status renders download section; Sent/Viewed status renders read-only message; Draft status renders full prepare form unchanged Task 2: Wire document detail page, update dashboard table for signedAt teressa-copeland-homes/src/app/portal/(protected)/documents/[docId]/page.tsx teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx **File 1: documents/[docId]/page.tsx** Add import for createAgentDownloadToken at the top: ```typescript import { createAgentDownloadToken } from '@/lib/signing/token'; ``` After the existing `const [doc, docClient] = await Promise.all([...])` block, add: ```typescript // Generate agent download URL server-side for Signed documents // Must be done here (server component) — PreparePanel is 'use client' and cannot call createAgentDownloadToken const agentDownloadUrl = doc.signedFilePath ? `/api/documents/${docId}/download?adt=${await createAgentDownloadToken(docId)}` : null; ``` Pass the two new props to PreparePanel: ```tsx ``` Note: `doc.signedAt` is available on the document object — it's a column in the documents table (timestamp("signed_at")). --- **File 2: DocumentsTable.tsx** Add `signedAt: Date | null` to the DocumentRow type: ```typescript type DocumentRow = { id: string; name: string; clientName: string | null; status: "Draft" | "Sent" | "Viewed" | "Signed"; sentAt: Date | null; signedAt: Date | null; // ADD clientId: string; }; ``` Add a "Date Signed" column header after the "Date Sent" ``: ```tsx Date Signed ``` Add a "Date Signed" `` in the row map after the "Date Sent" cell: ```tsx {row.signedAt ? new Date(row.signedAt).toLocaleDateString("en-US", { timeZone: "America/Denver", month: "short", day: "numeric", year: "numeric", }) : "—"} ``` --- **File 3: dashboard/page.tsx** Add `signedAt: documents.signedAt` to the select object: ```typescript const allRows = await db .select({ id: documents.id, name: documents.name, status: documents.status, sentAt: documents.sentAt, signedAt: documents.signedAt, // ADD clientName: clients.name, clientId: documents.clientId, }) .from(documents) .leftJoin(clients, eq(documents.clientId, clients.id)) .orderBy(desc(documents.createdAt)); ``` The `allRows` inferred type will now include `signedAt: Date | null`, which matches the updated DocumentRow type in DocumentsTable.tsx. cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -20 && npm run build 2>&1 | tail -10 tsc --noEmit passes with no errors; npm run build passes cleanly; DocumentsTable accepts rows with signedAt field; Dashboard query selects signedAt; Document detail page imports and calls createAgentDownloadToken for signed docs; PreparePanel receives agentDownloadUrl and signedAt props 1. `npx tsc --noEmit` passes — no type errors across all four modified files 2. `npm run build` completes successfully 3. PreparePanel renders three distinct states: Draft (prepare form), Sent/Viewed (read-only message), Signed (green panel with download link) 4. agentDownloadUrl is generated in the server component (page.tsx), not in PreparePanel 5. DocumentsTable has Date Signed column 6. Dashboard query includes signedAt in select - Agent can navigate to a Signed document detail page and see a green "Document Signed" panel with signed timestamp and "Download Signed PDF" anchor link - Download button is absent for Draft/Sent/Viewed documents - Dashboard table shows "Date Signed" column with date for Signed documents, "—" for others - Build passes with no TypeScript errors After completion, create `.planning/phases/07-audit-trail-and-download/07-02-SUMMARY.md`