# Scratchpad — Resume Draft Contract - Plan slug: `resume-draft-contract` - Created: `2026-01-23` ## What This Is Keep a lightweight, continuously-updated log of discoveries and decisions made while implementing this plan. Prefer short bullets. Append new entries as you learn things, and also *update earlier notes* when a decision changes or an open question is resolved. ## Decisions - (2026-01-23) Resume flow starts at Step 1 — user confirmed they want to review from the beginning when resuming a draft. - (2026-01-23) Drafts in separate tab — dedicated "Drafts" tab alongside Templates and Client Contracts. - (2026-01-23) No auto-save — manual save only via "Save Draft" button. - (2026-01-23) Discard with confirmation — explicit "Discard Draft" action with confirmation dialog. ## Discoveries / Constraints - (2026-01-23) Draft functionality already exists at DB level: - `contracts.status` has `'draft'` value - `createClientContractFromWizard` accepts `isDraft` option - `handleSaveDraft()` exists in ContractWizard.tsx (lines 381-420) - `is_active: false` + `status: 'draft'` for draft contracts - (2026-01-23) Key files: - Contract Wizard: `packages/billing/src/components/billing-dashboard/contracts/ContractWizard.tsx` - Contracts List: `packages/billing/src/components/billing-dashboard/contracts/Contracts.tsx` - Wizard Actions: `packages/billing/src/actions/contractWizardActions.ts` - Contract Actions: `packages/billing/src/actions/contractActions.ts` - Contract Model: `packages/billing/src/models/contract.ts` - (2026-01-23) Current wizard state interface `ContractWizardData` already has: - `contract_id?: string` — set when draft is saved - `is_draft?: boolean` — draft indicator - `template_id?: string` — template reference - (2026-01-23) Contracts.tsx has two tabs: Templates and Client Contracts — we'll add a third "Drafts" tab - (2026-01-23) Existing contract update blocks setting status to 'draft' if contract has invoices (contractActions.ts:293-299) - (2026-01-24) Contracts page now has third subtab `drafts`: - Added URL mapping for `subtab=drafts` and a placeholder Drafts tab view in `packages/billing/src/components/billing-dashboard/contracts/Contracts.tsx` - Next: wire Drafts tab to real data/actions + resume/discard UX - (2026-01-24) Draft count badge added to Drafts tab: - Uses existing loaded `clientContracts` and counts `status === 'draft'` - Badge is hidden when count is 0 (will align with later acceptance/test expectations) - (2026-01-24) Added server action `getDraftContracts()`: - Location: `packages/billing/src/actions/contractActions.ts` - Query joins `contracts` + `client_contracts` + `clients` (and template name), filters by tenant + `status='draft'`, orders by `updated_at desc` - (2026-01-24) Drafts tab now renders a Drafts DataTable with Contract Name column: - Draft data fetched via `getDraftContracts()` in `packages/billing/src/components/billing-dashboard/contracts/Contracts.tsx` - Added search input + localized date rendering (will be broken out into checklist items as we complete them) - (2026-01-24) Drafts table includes Client column (`client_name`). - (2026-01-24) Drafts table includes Created column (localized date). - (2026-01-24) Drafts table includes Last Modified column (localized date). - (2026-01-24) Drafts table includes Actions dropdown with Resume/Discard placeholders. - (2026-01-24) Drafts table default-sorts by Last Modified (updated_at desc) and supports header sorting via DataTable. - (2026-01-24) Drafts tab search filters by contract name. - (2026-01-24) Drafts tab search filters by client name. - (2026-01-24) Drafts tab uses standard DataTable pagination + items-per-page controls. - (2026-01-24) Drafts tab shows empty state when there are no draft contracts. - (2026-01-24) Added `getDraftContractForResume(contractId)` to load full wizard data from a draft: - Location: `packages/billing/src/actions/contractWizardActions.ts` - Validates `contracts.status === 'draft'`, then maps contract + client_contract + contract lines/services/configs into `DraftContractWizardData` - (2026-01-24) Resume flow wired from Drafts table: - Drafts Actions → Resume calls `getDraftContractForResume()`, then opens `ContractWizard` with `editingContract` data. - Updated `ContractWizard` to re-initialize state when `open`/`editingContract` change (was previously only using initial props once). - (2026-01-24) Resume always opens wizard on Step 1 (Contract Basics) by resetting `currentStep` to 0 on open. - (2026-01-24) Resumed drafts pre-populate all wizard steps using `getDraftContractForResume()` mapping (lines/services/configs). - (2026-01-24) Wizard now upserts drafts instead of creating duplicates: - `ClientContractWizardSubmission` includes optional `contract_id` - `createClientContractFromWizard()` updates existing draft when `contract_id` is provided (clears old lines/configs + rewrites in a transaction) - (2026-01-24) Completing a resumed draft activates in-place: - `createClientContractFromWizard()` transitions updated draft to `status='active'` / `is_active=true` when called without `isDraft` - (2026-01-24) Discard draft flow now uses a confirmation dialog including contract + client names. - (2026-01-24) Discard confirmation includes Cancel + Discard actions (ConfirmationDialog). - (2026-01-24) Discard confirmation calls `deleteContract(contractId)` and shows toast success/error. - (2026-01-24) Drafts list refreshes after discard by calling `fetchContracts()` (badge count updates too). - (2026-01-24) Draft queries are tenant-scoped: - `getDraftContracts()` and `getDraftContractForResume()` filter by `{ tenant }` - Draft updates via `createClientContractFromWizard()` update path are tenant-scoped - (2026-01-24) Permissions: - Resuming drafts (`getDraftContractForResume`) requires `billing:create` + `billing:update` (unless `E2E_AUTH_BYPASS=true`) - Discarding drafts (`deleteContract`) requires `billing:delete` (unless `E2E_AUTH_BYPASS=true`) ## Commands / Runbooks - Run billing package tests: `npm test -w packages/billing` - Run server dev: `npm run dev -w server` - (2026-01-24) Implemented T052: discard confirmation dialog message includes permanent deletion + cannot-undo warning (unit test). - (2026-01-24) Implemented T053: discard confirmation dialog renders a Cancel button (unit test). - (2026-01-24) Implemented T054: discard confirmation dialog renders a Discard button (unit test). - (2026-01-24) Implemented T055: Cancel closes discard dialog without calling deleteContract (unit test). - (2026-01-24) Implemented T056: Discard confirmation calls deleteContract(contractId) (unit test). - (2026-01-24) Implemented T057: confirming discard removes the draft from Drafts list (unit test via refreshed fetch). - (2026-01-24) Implemented T058: successful discard shows toast.success('Draft discarded') (unit test with mocked react-hot-toast). - (2026-01-24) Implemented T059: discard errors show toast.error(message) (unit test with deleteContract throwing). - (2026-01-24) Implemented T060: discard triggers fetchContracts() refresh (assert getDraftContracts called again). - (2026-01-24) Implemented T061: Drafts badge count clears after discard (unit test by rendering tabs icon). - (2026-01-24) Implemented T062: getDraftContracts tenant filter prevents cross-tenant draft visibility (unit test with configurable tenant). - (2026-01-24) Implemented T063: getDraftContractForResume rejects when user lacks billing:create permission. - (2026-01-24) Implemented T064: deleteContract rejects when user lacks billing:delete permission. - (2026-01-24) Stabilized jsdom test isolation: added `cleanup()` + body scroll-lock/style reset in wizard/drafts tests to prevent cross-file DOM leakage and pointer-events issues when running the full billing test suite. - (2026-01-24) Tests: - Added `packages/billing/tests/contractsTabs.test.ts` to assert the contracts tabs config includes Templates / Client Contracts / Drafts. - Extracted tab label mapping into `packages/billing/src/components/billing-dashboard/contracts/contractsTabs.ts` for unit testing without DOM. - Extended `packages/billing/tests/contractsTabs.test.ts` to assert Drafts label maps to `subtab=drafts`. - Added `getDraftTabBadgeCount()` helper + test to assert Drafts badge appears when count > 0. - Added a test asserting badge behavior as count changes (0 → 1) to represent badge updates after draft creation. - Added a test asserting badge hides when count returns to 0 (represents update after discard). - Badge hidden at 0 is asserted via `getDraftTabBadgeCount(0) === null`. - Added `packages/billing/tests/draftContractsActions.test.ts` to unit test `getDraftContracts()` query constraints (status/tenant/select/order). - (2026-01-24) Added `packages/billing/tests/draftContractsTable.test.tsx` and implemented T012 to assert draft contract names render in the Drafts DataTable (forces DataTable width in jsdom so non-Actions columns are visible). - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T013 to assert client names render in the Drafts DataTable. - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T014 to assert created dates render via `toLocaleDateString()`. - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T015 to assert updated dates render via `toLocaleDateString()`. - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T016 to assert each draft row renders an actions menu trigger. - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T017 to assert the actions menu includes a Resume option. - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T018 to assert the actions menu includes a Discard option. - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T019 to assert Drafts table headers trigger sorting (initial `updated_at desc` then `contract_name asc` on click). - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T020 to assert draft search filters by contract name. - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T021 to assert draft search filters by client name. - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T022 to assert draft search is case-insensitive. - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T023 to assert Drafts table pagination renders (multiple pages when > page size). - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T024 to assert Drafts pagination can navigate to page 2. - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T025 to assert Drafts empty state renders when there are no drafts. - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T026 to assert Drafts empty state messaging prompts saving drafts. - (2026-01-24) Added `packages/billing/tests/draftContractForResumeActions.test.ts` and implemented T027 to unit test `getDraftContractForResume()` returns core wizard data for a draft contract. - (2026-01-24) Refactor to keep unit tests lightweight: - Added `@alga-psa/auth/withAuth` export + Vitest alias so billing actions can import `withAuth` without importing the full `@alga-psa/auth` entrypoint (which pulls in NextAuth/UI modules in source form). - Updated `packages/billing/src/actions/contractActions.ts` + `packages/billing/src/actions/contractWizardActions.ts` to import `withAuth` from `@alga-psa/auth/withAuth`. - In resume tests, mocked `@alga-psa/auth/getCurrentUser` to avoid NextAuth session code paths when loading billing models. - (2026-01-24) Extended `packages/billing/tests/draftContractForResumeActions.test.ts` and implemented T028 to assert contract lines map into wizard service arrays. - (2026-01-24) Extended `packages/billing/tests/draftContractForResumeActions.test.ts` and implemented T029 to assert service configuration fields (rates, unit_of_measure, bucket overlays) map into resume payload. - (2026-01-24) Extended `packages/billing/tests/draftContractForResumeActions.test.ts` and implemented T030 to assert non-draft contracts cannot be resumed. - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T031 to assert clicking Resume opens the wizard dialog (via mocked `ContractWizard`). - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T032 to assert the Resume flow passes draft data into the wizard (contract_id/client_id/contract_name). - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T049 to assert clicking Discard opens the confirmation dialog. - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T050 to assert discard confirmation includes the contract name. - (2026-01-24) Extended `packages/billing/tests/draftContractsTable.test.tsx` and implemented T051 to assert discard confirmation includes the client name. - (2026-01-24) Added `packages/billing/tests/contractWizardResume.test.tsx` and implemented T033 to assert resuming a draft starts the wizard on Step 1 (Contract Basics) without rendering later steps. - (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T034 to assert Contract Basics step receives the draft client selection. - (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T035 to assert Contract Basics step receives the draft contract name. - (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T036 to assert Contract Basics step receives the draft dates. - (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T037 to assert Fixed Fee step receives pre-populated fixed services when navigating forward from basics. - (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T038 to assert Products step receives pre-populated product services when navigating forward. - (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T039 to assert Hourly step receives pre-populated hourly services when navigating forward. - (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T040 to assert Usage step receives pre-populated usage services when navigating forward. - (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T041 to assert Review step receives the full draft payload when navigating forward. - (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T042 to assert Save Draft submits `contract_id` + `{isDraft:true}` when resuming. - (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T043 to assert Save Draft keeps the existing `contract_id` (preventing duplicate creation). - (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T044 to assert Save Draft reports the same `contract_id` via `onComplete`. - (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T045 to assert finishing a resumed draft calls `createClientContractFromWizard` without `{isDraft:true}` (activation path). - (2026-01-24) Extended `packages/billing/tests/contractWizardResume.test.tsx` and implemented T046 to assert finishing a resumed draft does not pass `{isDraft:true}` (meaning `is_active` is set true in the action). - (2026-01-24) Added `packages/billing/tests/contractsActivationFlow.test.tsx` and implemented T047 to assert a resumed/activated contract disappears from Drafts after wizard completion + refresh. - (2026-01-24) Extended `packages/billing/tests/contractsActivationFlow.test.tsx` and implemented T048 to assert activated contracts appear in Client Contracts after switching tabs. ## Links / References - PRD: [./PRD.md](./PRD.md) - Contract Wizard: `packages/billing/src/components/billing-dashboard/contracts/ContractWizard.tsx` - Contracts List: `packages/billing/src/components/billing-dashboard/contracts/Contracts.tsx` - Wizard Actions: `packages/billing/src/actions/contractWizardActions.ts` ## Open Questions - (None currently — key decisions made during planning)