26 file(s), +1090 / -1069 lines
| Path | Role | + | - |
|---|---|---|---|
| messages/en.json | Config | +24 | -2 |
| messages/hi.json | Config | +24 | -2 |
| src/app/[locale]/(workspace)/matters/[matterId]/layout.tsx | App route | +13 | -1 |
| src/app/[locale]/(workspace)/matters/[matterId]/record/page.tsx | App route | +0 | -14 |
| src/app/[locale]/(workspace)/matters/actions.ts | App route | +37 | -0 |
| src/components/matter-detail/matter-right-rail.tsx | Component | +6 | -1 |
| src/components/matter-detail/rail-icon-strip.tsx | Component | +2 | -1 |
| src/components/matters/evidentiary-record-notice.tsx | Component | +0 | -47 |
| src/components/matters/import-into-matter-sheet-v2.stories.tsx | Component | +182 | -0 |
| src/components/matters/import-into-matter-sheet-v2.tsx | Component | +447 | -0 |
| src/components/matters/import-into-matter-sheet.stories.tsx | Component | +0 | -122 |
| src/components/matters/import-into-matter-sheet.tsx | Component | +0 | -123 |
| src/components/matters/import-sheet-presentation.ts | Component | +0 | -42 |
| src/components/matters/live-matter-documents-page.tsx | Component | +19 | -115 |
| src/components/matters/matter-intake-drawer.tsx | Component | +75 | -0 |
| src/components/matters/matter-shell-chrome.tsx | Component | +31 | -3 |
| src/components/matters/upload-dropzone.stories.tsx | Component | +0 | -29 |
| src/components/matters/upload-dropzone.tsx | Component | +0 | -143 |
| src/components/matters/upload-queue-item.stories.tsx | Component | +0 | -101 |
| src/components/matters/upload-queue-item.tsx | Component | +0 | -107 |
| src/components/matters/upload-status-pill.stories.tsx | Component | +0 | -43 |
| src/components/matters/upload-status-pill.tsx | Component | +0 | -39 |
| src/components/matters/use-document-upload-queue.ts | Component | +14 | -1 |
| src/components/matters/use-matter-import-v2.ts | Component | +191 | -0 |
| src/components/matters/use-matter-import.ts | Component | +0 | -133 |
| src/lib/matters/gateway.ts | Library | +25 | -0 |
The founder-approved Storybook prototype (proto/import-drawer-v2,
2026-07-07) is now the production Import drawer. It replaces the W1 sheet
(Story-1858): instead of a passive dropzone + a "source" segmented filter,
counsel must first arm whose documents these are — one tap
that recolours the zone and pre-stamps every dropped file's side.
Classification becomes unavoidable and effortless. The prototype was lifted
verbatim (no redesign); this story wires it to the real upload machinery,
internationalises every string, moves it onto the Matter Action Rail so it
opens on any matter page, and fixes two rail defects.
import-into-matter-sheet-v2.tsx keeps the approved visuals,
tokens and motion; all inline copy now resolves through
useTranslations("Matters.import") / "Common"
(new keys added to both en and
hi). Armed-source flow exactly as prototyped: dropzone inert
until Client's / Opponent's / Other is picked; zone recolours;
drop pre-stamps the side; per-row chip cycles/corrects it; file-type
icons per extension (lucide mapping).
use-matter-import-v2.ts reuses the existing
presign → S3 → finalize queue (use-document-upload-queue,
upload-client) and the real import-ledger mint (functions
Story-1855), but takes the side per drop (not a global bucket).
The queue's UploadItem statuses map onto the drawer's
journey rows honestly:
queued/uploading → uploading,
processing → reading,
done → registered,
failed/rejected → failed. Registered rows auto-clear ~6s
after landing; the footer is a trust line + a background spinner —
no button (close = X + outside click).
droppable is false, so
no file is ever taken (client-side guarantee today). Server enforcement
lands with functions Story-2110.
matter-shell-chrome.tsx (MatterIntakeDrawer).
The Action Rail's Intake (☁️) icon opens it on Brief / Record / Filings /
Sessions — any matter page. Per the founder's scope amendment the Record
page's "Upload document" button was removed entirely: the
rail cloud is the only way in, so the open-state is plain local state in
the shell (the earlier cross-tree matter-intake-store is gone
— no sibling trigger left to bridge). The controller lives with the
drawer, so uploads keep running in the background while it is closed or
the page is navigated; a registered upload reconciles the Record register
via router.refresh() (the Record body re-syncs its rows from
the refreshed server prop, React's adjust-state-during-render pattern).
RailIconStrip / MatterRightRail dropped the
activeId="settings" default. Nothing is highlighted unless a
panel is genuinely open; the shell passes
activeId={intakeOpen ? "intake" : undefined}, so the cloud
icon lights only while the drawer is up.
min-h-full (it grew with content, so AppChrome's scroll area
scrolled the whole shell, rail included). It is now
h-full min-h-0 overflow-hidden — it fills AppChrome's
already-viewport-bounded scroll area and clips itself, confining scroll to
the content <main>. Ribbon + Worknav + rail stay
stationary. h-full (parent is bounded by AppChrome's
h-screen), not h-dvh or
position hacks — no double scrollbar; Brief/Record bodies still scroll.
import-into-matter-sheet.tsx and its exclusive machinery
(use-matter-import, import-sheet-presentation,
upload-dropzone, upload-queue-item,
upload-status-pill, evidentiary-record-notice,
all with their stories) had no other importer — deleted (delete-safety
grep over src/ + tests/ + relative imports).
functions Story-2110 (fn#577) merged mid-build and its contract synced to
the mirror (Chore-2112), so declared_side is now wired
end-to-end — no seam:
ImportItemInput.declaredSide (DeclaredSide =
client|opponent|other, identical to the drawer's
SourceSideV2). The backend persists it on the ledger item;
the document row inherits it at presign (a privileged item can never be
presigned, so privileged material can't land).
matters:write-gated PATCH
/app/matters/{id}/documents/{doc_id}/declared-side through a
server action (setDocumentDeclaredSideAction →
mattersGateway.setDocumentDeclaredSide, HR-21
assertContract on the DocumentListItem reply).
The update is optimistic — the chip flips instantly and rolls back with a
toast if the PATCH fails. Before a file registers (no documentId
yet) the mint declaration already stands, so the chip updates locally and
the PATCH engages once the row lands.
No pnpm codegen was run locally; the branch was rebased onto
the Chore-2112 codegen-sync main so the generated client is the
committed mirror.
Matters/Import/ImportIntoMatterSheetV2 → Empty — dropzone disabled: "Select a source to begin".… → ArmedClient — "Client's" picked, zone recolours green, invites the drop.… → Privileged — the protective refusal; nothing uploads.… → ReadOnly — Bug-1614 parity: sources disabled, nothing droppable (new canUpload gate).… → Importing — each file on its own journey (dots uploaded → reading → registered), per-row side chip.… → AllRegistered — everything landed; rows auto-clear (~6s).… → FromTheRail — the ☁️ rail icon opens the drawer on any matter page.pnpm lint — clean (one pre-existing <img> warning in add-matter, untouched).pnpm typecheck — clean.pnpm test — 163 files / 1014 tests green.pnpm build — green; pnpm check:routes — surface registry fresh (37 surfaces).main, so the OpenAPI-mirror freshness gate is satisfied.src/ or tests/.
Live-data seam: the drawer runs the same presign → S3 →
finalize path the Record page always used; the journey rows are driven by
real queue statuses (uploading/processing/done/failed). The end-to-end
staging run against a real matter — including the empty first-run Record —
is the component-boundary → system-boundary check to confirm on
stg.app.pramaan.io, and declared_side persistence
is gated on functions Story-2110 landing in the mirror.