Files
red/.planning/phases/16-multi-signer-ui/16-04-PLAN.md
2026-04-03 16:16:07 -06:00

10 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
16-multi-signer-ui 04 execute 3
16-02
16-03
teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx
teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx
true
MSIGN-09
truths artifacts key_links
Multi-signer Sent documents show N/M signed badge in Status column
Single-signer documents show no N/M badge (unchanged)
Fully signed documents show existing Signed badge only (no N/M)
N/M is computed from signingTokens with usedAt IS NOT NULL
path provides contains
teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx Server-side query joining signingTokens for per-signer completion count signingTokens
path provides contains
teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx N/M signed badge rendering in Status column signed
from to via pattern
dashboard/page.tsx query DocumentsTable rows signedCount and totalSigners fields on row data signedCount|totalSigners
from to via pattern
DocumentsTable N/M badge conditional render when totalSigners > 0 and status === Sent totalSigners.*>.*0
Add the N/M signed badge to the dashboard documents table for multi-signer documents per D-11, D-12, D-13.

Purpose: MSIGN-09 (dashboard shows per-signer completion status). Agent sees at a glance how many signers have completed for documents that are partially signed.

Output: Dashboard query includes signing token counts; DocumentsTable renders "N/M signed" badge next to Status for multi-signer Sent documents.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/16-multi-signer-ui/16-CONTEXT.md @.planning/phases/16-multi-signer-ui/16-UI-SPEC.md From schema.ts: ```typescript export const documents = pgTable("documents", { id: text("id").primaryKey(), // ... signers: jsonb("signers").$type(), status: text("status").$type<"Draft" | "Sent" | "Viewed" | "Signed">().default("Draft").notNull(), // ... });

export const signingTokens = pgTable('signing_tokens', { id: text('id').primaryKey(), documentId: text('document_id').notNull().references(() => documents.id, { onDelete: 'cascade' }), signerEmail: text('signer_email'), createdAt: timestamp('created_at').defaultNow().notNull(), expiresAt: timestamp('expires_at').notNull(), usedAt: timestamp('used_at'), });


DocumentsTable current row type:
```typescript
type DocumentRow = {
  id: string;
  name: string;
  clientName: string | null;
  status: "Draft" | "Sent" | "Viewed" | "Signed";
  sentAt: Date | null;
  signedAt: Date | null;
  clientId: string;
};

StatusBadge:

export function StatusBadge({ status }: { status: "Draft" | "Sent" | "Viewed" | "Signed" });

UI-SPEC badge spec:

  • Style: inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-700 ml-1.5
  • Text: "N/M signed"
  • Only for multi-signer (signers non-empty) AND status "Sent"
  • Not for status "Signed" (existing badge sufficient)
Task 1: Add signing token counts to dashboard query - teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx - teressa-copeland-homes/src/lib/db/schema.ts (signingTokens table, documents.signers) - .planning/phases/16-multi-signer-ui/16-CONTEXT.md (D-11, D-12, D-13) - .planning/phases/16-multi-signer-ui/16-UI-SPEC.md (Component Inventory section 5) teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx Import `signingTokens` from `@/lib/db/schema` and `sql, isNotNull, count` from `drizzle-orm`.
The dashboard currently uses a simple select+leftJoin query. To add signer counts, use a subquery approach to avoid N+1:

After the main `allRows` query, compute signing progress for multi-signer documents. Use a separate query for signing token counts to keep it simple:

```typescript
import { signingTokens } from "@/lib/db/schema";
import { sql } from "drizzle-orm";

// After allRows query, fetch signing token progress for all documents in one query
const tokenCounts = await db
  .select({
    documentId: signingTokens.documentId,
    total: sql<number>`count(*)`.as('total'),
    signed: sql<number>`count(${signingTokens.usedAt})`.as('signed'),
  })
  .from(signingTokens)
  .groupBy(signingTokens.documentId);

const tokenMap = new Map(tokenCounts.map(t => [t.documentId, { total: Number(t.total), signed: Number(t.signed) }]));
```

