Review Kit #
Classification: Restricted (describes source code structure).
Status: accepted (ENG-9191, 2026-10-22). Owners: Estu (kit, onboarding), Koki (invoice review,
SheetGrid), Ryota (bank statement).
Context #
The document review exists twice. Webapp onboarding 1.5
(apps/webapp/src/features/onboarding/components/extraction/**) has the AP bill review people
use today. @bonsai/ui-v2 holds a port of it from 5 October, which nobody imports. The two have
drifted apart by dozens of commits. Direct expense and bank statement embed the main app’s forms
with no cards, and credit notes were never exercised. Meanwhile tofu-web’s product review page
renders a third UI, ui-v2 extraction-review, for invoice types only.
Decision #
- The review core lives in
@bonsai/utils/review; the presenters in@bonsai/ui-v2/components/review-kit. The core is TypeScript with no React: theReviewContexttype (the container builds it from its sources), the rules, the number resolver, the pipeline and its modifiers, theReviewModelcontract and the write-side types (ReviewEdit,ReviewIssue,ReviewViewState,ReviewTransport). An app’sdomain/may import it; ui-v2 re-exports it beside the presenters. The presenters are presenters only: props in, callbacks out. No@/imports, store, translator, query client, fetch or tracking. - Apps hold containers; the core holds one
ReviewTypeSpecper type. Each type’s spec (toModel,apply,toPayload) lives in the core; an app’s container feeds the kit’sonEditinto the review session and gives it aReviewTransport(its own mutations or commands). Loading, commands and anything React-shaped stay in the container, never indomain/. - No app imports another app’s review components. Webapp and tofu-web share the kit, never each other’s code.
- One kit for every type. AP bill, AR invoice, direct expense, bank statement and both credit notes differ only in the model their type spec builds. A bank statement has one section per account, and every other type has exactly one.
- Port, don’t redesign. A presenter moved from webapp must look the same as on
main. The jest baseline incomponents/extraction/__tests__/baseline/stays green without snapshot updates through M3.5.1–3. New design goes only where today has none (DE, BS and CN cards, the per-account tabs).
Data flow #
flowchart LR
B[bonsapi] --> C["container (webapp / tofu-web)"]
C --> M["toReviewModel.<type>"]
M --> R[ReviewModel]
R --> K["review kit (ui-v2)"]
K -- "onEdit" --> S["review session (core)"]
S -- "ReviewTransport (app)" --> B
The container also resolves every string. The model carries finished labels, and the kit’s own
copy arrives as ReviewKitLabels, because ui-v2 has no translator. The pipeline’s catalog
visibility step filters fields by provider, integration capability and entity settings; the
container supplies the catalog (config/extraction_fields/*.yaml) and the context it reads.
Consequences #
- Webapp’s
components/extraction/shrinks to containers (stores, publish dialogs, review lock, tracking). The ui-v2 copies from 5 October that the kit replaces are deleted in M3.5.4. - A change to a kit presenter must pass the stories for all six types, not only the AP bill.
- DE, BS and CN gain the AP bill’s verdict, zoom lens, keyboard navigation, sheet and Mark done by getting a type spec, not new components.
- Data the model needs but bonsapi lacks (a page number per bank transaction, bounding boxes for DE fields) becomes a backend ticket. The kit never derives it.
References #
- Plan and per-file inventory: ENG-9191 and its child tickets ENG-9192 to ENG-9200 (milestone “M3.5: Review kit in ui-v2 and document-type parity”).
- Feature parity map (94 features, 179 fields, each with its kit mechanism and an approval column): linked from ENG-9191.
- Contract:
libs/typescript/utils/src/review/(core) andlibs/typescript/ui-v2/src/components/review-kit/README.md(presenters).