Import into <matter> — drop your whole case into the Record
~99% of lawyers onboard by importing a years-long case at once. W1 is the front door that must absorb chaos without fear. You open Import from inside a matter, so the matter is the container — no picker, no forced classification. Drop a folder, a ZIP, or a pile of files; everything enters this matter's Evidentiary Record. The "wow" (sort, OCR, annexure-split) lives downstream on the Record — W1 shows upload accountability only.
What shipped
- The sheet —
import-into-matter-sheet.tsx: the matter as a fixed read-only breadcrumb (no selector → the wrong-matter footgun is removed), the §6 Evidentiary-Record warning, a three-bucketSegmentedFiltersource axis, the folder/ZIP/file dropzone, promise + privacy lines, per-file rows, and a Run-in-background footer. ReusesBaseSheet,SegmentedFilter,UploadDropzone,UploadQueueItem,SecondaryButton. - Source buckets —
import-source.ts: Client documents · Opponent documents · Other / not sure (default). This is the SOURCE axis only — a weak declared hint, never privilege or side. Work-product privilege is caught downstream (Blake flags + counsel confirm). - Folder import —
folder-drop.tsrecurses thewebkitGetAsEntrytree so dragging a nested case folder flattens into every contained file;UploadDropzonegains aChoose folderpicker (webkitdirectory). ZIPs ride through as one archive (the backend unpacks them on the Record). - The §6 warning —
evidentiary-record-notice.tsxresolves the legal WORDS (Record, privileged, work-product) through the canonical legal-lexicon module (Story-1854, merged) viauseLegalTerm; the connective sentence is i18n with those labels interpolated. Legal vocabulary stays single-sourced, never i18n. - Ledger mint (wired to the real backend) —
useMatterImportmints the import on selection through the generated clientappCreateDocumentImport→/api/internal/matters/[matterId]/documents/imports→mattersGateway.mintDocumentImport→POST /app/matters/{id}/documents/imports(functions Story-1855, PR #496). The server derivesexpected_countfrom the enumerated items; the response's per-item ids thread back into each file's presign asimportItemId, so the ledger item walks expected → presigned → accounted. Files then stream via the existing presign → S3 PUT → finalize path (per-file progress, Retry, cancel). - Hygiene — the superseded Story-1518
intake-upload-sheet+ story were removed (the new sheet supersedes them and nothing else referenced them).
How uploads wire to the ledger
| Step | Path | Status |
|---|---|---|
| Mint import (expected_count derived server-side) | generated appCreateDocumentImport → /api/internal/matters/{id}/documents/imports → gateway → functions ledger | Live (real endpoint) |
Presign upload (binds importItemId) | server action → presignDocumentUpload | Live |
| Transfer bytes | browser → S3 (direct PUT, progress) | Live |
| Finalize → lands on Record + ledger item accounted | server action → finalizeDocumentUpload | Live |
All upstream calls ride the shared gateway, so traceparent /
correlation propagate automatically (pramaan-docs ADR-104 / ADR-065). No
OTel SDK, manual spans, or ad-hoc headers were added on this surface.
System-boundary status (seam closed)
The ledger endpoints shipped on main (functions Story-1855,
PR #496). This PR is wired to the real backend, not a
fixture (AGENTS.md "done = system boundary"):
- The OpenAPI mirror was synced from
pramaan-functionsorigin/mainandpnpm codegenrun —src/gennow carriesImportCreateRequest/Response,appCreateDocumentImport, and the newpresign.importItemId. - Mint goes through the generated client + the BFF proxy; the request is contract-validated at the proxy (HR-21) and the response in the gateway (
assertContract, HR-21/22). - No stub remains. The provisional local schema and the
document-importsplaceholder route were deleted; the legal-lexicon stub was deleted and the notice now consumes the merged Story-1854 module. - Mint is required (no best-effort fallback): the real mint runs first and owns the completeness denominator. If it fails nothing is enqueued — the failure is surfaced with the
trace_idand counsel retries. (The presign still accepts a nullimportItemIdfor a future loose-file path, but the W1 flow always mints first.) - The W1 source bucket (Client/Opponent/Other) is a client-side display hint for V1 — distinct from the ledger's selection
source_kind; the V1 ledger contract does not persist the declared bucket.
Reconciliation note
The issue AC listed per-file stages "uploaded → scanned → extracting". The
canonical mockup (mockups/W1.md, 2026-06-28) supersedes
that: W1 = upload only — scan/OCR/index move to the Record.
Per repo doctrine (linked mockup > pasted brief), W1 shows transfer
progress only; processing facts belong to the Record / matter-detail page.
Verification
- Storybook story with the four required states — Empty · Uploading · MixedProgress · FailedRetry (
import-into-matter-sheet.stories.tsx); compiles under typecheck + build (visual pass viapnpm storybook). folder-drop.test.tsproves nested-folder recursion flattens; flat-list fallback covered.pnpm typecheck,pnpm lint,pnpm test(935),pnpm build,pnpm check:routes— all green;git diff --checkclean; codegen idempotent against the synced mirror.- i18n: every new non-legal key added to both
messages/en.jsonandmessages/hi.json; legal copy from the lexicon module, not i18n. Tokens only — no color/hex literals.
Files changed
68 file(s), +3552 / -185 lines
| Path | Role | + | - |
|---|---|---|---|
| messages/en.json | Config | +20 | -0 |
| messages/hi.json | Config | +20 | -0 |
| openapi/app-api.json | Config | +517 | -0 |
| openapi/permission-map.json | Config | +16 | -0 |
| src/app/api/internal/matters/[matterId]/documents/imports/route.ts | App route | +83 | -0 |
| src/components/matters/evidentiary-record-notice.tsx | Component | +47 | -0 |
| src/components/matters/import-into-matter-sheet.stories.tsx | Component | +122 | -0 |
| src/components/matters/import-into-matter-sheet.tsx | Component | +123 | -0 |
| src/components/matters/import-sheet-presentation.ts | Component | +42 | -0 |
| src/components/matters/intake-upload-sheet.stories.tsx | Component | +0 | -83 |
| src/components/matters/intake-upload-sheet.tsx | Component | +0 | -83 |
| src/components/matters/live-matter-documents-page.tsx | Component | +10 | -4 |
| src/components/matters/upload-dropzone.tsx | Component | +68 | -12 |
| src/components/matters/use-document-upload-queue.ts | Component | +22 | -3 |
| src/components/matters/use-matter-import.ts | Component | +133 | -0 |
| src/gen/client/appCreateDocumentImport.ts | Generated contract | +73 | -0 |
| src/gen/client/appGetMatterImportCompleteness.ts | Generated contract | +63 | -0 |
| src/gen/client/index.ts | Generated contract | +2 | -0 |
| src/gen/hooks/index.ts | Generated contract | +12 | -0 |
| src/gen/hooks/useAppCreateDocumentImport.ts | Generated contract | +156 | -0 |
| src/gen/hooks/useAppGetMatterImportCompleteness.ts | Generated contract | +131 | -0 |
| src/gen/hooks/useAppGetMatterImportCompletenessSuspense.ts | Generated contract | +133 | -0 |
| src/gen/index.ts | Generated contract | +91 | -0 |
| src/gen/schemas/documentPresignUploadRequest.json | Generated contract | +5 | -0 |
| src/gen/schemas/importCreateRequest.json | Generated contract | +61 | -0 |
| src/gen/schemas/importCreateResponse.json | Generated contract | +88 | -0 |
| src/gen/schemas/importDroppedReason.json | Generated contract | +35 | -0 |
| src/gen/schemas/importItemInput.json | Generated contract | +37 | -0 |
| src/gen/schemas/importItemInputKind.json | Generated contract | +7 | -0 |
| src/gen/schemas/importItemState.json | Generated contract | +15 | -0 |
| src/gen/schemas/importItemView.json | Generated contract | +35 | -0 |
| src/gen/schemas/importSourceKind.json | Generated contract | +7 | -0 |
| src/gen/schemas/importStatus.json | Generated contract | +6 | -0 |
| src/gen/schemas/matterImportCompletenessResponse.json | Generated contract | +106 | -0 |
| src/gen/types/AppCreateDocumentImport.ts | Generated contract | +77 | -0 |
| src/gen/types/AppGetMatterImportCompleteness.ts | Generated contract | +71 | -0 |
| src/gen/types/DocumentPresignUploadRequest.ts | Generated contract | +4 | -0 |
| src/gen/types/ImportCreateRequest.ts | Generated contract | +25 | -0 |
| src/gen/types/ImportCreateResponse.ts | Generated contract | +47 | -0 |
| src/gen/types/ImportDroppedReason.ts | Generated contract | +29 | -0 |
| src/gen/types/ImportItemInput.ts | Generated contract | +38 | -0 |
| src/gen/types/ImportItemInputKind.ts | Generated contract | +13 | -0 |
| src/gen/types/ImportItemState.ts | Generated contract | +20 | -0 |
| src/gen/types/ImportItemView.ts | Generated contract | +29 | -0 |
| src/gen/types/ImportSourceKind.ts | Generated contract | +13 | -0 |
| src/gen/types/ImportStatus.ts | Generated contract | +12 | -0 |
| src/gen/types/MatterImportCompletenessResponse.ts | Generated contract | +65 | -0 |
| src/gen/types/index.ts | Generated contract | +44 | -0 |
| src/gen/zod/appCreateDocumentImportSchema.ts | Generated contract | +96 | -0 |
| src/gen/zod/appGetMatterImportCompletenessSchema.ts | Generated contract | +88 | -0 |
| src/gen/zod/documentPresignUploadRequestSchema.ts | Generated contract | +7 | -0 |
| src/gen/zod/importCreateRequestSchema.ts | Generated contract | +38 | -0 |
| src/gen/zod/importCreateResponseSchema.ts | Generated contract | +44 | -0 |
| src/gen/zod/importDroppedReasonSchema.ts | Generated contract | +30 | -0 |
| src/gen/zod/importItemInputKindSchema.ts | Generated contract | +17 | -0 |
| src/gen/zod/importItemInputSchema.ts | Generated contract | +48 | -0 |
| src/gen/zod/importItemStateSchema.ts | Generated contract | +25 | -0 |
| src/gen/zod/importItemViewSchema.ts | Generated contract | +28 | -0 |
| src/gen/zod/importSourceKindSchema.ts | Generated contract | +17 | -0 |
| src/gen/zod/importStatusSchema.ts | Generated contract | +14 | -0 |
| src/gen/zod/index.ts | Generated contract | +33 | -0 |
| src/gen/zod/matterImportCompletenessResponseSchema.ts | Generated contract | +56 | -0 |
| src/lib/kubb-client.ts | Library | +12 | -0 |
| src/lib/matters/folder-drop.test.ts | Library | +63 | -0 |
| src/lib/matters/folder-drop.ts | Library | +80 | -0 |
| src/lib/matters/gateway.ts | Library | +32 | -0 |
| src/lib/matters/import-source.ts | Library | +33 | -0 |
| src/lib/matters/upload-queue.ts | Library | +18 | -0 |