Then augment the rows passed to DocumentsTable:
```typescript
const enrichedRows = filteredRows.map(row => {
  const tc = tokenMap.get(row.id);
  return {
    ...row,
    signedCount: tc?.signed ?? null,
    totalSigners: tc?.total ?? null,
  };
});
```

Pass `enrichedRows` to `<DocumentsTable rows={enrichedRows} ...>` instead of `filteredRows`.

Also need to add `signers` to the allRows select to know which documents are multi-signer:
```typescript
signers: documents.signers,
```
And include it in enrichedRows:
```typescript
hasMultipleSigners: Array.isArray(row.signers) && row.signers.length > 0,
```
cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -30 - `grep -n "signingTokens" teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx` shows import and query - `grep -n "signedCount" teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx` shows field enrichment - `grep -n "totalSigners" teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx` shows field enrichment - `grep -n "hasMultipleSigners" teressa-copeland-homes/src/app/portal/(protected)/dashboard/page.tsx` shows multi-signer detection - `npx tsc --noEmit` passes Dashboard query fetches signing token counts per document and passes enriched rows to DocumentsTable Task 2: Render N/M signed badge in DocumentsTable Status column - teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx - teressa-copeland-homes/src/app/portal/_components/StatusBadge.tsx - .planning/phases/16-multi-signer-ui/16-CONTEXT.md (D-11, D-12, D-13) - .planning/phases/16-multi-signer-ui/16-UI-SPEC.md (Component Inventory section 5, badge spec) teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx Update the `DocumentRow` type to include optional new fields: ```typescript type DocumentRow = { id: string; name: string; clientName: string | null; status: "Draft" | "Sent" | "Viewed" | "Signed"; sentAt: Date | null; signedAt: Date | null; clientId: string; signedCount?: number | null; totalSigners?: number | null; hasMultipleSigners?: boolean; }; ```
In the Status `<td>` cell (currently just `<StatusBadge status={row.status} />`), add the N/M badge after the StatusBadge:

```tsx
<td style={{ padding: "0.875rem 1.5rem" }}>
  <StatusBadge status={row.status} />
  {row.hasMultipleSigners && row.status === 'Sent' && row.totalSigners != null && row.totalSigners > 0 && (
    <span className="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-700 ml-1.5">
      {row.signedCount ?? 0}/{row.totalSigners} signed
    </span>
  )}
</td>
```

Per D-12: Only show for multi-signer documents (`hasMultipleSigners`).
Per D-13: Only show for status "Sent" — not for "Signed" (existing Signed badge sufficient).
The UI-SPEC specifies `bg-blue-50 text-blue-700` for the badge (matching the blue theme of Sent status).
cd /Users/ccopeland/temp/red/teressa-copeland-homes && npx tsc --noEmit 2>&1 | head -30 - `grep -n "signedCount" teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx` shows field in type and render - `grep -n "totalSigners" teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx` shows field in type and render - `grep -n "hasMultipleSigners" teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx` shows conditional - `grep -n "bg-blue-50 text-blue-700" teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx` shows badge styles - `grep -n "ml-1.5" teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx` shows margin from StatusBadge - `grep -n "signed" teressa-copeland-homes/src/app/portal/_components/DocumentsTable.tsx` shows "signed" text in badge - `npx tsc --noEmit` passes - Multi-signer Sent documents show "N/M signed" badge next to Sent status badge per D-11 - Single-signer documents show no N/M badge per D-12 - Signed documents show only existing "Signed" badge per D-13 - Badge uses exact UI-SPEC classes: `inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-blue-50 text-blue-700 ml-1.5` - TypeScript compiles cleanly - `npx tsc --noEmit` passes - Dashboard query includes token count subquery - N/M badge renders only for multi-signer Sent documents - Badge styling matches UI-SPEC exactly

<success_criteria>

  • Dashboard shows "1/2 signed" badge for partially-signed multi-signer documents
  • No badge for single-signer or fully-signed documents
  • Badge computed server-side from signingTokens count
  • TypeScript compiles with no errors </success_criteria>
After completion, create `.planning/phases/16-multi-signer-ui/16-04-SUMMARY.md`