Round #44Score 4/5Retail / e-commerce

Three reconciliation screens later, the over/short display became Delta

Route: /register-close
Desktop preview
Mobile preview

Captured at a 375px viewport. Long pages scroll inside the frame.

The screenshots stay in Japanese. Every round is a real screen for a real Japanese industry, so an English re-render would no longer be the screen the agent actually produced.

Write-up

Three reconciliation screens later, the over/short display became Delta

⁠Build log series⁠: I hand a real screen to an AI that has never seen Gunjo (@gunjo/ui) and let it build. Retail screen three: a ⁠register close / daily cash-up⁠ (a denomination-by-denomination cash count, reconciliation against the expected balance, over/short, a breakdown by payment method, and the carry-over float).

Same policy as the last two rounds: three to five screens per industry, spread across the most different jobs I can find. After (selling) and (counting), this round closes: at the end of the day, count the drawer, reconcile it against the expected balance, and close the register. Neither a sales screen nor an inventory screen, but a ⁠cash reconciliation screen⁠.

Result: 4/5

tsc and build passed, the console stayed clear, 375px held up (a handheld or tablet next to the register), the denomination count recalculated the counted cash instantly, over/short rendered with sign, arrow, word, color, and announcement, anything past plus-or-minus ¥1,000 revealed a reason Select and blocked the close, a completed close went read-only, the page had a single h1, and the payment-method breakdown matched total sales.

The components held on a third mirror-opposite screen

Retail #42, the register (selling):  cart, change, yen-or-percent discounts
Retail #43, the stocktake (counting): book stock, counted stock, variance
Retail #44, the close (closing):      denomination count, expected balance, over/short

Three different jobs, and the cross-cutting components fit every one. The agent's notes:

  • EditableDataTable (issue #199): used it, fits well.⁠ A denomination-by-quantity count grid with computed amounts. renderFooterCell gave the "counted cash total" row for free, rowLabel plus the automatic ctx.ariaLabel labeled every quantity cell, and the table-to-card switch meant ⁠zero horizontal scroll at 375px⁠. "The single most effective component on this screen."
  • RevealSection (issue #213): used it, exactly right.⁠ Only when over/short exceeds plus-or-minus ¥1,000 does the reason Select appear through role="region" with an announcement, and the close stays blocked until a reason is chosen.
  • NumberInput: used it⁠ for quantities and the float. min and max, a draft string you can clear and retype, and disabled in the read-only state.
  • CurrencyInput / formatCurrency {signed} (issue #180): used them, the twelfth consecutive round.⁠ Signed yen on over/short and paid-ins and paid-outs (+¥930, -¥1,280). "JPY and ja-JP defaults with zero setup, genuinely good."

The main event: three sightings of over/short, so I built the component

The agent named the over/short display as ⁠the biggest gap⁠:

The missing higher-level component is a ⁠signed difference, an over/short display⁠. Every reconciliation screen (cash over/short, inventory variance, budget variance) needs a signed amount, a state word, an icon decided by the sign, a meaningful tone, and an announcement of all of it. Statistic does not format signed yen for you and cannot carry a state word. So I hand-rolled a VarianceDisplay. A <Variance value={-930} ... /> would be the single most effective addition.

That made ⁠three sightings⁠: profit-and-loss (), stocktake variance (), and the register over/short here. Each time, an agent with no prior knowledge rewrote the same thing by hand (a signed number, a direction arrow, a sign-driven tone, an announced label) because Statistic is a card that does not fit in a cell.

Following ⁠the rule of three (three separate screens, then build it)⁠, I shipped Delta (issue #221, PR #223):

// Profit and loss: default tones (positive = success, negative = destructive)
<Delta value={12500} format={yen} labels={{ positive: "Increase", negative: "Decrease" }} />

// Cash over/short: positive is not necessarily good, so override the tones
<Delta value={-930} format={yen}
  tones={{ positive: "warning", negative: "destructive", zero: "success" }}
  labels={{ positive: "Over", negative: "Short", zero: "Balanced" }} showLabel />
  • The sign of value decides the arrow (up, down, or flat), the tone, and the label. It is a minimal inline <span> component, and unlike Statistic it fits inside a table cell.
  • format defaults to signed, digit-grouped ja-JP. Pass formatCurrency and you get yen.
  • tones can be overridden per sign: profit keeps the green-when-positive default, while cash reconciliation flips an overage to the warning tone. ⁠"Positive means good" is not universal⁠, and that is the crux of reconciliation.
  • labels are always announced (sr-only, or visible with showLabel). ⁠The meaning never rides on color or the arrow alone.⁠
  • Pure display, so it is server-safe, with no client boundary.

Logged, not built (not yet at three)

  • 🟠 ⁠A payment-method breakdown, a summary-table component⁠: the label, amount, and share rows for cash, credit, e-money, QR, and gift certificates, plus a total row, went into a bare <table> by hand. This is issue #178 territory (DescriptionList, a summary list). Review screens keep reconfirming it, and it is approaching the threshold (the transfer confirmation in , the invoice screen in , and here).
  • 🟠 ⁠A cash reconciliation, ledger-summary component⁠: opening float plus cash sales, plus or minus paid-ins and paid-outs, equals expected against counted. That stack of signed label-value rows with an emphasized total was also hand-rolled (ReconRow). Same issue #178 neighborhood.
  • 🟠 ⁠Denomination-count helpers⁠: EditableDataTable is a good base, but the denomination rows, the computed amount column (denomination times quantity), and the counted-total footer get rebuilt every time. POS-specific, first sighting.
  • 🟡 EditableDataTable renders both the desktop table and the mobile cards into the DOM⁠ (hidden with md:), so nine denominations mean eighteen quantity inputs exist. Worth knowing for scripts, tests, and form submission.
  • 🟡 The JSDoc never says ctx.ariaLabel already combines rowLabel with the header, so the agent prefixed it twice (now fixed).

What I took away: "three to five different screens" is a component-building machine

The cross-cutting components (money, grid, reveal) work even when the job is inverted. On top of that, ⁠when the same missing component recurs on three screens, it is not screen glue; it is the signature of an absent component⁠. This round, the signed-difference display crossed that line. Running an industry three to five screens deep with the jobs spread out does two things at once: it gives the industry verdict its depth, and it ⁠fires the rule of three so the design system gains exactly one component⁠. Next, issue #178 (the summary list) is approaching the same threshold.

Next up: build log

  • ⁠One more retail screen⁠ (returns, register open, or a third scan-input sighting for issue #220), or a new industry. I will also watch whether the next clean agent discovers and uses Delta (issue #221).

The run

Gunjo was still in early alpha at the time; issues are welcome.

This series is co-created with AI (Claude and Codex). A human designs the experiments, makes the calls, and fact-checks before publishing; the AI does the hands-on work and drafting.

Components from @gunjo/ui

The components this screen's source imports directly.

The code the agent actually wrote

Click a file to expand its source.

業界別「AI指示書パック」、先行登録を受付中です。