Review context inventory #
Classification: Restricted (describes source code structure).
Every input that changes the review UI. Companion to the Review Kit ADR; the review core’s comments cite it as “inventory §n”.
Purpose: before porting the review to ui-v2/review-kit, know every input that decides what the review shows, allows or computes, so each one has a home in the kit architecture (context field → rule → modifier → model) and nothing is discovered after the port. Built 2026-10-23 from seven code scans of the sources listed under Sources; a bracketed number like [4] names the scan’s area, and its sources are the files to open.
Reading order: §1 is the shape of ReviewContext (what the container must gather). §2 is the catalogue of inputs by area, with the kit mechanism each one maps to. §3 lists the defects and divergences the scans found, which must be decided (fix before port, fix as you port, or carry over) rather than silently ported. §4 is what changes in ENG-9191.
Legend for the “kit” column: ctx = field on ReviewContext; rule = a Specification over ctx (field/column visibility); mod = pipeline modifier that sets flags on the model; adapter = per-type adapter logic; container = stays in the app (fetch, commands, persistence); labels = string the container resolves.
1. ReviewContext: what the container gathers
#
One object, built once per document by the app’s container, passed to buildReviewModel(raw, ctx). The kit never reads any of these sources itself.
interface ReviewContext {
type: ReviewDocumentType; // 6 types; credit notes are their OWN type here (see §3.4)
status: ExtractionStatus; // 18 values [6 §3]
phase: ReviewPhase; // loading | extracting | reprocessing | ready | failed | not_extracted
integration: {
state: 'none' | 'active' | 'disconnected'; // disconnected = exists but not ACTIVE; today treated as none [4]
provider: Provider | 'NON_INTEGRATED'; // 14 accounting providers; unnamed ones resolve to NON_INTEGRATED in rules [4]
capabilities: ReadonlySet<Capability>; // from bonsai-model capabilities(): can_publish*, can_create_*, has_header_level_tags, supports_double_entry_bank_statement, can_publish_attachments … [4 T1]
metadata: IntegrationMetadataFlags; // discounts_enabled, address_for_sale_enabled, deposits_enabled, allow_service_date, use_account_numbers, pays_tax, multi_currency_enabled, country_code, invoice_auto_number_disabled, FreeAgent company fields, Xero short_code + bank_feed_connections, base_currency, default_item_id, book_close_date [4 T2b]
accountingConfig: AccountingConfig | null; // per type: tax_calculation, line_level_tax_type, extraction_level_tax_type, tax_calculation_mode, rounding_method, tax_rounding_method, *_decimal_places, supports_tax_group_override, calculate_groups_by_tax_components, supports_line_amount_types, empty_is_auto_tax [4 T2a]
features: IntegrationFeatures; // the derived "information" flags (isSupportsLineDiscount, requiresContactCode, ignoresContactRole, tax treatments, supportsInvoiceDueDate, …) — tofu-web already has this as domain/integrations/features.ts [4 T3]
};
entity: {
settings: EntitySettingsForReview; // §2.1: double entry per type, tax amount override per type, validate_total_amount, validate_tax_identification_number, is_contact_tin_sync_enabled, hidden_extraction_fields per type, csv_export_target, auto_publish_* , account_type_column_enabled, calculation_decimals, extraction_config, multi_currency_enabled, BS: enable_multi_account, enable_child_transactions, validate_* ×4, bank_statements_enable_double_entry, CN/AR accounting status on publish
typesEnabled: ReadonlySet<ReviewDocumentType>;
tagRoots: TagRoot[]; // header + line roots with level/scope/mandatory/archived [5 §3]
internalAccounting: boolean; // non-integrated entity has internal accounts/tax/items/contacts [4]
};
options: { // picker sources, already filtered by the container (compatibility, archived, date, class) [5 §4]
accounts: PickerOption[]; taxRates: PickerOption[]; salesTaxRates: PickerOption[]; items: PickerOption[]; contacts: PickerOption[]; locations: PickerOption[]; currencies: PickerOption[]; bankAccounts: PickerOption[];
canCreate: { contact; account; taxRate; currency; item; bankAccount }; // permission + capability + pays_tax already combined
};
numbers: { // resolved once, see §2.2
decimals: { line; tax; total; unitPrice; quantity; taxRate };
minDecimals: number; // 2 today, ISO minor units when decided
rounding: RoundingMethod; taxRounding: RoundingMethod; taxMode: 'LINE_BY_LINE' | 'ACCRUED';
locale: string; // display only; parsing stays '.'-decimal until decided (§3.2)
};
permissions: { // §2.6
canRead; canUpdate; canVerify; canDelete; canWriteIntegration; isImpersonating; isAdmin; trialBlocked;
};
document: {
isDoubleEntry: boolean; // per-DOCUMENT flag, not the entity setting [5 §7]
isPublished: boolean; lastExportedAt; externalId; errorPhase;
duplicates: DuplicateMatch[]; pages: { total; read: number[]; held: number[] }; version: 1 | 2 (BS)
};
onboarding?: { // only in webapp onboarding and tofu-web onboarding
lock: null | 'ready' | 'final'; limitReached; unitsLeft; discount: { earned; cap }; demo: boolean;
};
locale: { ui: string; dateFormat: (d) => string; rtl: boolean };
}
Everything else the scans found is either derived inside the pipeline from these (e.g. “Publish hidden because double entry is on and provider is not Money Forward”) or stays in the container (commands, polling, analytics, persistence).
2. Catalogue of inputs by area #
2.1 Entity settings [1] #
No organization setting is read by any review code. Entity inputs that matter:
| Input | Effect today | Types | Kit |
|---|---|---|---|
{ap_bills,ar_invoices,direct_expenses}_enable_double_entry |
Publish hidden when provider can’t publish DE; export gains journal formats; knowledge-panel field lists switch. Form columns follow the document’s is_double_entry, not this setting. |
AP AR DE | ctx.entity.settings + ctx.document.isDoubleEntry → mod applyEntitySettings, rule for debit/credit columns |
bank_statements_enable_double_entry |
BS editor double-entry mode, contra-defaults panel, publish gate; clamped by canBankStatementUseDoubleEntry |
BS | ctx + adapter(BS) |
line_item_tax_amount_override_enabled (+ AR, DE variants) |
Line tax amount editable; off → tooltip; Xero DE always off; no integration → always on | AP AR DE | mod → column.editableToggle |
account_type_column_enabled |
Initial state of Account Type column | AP AR CN DE | mod → column visible |
auto_publish_*_enabled ×6 |
Mark Ready reads “Ready & Publish”; sync progress includes publish | all | mod → footer.primaryLabelVariant |
ap/ar_credit_notes_accounting_status_on_publish, ar_invoices_accounting_invoice_status_on_publish/_on_verify |
Status passed to WASM validator | CN, AR (FreeAgent) | container (validator input) |
{type}_enabled ×6 (+ credit_note feature flag) |
Which types exist in pickers/filters | all | ctx.entity.typesEnabled (container) |
snippet_type, ai_auto_split_type, ai_page_classification_type |
Document-level snippet/reprocess actions, snippet hold | document | container (document panel actions) |
calculation_decimals, extraction_config (per type: tax mode, rounding ×2, decimals ×4) |
Decimals/rounding for non-integrated entities (provider config overrides) | AP AR CN DE | ctx.numbers (resolver, §2.2) |
multi_currency_enabled |
Effectively no effect (bug) | AP AR DE | drop or fix (§3) |
{type}.hidden_extraction_fields |
Hides header fields, line columns, BS columns; required fields never hidden; never applies to credit notes | AP AR DE BS | rule notHidden(field) |
{type}.validate_total_amount (default true) |
Mark Ready blocked on total mismatch; onboarding verdict | AP AR DE (CN→parent) | ctx → mod applyValidationPolicy → check.blocking |
{type}.validate_tax_identification_number |
THE tax-ID mismatch block | AP AR DE CN | ctx → mod → counterparty.taxIdWarning.blocking |
{type}.is_contact_tax_identification_number_sync_enabled |
Writes TIN to contact on verify | AP AR DE CN | container (command side effect) |
{type}.csv_export_target |
Locks export format list (non-integrated) | AP AR DE BS | container (export command) |
BS enable_multi_account, enable_child_transactions, validate_opening_closing_balance / credit_debit_total / subtotal / per_line_balance |
Add/Move account, add child, which Summary rows + per-row status | BS | ctx → adapter(BS) + mod |
auto_ready_*, auto_done_* |
Only edited by the onboarding automation panel | all | out of kit |
Not read by review (safe to ignore): auto_import_*, total_rounding, duplicate_detection_enabled, extract_duplicates_enabled, knowledge toggles, country_id, multi_type_extraction_enabled, expense_claim_enabled, target_accounting_software, all org settings.
2.2 Numbers: decimals, rounding, tax calculation [2] #
| Input | Resolution today | Kit |
|---|---|---|
| Decimal places | One resolver getExtractionDecimalPlaces: (a) currency tax_decimal_places only when no integration; (b) entity extraction_config when integration has no config; (c) integration accounting_config[type]; (d) entity calculation_decimals; (e) 2. Total = line decimals (total_decimal_places never read). Unit price: provider (QBO 7, Xero/Bukku/FreeAgent/Jaz 4, Zoho 5, freee 3, else 2) or max(2, line). Quantity: config or 4 (input fallback 5). Tax rate: 6 (invoice) / 2 (DE). |
ctx.numbers.decimals, computed by one shared resolver moved to a pure lib; kit only formats |
| Minimum shown decimals | CURRENCY_MIN_DECIMAL_PLACES = 2 applied in onboarding sheet + total check, dropped in main-review line inputs, absent in footers/text |
ctx.numbers.minDecimals; presenters always honour it |
| Rounding + tax mode | Integration accounting_config else entity extraction_config; all providers NEAREST, Japan preset ROUND_DOWN; calculator tofu-calculator-core (WASM) |
ctx.numbers; calculator stays in container (adapter changeRows recomputes) |
| Total reconciliation (per-line vs once on 10-dp sum) | Only in _calculateInvoiceTotal and Mark Ready; not initial load/single-cell/DE |
adapter: one path for every recompute |
| External tax (QBO US AR) | tax_calculation = External + external_total_tax; two different formulas exist |
adapter(AR) with the backend formula |
| Line amount type | Stored else EXCLUSIVE; forced NO_TAX (AP/DE, no-tax provider); EXCLUSIVE under reverse charge; selector hidden when supports_line_amount_types false; onboarding ignores all of this |
rule priceBasisAllowed(ctx) → lines.taxMode.options |
| Discounts / deposit / balance due | Line discount AR only (none/Xero/Sage/MYOB, not Sage AP); header discount QBO or none (calc also Xero/Sage/MYOB); deposit QBO; balance due from calculator | rules on ctx.integration.features/metadata |
| Display locale | Hard-coded en-US everywhere |
ctx.numbers.locale (decide: keep en-US or follow UI locale) |
| Typed number parsing | . only; 1.234,50 cut / rejected / dropped depending on surface |
one parser in the kit’s cell editor, behaviour decided once (§3.2) |
| Date parsing | Stored MMM dd, yyyy; ambiguous numeric = MM/DD unless first > 12; invalid → today (9215 cancelled) |
one parser; decision pending (§3.2) |
| BS numbers | Hard-coded 2 dp + NEAREST; Number() loses precision; calcOptions exists but never passed |
adapter(BS) receives ctx.numbers like every other type |
2.3 Validation [3] #
Four points: live form (zod + WASM tofu-validator-core, shared by main review and onboarding), Mark ready (client TS + backend validate_ready, not in WASM), Mark done (onboarding only: total check + blocked dialog), Publish (Rust preflight + connector + bank-feed preflights).
| Input | Kit |
|---|---|
| WASM provider rules (QBO, Xero, Zoho, FreeAgent, AutoCount; Other = common only). Inputs: provider settings from metadata (pays_tax, base currency, multi-currency, QBO default item + book close date, FreeAgent company), cached catalogues, invoice status, today (UK). Skipped when no contact. | container runs it; results enter the model as field.error, cell.error, notices[validation] |
| TS required fields per provider (Xero AR account/tax, QBO no-tax, Zoho AR number) | rule set requiredBy(ctx) → field.required; one source, not three |
| Contra required (double entry) | adapter/mod → cell.error |
| Archived tag refusal on save | container command guard |
| Mark ready client gates: ≥1 line/transaction, total vs recalculated (full calculator), tax ID vs contact (3 variants, Zoho fetch), new contact place of supply, contact creation permission, BS publish confirmation | mod applyValidationPolicy sets footer.primary.blockedBy[]; container shows the dialog variant |
| Onboarding-only: total check blocked dialog (Fix it / Turn off check / Ask admin), validation banner, per-cell errors + prev/next problem, unknown account/tax (non-blocking) | kit presenters (ReviewNotice, LinesCard problem nav) fed by model; the “turn off” action is a command |
Non-blocking warnings: tax ID 3 variants, tax rate not applicable, unknown currency, mandatory tag roots (no rule exists in VC), date parse (silent), duplicates, YAML required (= cannot hide, not a value check) |
model flags; mandatory tag root needs a real rule (§3.3) |
| BS: backend enforces opening/closing + per-line only; publish ignores the four toggles and runs ALL; UI shows credit/debit/subtotal too | adapter(BS) check rows mirror the toggles; note the backend gap |
Publish failures: 400 reasons, connector validate_bonsai first English error saved on the document, Zoho own checks |
model notices[publish_failed]; container maps keys to labels |
2.4 Integration capabilities, config, metadata, provider branches [4] #
- Capabilities read by review:
can_publish(viacanPublishExtraction, but 3 surfaces use the raw flag and skip CN rules),can_publish_credit_note,can_publish_ap_credit_note,can_publish_bank_statement(only for BS double entry; publish list is hard-coded),supports_double_entry_bank_statement(missing from tofu-web schema),can_publish_attachments,can_create_contacts/currencies/accounts/tax_rates/bank_account,has_header_level_tags(knowledge panel only; form uses a provider list),default_invoice_status_on_publish. - Not read:
can_update_contacts,can_sync_contact_tax_identification_number(list used instead),can_create_items/tags,can_sync,is_auto_assign_tags,can_import,can_attach_ar_invoice_files,can_self_prompt_*. - AccountingConfig per type and metadata flags: see §1. Per-provider decimals/tax mode table in [2].
- Provider branches not expressed as flags (must become
featuresor rules before the port): Zoho AR number required; Xero AR account/tax required; QBO ACCPAY no-tax; QBO/UNKNOWN discount section; QBO waits for locations; FreeAgent republish status; Xero dropspayment_type; Xero tax override unsupported; Xero bank-feed UI; per-provider export defaults;canPublishDoubleEntry(Money Forward only);supportsPublishForType(BS list); Xero+DE override off; AutoCount+AP no due date; QBO ARCN credit-note discount; AutoCount payment method; Sage AP no line discount; Xero ignores contact role; Zoho place of supply; validator provider list;getIntegrationInformationderived fields (line discount, items, service header, sales taxes, contact type, contact code (Bukku), DE paid-from (not FreeAgent), Sage addresses, Zoho/FreeAgent tax treatments, TIN sync list); header-tag provider policy; tag level/scope policy (Zoho/Sage, Sage DE none);IFVtoken list (only 5 providers named). - tofu-web already has
domain/integrations/features.tsas the port ofgetIntegrationInformation: this becomes the sharedIntegrationFeaturesfor both apps.
2.5 Field catalog, pickers, tags, priority [5] #
- YAML attributes honoured:
name,display_name(header labels only),extraction_type,required(= cannot hide),supported/unsupported_providers(where the form calls the catalog),requires_metadata_flag(addresses only). Ignored by forms:type,group_name,requires_capability(header tags),requires/excluded_by_entity_settings(forms use per-document flag),requires_accounting_config(read directly),requires_accounting_config_non_null,supported_version(serverv2.versiondecides). Nopriority/hint/aliases/optionsexist. Line column headers come from i18nreview.shared.columns, not YAML. - Fields decided outside YAML: location (shown when list non-empty), header tags (provider policy), AR header discount, due date (AutoCount), tax treatment, deposit, service date, DE paid-from account, AR line discount (MYOB not in YAML).
- Onboarding today:
SECTION_FIELDShas nointegrationsection → location, tax treatment, header tags never render; line sheet has no item/tag/contra/service-date/account-type columns; DE gets a column subset; BS renders the full slot. - Priority fields: star in knowledge panel only; drive hinoki re-extraction. No ordering in forms.
- Tag roots: four different computations (invoice/DE header: integration-only + provider policy + level/scope + mandatory + archived rules; invoice/DE lines:
getVisibleTagColumns+ LINE_ITEM + mandatory; BS2: parentless roots, no level/scope/provider/mandatory filter,deleted_atonly; onboarding: none). → onetagColumnsFor(ctx, level, scope)in the shared lib. - Pickers: account compatibility filter copied ~10×; tax-rate filtering differs per form (invoice: date + class + selectable; DE: none; BS2: purchase+sales merged); Create New gated differently per form (invoice: permission + capability; DE: non-integrated only; BS2: readOnly only; items/locations: none). → container builds
ctx.optionswith one filter set;canCreatecomputed once. - Persisted prefs: items per page (localStorage
ui-stateper key), review split widths, review mode; BS2 column toggle in memory only;ColumnVisibilityMenuhas zero usages. → container. - Double entry: per-document
is_double_entry(set at extraction); AR contra = debit, AP contra = credit; BS contra defaults derived from first transaction, not persisted.
2.6 Flags, permissions, status, trial, locale, layout, concurrency, analytics [6] #
- Feature flags read in review:
credit_note,preprocess_docsonly. Env:NEXT_PUBLIC_BANK_STATEMENT_HELP_URL;?zoom=auto|lens. - Permissions: resource per type (INVOICE / DIRECT_EXPENSE / BANK_STATEMENT / CREDIT_NOTE),
canUpdate / canVerify (INVOICE_VERIFICATION:update) / canDelete,INTEGRATION:writefor inline create, read gates, impersonation denies writes, trial cap denies create/extract, tutorial allows all. tofu-web hascanInEntityinstead; webapp hasuse-permission. → ctx.permissions, computed by each app. - Status machine: 18 extraction statuses; per-status primary action/editable (
extraction-action-map); static statuses lock; page lock; type-change and re-extract preconditions; edit warning for published/exported; onboarding lock kindsready | finalwith 8 final notices; form freeze during Mark done; publish not offered for ERROR/LIMIT_REACHED/VERIFIED. → modapplyDocumentLock+footerderivation from one status table shared by both apps. - Trial/onboarding: LIMIT_REACHED read-only notice; units left; discount loop (1 %/doc, BS 1 %/page, cap 20 %); publish facts (bankFeed, autoPublishes, exportTypes); demo beats; tofie rail (36 line ids); persisted progress. → ctx.onboarding + container.
- Locale: 49 locales, RTL for ar/he/ur (used in two onboarding components only),
useDateFormat, calendar locale. Label volume:review1,485 leaves (89 namespaces),onboarding679 (63 namespaces), plus statuses 103, extractionFields 197, apiToasts 66, common 66 →ReviewKitLabelswill be large; group it by presenter. - Layout: container breakpoints (560/440/400/450/…), viewport mobile 768 / onboarding lg 64rem; review split persisted (webapp) vs in-memory (onboarding); hotkeys (bulk mod+shift+*, viewer q/w/e/r/a/s/d/f/c/v/j, onboarding mod+Enter, sheet keys); zoom lens geometry; page image sources (signed URLs, size map, refetch on focus, tutorial r2Key); rotate debounced 500 ms; snippet drafts in localStorage. →
ReviewWorkspaceprops + container. - Concurrency: 120 s list polling (off in tutorial); staleness detector invalidates detail (no banner in main review); client-only save confirmation (no server optimistic lock); 409 retry on verification; unsaved-changes guard; onboarding save queue (settle 60 ms, timeout 10 s); no websockets. → container.
- Analytics: business events (Datadog RUM + Amplitude) and 147
onboarding_*events with server mirror; the review-emitted ones are listed in [6 §8]. → container, triggered from callbacks; the kit emits nothing.
2.7 Bank statement specifics [7] #
★ items with no invoice analogue (kit decisions in M3.5.6): multi-account panels + Add/Restore + identity/currency buckets + filters; per-account published lock (host currently locks the whole statement once any group is published; backend guard differs); bank-feed Connected badges (Xero-only concept); new-account Accept/Edit (dialog lacks the account number the backend requires); Summary with editable extracted values + extBalance/calcBalance/status columns + 4 validation toggles; per-account contra defaults; Move to Account / Change Currency / child rows; reorderTransactions exists with no UI; no keyboard navigation between cells (move never called); pagination local, virtualised > 100; bulk publish per-account result panel + error banners (map knows only Xero keys); partial read / page billing shown only in onboarding; export runs from the review header (host dialog never opens); 84-key label contract with hard-coded English in 10 places; tofu-web schema lacks supports_double_entry_bank_statement.
3. Defects and divergences to decide before/while porting #
Each of these is a place where porting “as is” would freeze a bug or a fork. Mark each: F fix before port (in webapp, small), P fix as you port (kit does it once), C carry over and ticket later.
Decision (Estu, 2026-10-23): everything is P. Nothing is fixed in webapp first and no separate tickets are opened now. The kit resolves each item once, during the port ticket that touches it; the webapp keeps its current behaviour until it switches to the kit. Items that only the backend can change (marked backend below) are recorded here as known gaps the kit must tolerate, not as work. Header tags follow the Rust capability
has_header_level_tags, not the TS provider list.Where each P lands: §3.1 duplicates and the number helpers → ENG-9191 (
rules.ts,numbers.ts) and ENG-9192/9193; §3.2 surface differences → ENG-9191 contract decides the single behaviour (min decimals 2, typed-number input rejected with a message, price basis gated, tax rate 6 dp, ACTIVE-filtered integration source, header tags by capability, TIN sync by capability,canPublishExtractioneverywhere); §3.3 gaps → the kit follows the backend as it is today and the gap is listed in the parity map with statusbackend; §3.4 modelling → ENG-9191.
3.1 Two or more copies of the same logic (P: the kit keeps exactly one) #
- Total-vs-lines check: Rust, TS mark-ready, zod, onboarding model, ui-v2 schema (ignores toggle).
- Tax ID match: Rust (1 variant), TS (3 variants), ui-v2 copy; two different block dialogs; CN differ client (blocks) vs backend (never checks).
- Required fields: TS utils vs WASM vs form-state; ui-v2 copy lacks Zoho/CN/i18n.
- Tax rate vs account class: 5 copies. Unknown account/tax: 2. Archived tags: 2. ≥1 line: 3.
- WASM adapter, schema refinement, error-tree walkers, BS host, BS calc: webapp vs ui-v2 copies.
- Line column builders ×5, account compatibility filter ×~10, tag roots ×4, tax-rate filters ×4, Create New gating ×4.
- Number helpers in ui-v2:
decimal-places.ts,formatCurrency×2,round×3, line amount type ×6, date suggestion parser, BS calc byte-identical.
3.2 Behaviour that differs by surface (decide once) #
- Min decimals: main review shows
849.6, onboarding849.60, footers none. - Typed numbers:
1.234,50cut (main), rejected (onboarding), dropped (BS). Masks cut instead of round. - Dates: MM/DD assumption, invalid → today (9215 cancelled; ui-v2 has the same parser).
- Price basis: onboarding not gated by
supports_line_amount_types, no-tax forcing, reverse charge, hidden fields. - Tax rate 6 dp (invoice) vs 2 dp (DE). Discount rate rounded to money decimals (12.5 % → 13 % at 0 dp).
- Integration source: total check reads
integrations[0], form reads ACTIVE-filtered hook. - Header tags: Rust capability (Zoho, Bukku, DynamicsBc) vs TS policy (Zoho, Bukku, FreeAgent).
- TIN sync: capability never read; provider list used.
- Credit notes offered Publish via raw
can_publishon 3 surfaces.
3.3 Gaps (C unless cheap) #
total_decimal_placesnever read; freee total 2 vs lines 0.- JS rounding half-away vs backend bankers on the external-tax swap; JS ignores
rounding_method. - Reconciliation not applied on initial load / single-cell edits / DE (backend applies to DE).
- Initial external-tax total formula differs from recalculation and backend.
- AR not forced NO_TAX under no-tax config (footer hides rows, calculator still taxes).
- Header discount for Xero/Sage/MYOB AR in the calc but input hidden.
- Mandatory tag roots: no blocking rule anywhere; only Zoho sets the flag.
- Hidden fields: never for CN; BS2 mapping lacks debit/credit fields; knowledge panel ignores type.
- EC (expense claim) falls to the decimals fallback; backend computes as AP.
- BS: publish ignores validation toggles; backend doesn’t check credit/debit/subtotal; new-account dialog vs backend account-number requirement; host locks whole statement;
supports_double_entry_bank_statementmissing from tofu-web schema; FreeAgent allows DE BS but Publish hidden. - Onboarding: location/tax treatment/header tags never render; DE and BS get no validation banner (sync mounted only in card stack); FreeAgent DE skips WASM (no contact).
- Disconnected integration behaves as non-integrated everywhere except one banner.
- Likely bug:
invoice-validation-utils.ts:186checksexternal_account_item_id(not in schema). - Dead code:
_isInvoiceReferenceEnabled,_isSupportsAccountField, unreachable branchLC:655-665,DIRECT_EXPENSE_QUANTITY_*,ColumnVisibilityMenu(0 usages),reorderTransactions(no caller).
3.4 Modelling decisions the scans force #
- Credit notes are a real type in ctx (config and entity overrides differ per CN), even though the form collapses them to AP/AR in two places.
- Double entry comes from the document, not the entity setting, for columns; the setting only gates publish/export/knowledge.
integration.state = 'disconnected'must exist so the kit can show the banner instead of silently falling back to internal accounting.numbersis resolved once by a shared pure resolver (moved out ofapps/webapp/src/shared/utils/currency.ts), used by webapp, tofu-web and the BS adapter.IntegrationFeatures(tofu-webdomain/integrations/features.ts) becomes the single home for every provider branch in §2.4; rules read it, neverprovider ===.- Labels are grouped per presenter; the full set is ~1,500 review + ~700 onboarding leaves, so
ReviewKitLabelsmust be split (header,verdict,fields,lines,footer,notices,bankStatement,viewer).
Sources #
The seven scans read these files; every claim above traces to one of them. When a row here and the code disagree, the code wins and this page is corrected in the same PR.
| Scan | Area | Sources |
|---|---|---|
| [1] | Entity settings | libs/rust/bonsai-model/src/entity_setting.rs (EntitySetting), libs/rust/bonsai-model/src/entity.rs (extraction_config), webapp shared/lib/api/entities/read.ts |
| [2] | Numbers | webapp shared/utils/currency.ts (getExtractionDecimalPlaces), libs/rust/bonsai-model/src/integration.rs (AccountingConfig, per-provider defaults), libs/rust/tofu-calculator-core |
| [3] | Validation | libs/rust/tofu-validator-core/src, libs/rust/tofu-validator-wasm/src, webapp features/review/components/extracted-data/form/__tests__/wasm-validation-mapping.test.ts, apps/bonsapi/src/service (validate_ready), libs/rust/bonsai-integration/src/accounting (validate_bonsai) |
| [4] | Integration capabilities, config, metadata, provider branches | libs/rust/bonsai-model/src/integration.rs (IntegrationProvider::capabilities(), AccountingConfig, the *PublicMetadata structs), webapp shared/utils/integration.ts, shared/utils/integration-extraction-types.ts, shared/utils/integration-publish.ts, shared/lib/fields/integration-field-visibility.ts |
| [5] | Field catalog, pickers, tags | config/extraction_fields/*.yaml, webapp shared/lib/fields/integration-field-visibility.ts, shared/sync/accounting.ts, features/review/components/extracted-data/form/invoice/invoice-basic-information.tsx, .../invoice-lines/line-columns.tsx, .../line-table.tsx, .../double-entry/sections.tsx, the direct-expense form and transaction table |
| [6] | Flags, permissions, status, trial, locale, layout | config/features.yaml, webapp features/review/utils/extraction-action-map.ts, features/review/utils/status.ts, features/onboarding/publish.ts, features/onboarding/review-lock.ts, shared/lib/features |
| [7] | Bank statement | libs/typescript/ui-v2/src/components/bank-statement*, webapp features/review/components/extracted-data/bank-statement-v2, features/review/hooks/bank-statement-v2, config/extraction_fields/bank_statement.yaml, libs/rust/bonsai-model/src/integration.rs (supports_double_entry_bank_statement) |
4. What this changes in ENG-9191 (contract ticket) #
model.tsgainsReviewContext(shape in §1) andIntegrationFeaturesis lifted to a shared package; the brief §3.2 is updated accordingly.rules.ts: the five YAML rule kinds +notHidden+priceBasisAllowed+requiredBy+tagColumnsFor, each a pure(ctx) => …, with the provider-branch list in §2.4 as the backlog of rules to express.numbers.ts: the decimals/rounding resolver, one function, tested against the per-provider table in [2].- The file-by-file UI inventory (attached to ENG-9191) adds a column “ctx field” for every file it lists, so each webapp file is mapped to the context input it reads.
- Parity map: rows 95+ for the inputs in §2 that are not features (settings, numbers, validation points), so approval covers them too.
- §3 above is the agenda for one decision session (Estu + Koki + Ryota): F / P / C per line, written back into this file.