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.ts —
resolveWopcQuantityUnit(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]].