The Record learns to count pages, not just files
The Documents Register already aggregates each slice value and each group header into a small summary: how many files and how big (count + total size). But the question counsel actually asks of a bundle is "how many pages am I about to read?" โ a 3-file slice can be 600 pages, and a 40-file slice can be 80. This Story adds page totals alongside count and size in both the slice-navigator and the group headers, and a new Total pages option to the View field-sum control.
๐ฐ๏ธ Satellite โ why pages, and why "known so far"
Files and bytes are a proxy for effort; pages are the real unit of
legal reading. The register groups and slices the whole corpus (by type, by
tag, by Side, by participant), and every bucket already carries a summary.
The missing number is pages. The wrinkle: a document's page count is unknown
until it is segmented โ fresh uploads sit in a processing state with
pageCount === null. We must not block the total
on a slow pipeline, nor invent a page count. So the rule is:
totals are pages known so far โ a still-processing doc
contributes 0 to the aggregate, and the row itself shows
โ until it is segmented.
โ๏ธ Airplane โ one aggregate, three surfaces, one toggle
The pure engine (view-state.ts) already folds every group and
slice bucket into a Summary. We taught that fold one more field
and let every existing surface read it:
-
The engine โ
SummaryandSliceOptiongaintotalPages; the singleaddToSummary()reducer (and the slice-bucket loop) sumdoc.pageCount ?? 0. One place, every bucket. -
The slice navigator โ the per-value pill now renders
N pageswhen the field-sum mode ispages. -
The group header โ its summary string renders the page
total when the mode is
pages. - The View field-sum control โ a new Total pages choice sits between Total size and Count + size, and round-trips through the saved view.
๐ฌ Microscope โ the reducer is the whole change
The fold that makes "known so far" true (view-state.ts)
function addToSummary(summary: Summary, doc: RegisterDocument): void {
summary.count += 1;
summary.totalSize += doc.sizeBytes ?? 0;
// Pages are unknown until a doc is segmented; null contributes 0, so the
// total is "pages known so far" (the row itself shows "โ").
summary.totalPages += doc.pageCount ?? 0;
}
Because every group, every slice value, and the corpus-wide "All" row all flow through this one reducer (and the parallel slice-bucket loop), the new number is correct everywhere at once โ no surface re-implements counting.
The field-sum modes gain one member
export type FieldSumMode = "count" | "size" | "pages" | "both" | "none";
// presentation.ts โ summaryParts() now returns a third piece:
case "pages":
return { count: null, size: null, pages: summary.totalPages };
The components localise the page label through next-intl
(DocumentsRegister.summary.pages, an ICU plural) โ never a
hardcoded word โ and the control label
(DocumentsRegister.fieldSum.pages โ "Total pages") ships in both
en and hi.
What you can do now
| Surface | Before | After (mode = Total pages) |
|---|---|---|
| Slice navigator value | 5 15.4 MB | 142 pages |
| Group header | Bank statement ยท 2 ยท 1.0 MB | Bank statement ยท 32 pages |
| View โ Field sum | Count ยท Total size ยท Count + size ยท None | Count ยท Total size ยท Total pages ยท Count + size ยท None |
pageCount === null) adds nothing to
the total and shows โ in its own row โ the summary is honest
about what is known, and grows as the pipeline indexes more docs.
Acceptance
- โ Page totals in the slice-navigator per-value summary and the group-header summary.
- โ Total pages option in the View field-sum control; round-trips through the saved view.
- โ Processing docs show
โ; totals = pages known so far (null โ 0). - โ Wired into the engine where count/size already aggregate โ one reducer, every bucket.
- โ i18n en + hi for "Total pages" and the page-count plural; semantic tokens only.
- โ Storybook:
PagesOnlystories on the slice navigator and group header. - โ
pnpm lint ยท test (628) ยท tsc ยท buildgreen.
Files changed
13 file(s), +77 / -30 lines
| Path | Role | + | - |
|---|---|---|---|
| messages/en.json | Config | +2 | -0 |
| messages/hi.json | Config | +2 | -0 |
| src/components/documents/register/presentation.ts | Component | +10 | -6 |
| src/components/documents/register/register-group-header.stories.tsx | Component | +3 | -2 |
| src/components/documents/register/register-group-header.tsx | Component | +6 | -2 |
| src/components/documents/register/register-slice-navigator.stories.tsx | Component | +4 | -1 |
| src/components/documents/register/register-slice-navigator.tsx | Component | +9 | -9 |
| src/components/documents/register/view-popover/view-sub-panel.tsx | Component | +1 | -1 |
| src/lib/documents/register/pagination.test.ts | Library | +3 | -3 |
| src/lib/documents/register/saved-view.ts | Library | +1 | -1 |
| src/lib/documents/register/types.ts | Library | +6 | -2 |
| src/lib/documents/register/view-state.test.ts | Library | +22 | -1 |
| src/lib/documents/register/view-state.ts | Library | +8 | -2 |