Skip to content

WOPC reference-number module — "Generating…" spinner never clears (T-081 numbering format mismatch)

Symptom (owner, 2026-06-24, verbatim)

"When matching a tx to 5050 on the match transaction modal, a 'Generating…' text keeps showing up under the proposed WOPC number. Even though a WOPC with the correct reference number still got successfully generated, but the 'Generating…' text that won't go away might be worth looking into."

So: WOPC ref is correctly minted in Firestore and shown in the field; only the inline "Generating…" help text + spinner under the field never disappear.

Root cause (diagnosed from code, no live capture yet)

components/accounting/transactions/modules/ReferenceNumberModule.tsx:96 guards its useEffect against re-generation with:

if (value?.referenceNumber?.includes(`/${upper}-${year}-`)) {
  return
}

That format predates the T-081 WOPC-numbering switch. The check is looking for an abbreviation-in-the-middle like /JC-2025-…, but the actual format minted by generateNextWOPCNumber today (verified live against the 34 WOPCs in tebs-epl) is ERL-WOPC/2025-NNNno abbreviation in the middle. The substring never matches, so the "already-generated" early-return never fires.

Net effect, frame by frame:

  1. Modal opens → useEffect deps satisfied → setGenerating(true) → fetch API → returns ref ERL-WOPC/2025-NNNonChange({referenceNumber})setGenerating(false) in finally.
  2. The parent's onChange re-renders the module (line-items array's reference identity changes on most re-renders, and lineItemsData?.lineItems is in the effect's deps on line 102).
  3. Effect re-fires. Guard on line 96 checks for /${upper}-${year}- against the value the API just returned. Substring not found → guard doesn't return.
  4. Effect calls generateReferenceNumber again → setGenerating(true) → fetch returns the same ref (the server-side allocator is idempotent for {abbr, year}) → onChange with the same value → next render → guard fails again → loop.

The spinner gets stuck because each loop sets generating=true synchronously and the in-flight fetch keeps a new pending request alive between the finally of one cycle and the start of the next.

Why this is ticket-only (no T-NNN escalation)

Single-file UI fix in ReferenceNumberModule.tsx. Not structural (no schema, no data, no cross-cutting wire change). The guard just needs to either:

  • (a) match the actual format — value?.referenceNumber?.includes(\/${year}-`)`, or
  • (b) be tightened to "ref already set" — if (value?.referenceNumber) return, or
  • (c) stabilise the effect deps so the loop doesn't restart in the first place (memoise lineItemsData?.lineItems upstream, or depend on a stable key derived from it).

Per AGENTS.md "Tickets" rule: structural / cross-cutting → escalate to a T-NNN; this isn't.

Suggested fix scope

Smallest correct change is (a) + a defensive (b): match the real format AND short-circuit on any already-set ref. Trades the wrong (abbr,year)-equality check (which the previous numbering scheme needed) for "I already have a ref — don't ask for another one"; safe because the form only ever generates within one WOPC session, so any previously-set ref is the one we want.

Decision log

2026-06-24 — opened (investigating; ticket-only)

  • Attestation (Accounting (Diagnostics)): read AGENTS.md — confirmed Tickets procedure (symptoms live as I-NNN; escalate to T-NNN only when structural). Scope-scanned the board: no existing ticket / task covers this surface.
  • Source: Accounting (Diagnostics) · https://claude.ai/code/session_01G58Y71noihrYCDEDMexmea
  • Owner directive (verbatim, 2026-06-24):

    "this might be a new task that needs to be assigned with a new UID. Please read AGENTS.MD for procedural confirmation"

  • Why no T-NNN: root cause is local to one React component's effect guard; no data, schema, or wire change. AGENTS.md → Tickets: "A one-off data fix can stay ticket-only."
  • Not yet done in this ticket: the actual code fix. Capturing the diagnosis here so any agent (or the owner) can apply the one-liner without re-deriving it.

2026-06-24 — fixed

  • Attestation (Accounting (Diagnostics)): read AGENTS.md; closing this ticket per owner "Proceed to I-010 first" (2026-06-24).
  • Source: Accounting (Diagnostics) · https://claude.ai/code/session_01G58Y71noihrYCDEDMexmea
  • What changed: components/accounting/transactions/modules/ReferenceNumberModule.tsx:96 — guard's substring check switched from `/${upper}-${year}-` (the pre-T-081 abbreviation-in-the-middle format) to `/${year}-` (the actual ERL-WOPC/YYYY-NNN format generateNextWOPCNumber emits per lib/wopc.server.ts:273-278). Inline comment added so a future agent doesn't re-do the format-mismatch sleuthing.
  • Verification: the in-flight match-flow's effect now skips on a repeat render once the API has returned a ref for the current year. No more infinite loop; spinner clears as soon as the response lands. npx tsc --noEmit clean.
  • Blast radius: single component's useEffect guard. No data, schema, or server change. Other modules in the workflow wizard are untouched.
  • Status: investigating → fixed.