Skip to content

BUG — WOPC shows a quantity/unit ("xN" + unit, or literal "unit") on line items

Symptom (owner, 2026-06-16)

"Upon creating the WOPC, quantity unit are being shown." A quantity/unit appears on WOPC line items where the owner doesn't expect it. A WOPC is a contractor flat-fee payment confirmation, not a unit-priced invoice.

Root cause (8-agent workflow + adversarial verify, 2026-06-16)

TWO INDEPENDENT surfaces put a quantity/unit on the rendered WOPC. The live PDF/preview renderer is lib/paymentConfirmation/PaymentConfirmation.tsx (via lib/wopc/pdfRenderer.server.ts). The sibling lib/pdfTemplates/paymentConfirmation.tsx is DEAD (zero importers) — do NOT edit it.

Surface 1 — the "multiplier" column (THIS is the create-WOPC flow the owner describes). - PaymentConfirmation.tsx:888-889 reads itemSnapshot.quantity / quantityUnit; lines 913-918 render x{quantity} whenever quantity > 1 (gate at line 913), plus the unit string when quantityUnit is truthy. The column header is blank (line 845), so a stray unit reads as noise. - itemSnapshot is populated by the manual create flow: PaymentConfirmationForm.tsx:962-969 (full invoice) and :998-1005 (single item) copy quantity + quantityUnit VERBATIM from the source project invoice; CreateWOPCModal.tsx ships them into create-pending.ts:130 unchanged. The Manual-Entry tab sets quantity (default 1) but no unit → a bare x2. - So creating a WOPC from any invoice line with quantity>1 shows x{n} (+ a real unit if the invoice had one). The gate is quantity > 1, NOT "a real unit exists".

Surface 2 — the description subtitle (auto-match project payouts ONLY, not the manual create flow). - lib/accounting/projectWopcAuto.ts:59-61 appends ${quantity} × ${quantityUnit?.trim() || 'unit'} to the line description when quantity>1, with the LITERAL placeholder 'unit' (line 60) when the source item has no unit. Rendered as a subtitle (PaymentConfirmation.tsx:896-903). projectWopcAuto never sets itemSnapshot, so the auto path only ever hits this subtitle, never the multiplier column. - The literal word "unit" can ONLY appear via this path. coachingWopcAuto.ts emits no quantity/unit at all; composition.server.ts draws only images (no text).

Adversarial note: the initial hypothesis (blame the auto-builder only, clear the create UI) was REFUTED 2-of-3 — the manual create flow is a genuine second surface, so a fix touching only projectWopcAuto would leave the create-flow symptom unfixed.

Fix options (NEEDS OWNER PRODUCT DECISION before implementing)

Decision: should a contractor WOPC EVER show quantity/unit, or never (flat-fee mirror)? 1. Renderer guard (decisive; covers all stored WOPCs without data rewrite): PaymentConfirmation.tsx:913 — tighten the gate from quantity && quantity > 1 to require a real unit (&& quantityUnit), and/or drop the unit span entirely if WOPCs should never show units. 2. Kill the placeholder leak (honor-system "no hardcoded formatting"): projectWopcAuto.ts:60 — remove || 'unit'; only append the × unit line when a real unit exists. 3. (Optional) stop inheriting client-invoice quantity at the source: PaymentConfirmationForm.tsx:966-967 / :1002-1003 — confirm first; this changes historical display semantics. Per never-silently-fix-data, fix render + new-build only; do not retro-rewrite stored snapshots beyond scripts/backfill-wopc-quantity-unit.ts.

Recommended: (1) + (2). Confirm the product rule first.

Resolution (owner chose "only show a REAL unit", 2026-06-16)

FIXED + verified (tsc clean, 6/6 new unit tests, end-to-end render check all-pass; zero net-new lint): - Extracted pure rules to lib/paymentConfirmation/quantityUnit.tsresolveWopcQuantityUnit(raw) (trim; empty/whitespace/non-string → '') and shouldShowWopcMultiplier(quantity, unit) (quantity is number > 1 AND a real unit). Pure so it's unit-testable without rendering the jsx:preserve component. - PaymentConfirmation.tsx (multiplier column) now uses those helpers → a bare x2 with no unit is suppressed; x2 song still shows for a genuine unit; notes fallback in the column preserved. - projectWopcAuto.ts:59-61 — removed the literal 'unit' fallback; the N × unit description line now only appears when a real unit exists (honor-system: no hardcoded placeholder). - Regression test: tests/lib/paymentConfirmation/quantityUnit.test.ts (6 cases). - Render path is server-side (Puppeteer) so not browser-previewable without auth + a qty>1 WOPC; verified via direct renderToStaticMarkup of the live component instead. No data rewrite (render + new-build only, per never-silently-fix-data); stored snapshots already covered by scripts/backfill-wopc-quantity-unit.ts.

Log

  • 2026-06-16 created from owner report + 8-agent workflow. Root cause adversarially verified (initial "auto-builder only" hypothesis REFUTED 2/3 — manual create flow is a real second surface).
  • 2026-06-16 owner chose "only real unit"; implemented + verified; DONE.
  • 2026-06-16 PROMOTED to origin/main d5f8d873 (cherry-pick onto 337da79b, fast-forward push) per owner. eop production tracks main → fix goes live on deploy. NOT on origin/nightly (nightly is behind main; the fix will arrive there on a future main→nightly sync). See [[feedback_honor_system_design]].