Three screens kept rebuilding the ticket face until it became TicketStub
/station-outingCaptured 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 screens kept rebuilding the ticket face until it became TicketStub
Build log series: I give an AI that has never seen Gunjo (
@gunjo/ui) only the public npm package and the gunjo.jp docs, then ask it to build a real screen. This deeper passenger-facing rail round covered station shopping and outing suggestions: recommended places, stores, available coupons, and a digital membership card.
Result: 3.5/5, and the ticket face finally settled
The screen passed tsc and fit a 375px mobile-first layout. The agent reported:
Four leading components felt purpose-built for this screen:
PageHeaderfor the app bar,LoyaltySummaryCardfor the prominent JRE POINT balance and tier,FilterChipsfor food, gifts, and cafes, andListCardfor both stores and coupons, with the whole card acting as a button at least 44px tall. The framework took almost no effort. But the other side of the transaction, the pass a customer holds up at the register, had no component.
All four passenger-facing foundations were independently discovered on the same screen. The central finding lay elsewhere.
The core observation: the ticket face settled after wandering across three screens
The digital membership pass and coupon face were completely hand-rolled. This was the biggest gap. There was no
TicketStub,PassCard,BarcodeCard,CouponStub, orQrCode. The nearest components,ScanInputandScanGate, belong to staff and checkout: they read a code, the exact opposite of the customer presenting one. "The library needsBarcodeCardorPassStub: a value, a format such as Code 128 or QR, a readable label, perforation, androle=img. It is the only addition this screen needs. Everyone currently reinvents a barcode, often by sending an<img>request to a server."
The ticket face had wandered across three screens in the series: the boarding pass in #114, the international e-ticket in #123, and the station coupon or membership pass here. Three agents had hand-rolled it independently. The rule of three fired, so I built it.
What I built: TicketStub
// Coupon with a Code 128 barcode
<TicketStub value="CPN-NEWDAYS-3X-887412" codeLabel="887412">
<div className="flex items-center justify-between">
<span className="font-semibold">Triple points at NewDays</span>
<Badge variant="warning">Limited time</Badge>
</div>
<p className="mt-1 text-xs text-muted-foreground">Valid at participating NewDays stores through June 30, 2026</p>
</TicketStub>
// Boarding pass with QR, route, seat, and gate
<TicketStub value="NH106-X7K2QM-18K" format="qr" codeLabel="X7K2QM">{/* content */}</TicketStub>
value,format(Code 128 or QR), a readable label, perforated notches, and a content slot above for a flight route and seat, a coupon discount, or a membership tier.- The built-in code is a deterministic visual placeholder based on a seeded FNV value. It is safe for server rendering, avoids server and browser mismatches, and puts the real value in the
role=imgaccessible label. Consumers can replace it with a scannable implementation throughcode. - It is the consumer-facing counterpart to
ScanInputandScanGate: present a code withTicketStub, read one withScanInput. The by-use-case catalog now makes that distinction. - PR#377 verified a 327-cell QR and 69-bar Code 128 placeholder at 375px, the real value in
role=img, the perforation, the format toggle, no overflow, and no console errors.
What I built under src this round: TicketStub, on its third case and as the eleventh component produced by the series.
What I took away: the other side of the transaction is another component
Gunjo already has ScanInput and ScanGate for staff to read a code. Customers need the other direction: a code they can present. One concept, a barcode, becomes two components when staff and customers face opposite directions. This mirrors the split in #126 between RouteStops and Itinerary: vocabulary from one domain does not make the interaction the same. Deeper passenger coverage exposed the reverse side of a staff-facing component that had been present from the start. That asymmetry was exactly why the series set out to cover staff and passenger screens for every mode.
📊 Component scoreboard (11 built)
AmountBreakdown / ActionQueue / ListCard / Gantt-segments / SeatMap / LoyaltySummaryCard / RadioCard / FilterChips / PageHeader / Itinerary / TicketStub In progress: StatusLevel, SegmentedControl, a success variant on StatusScreen, an intraday Gantt mode, and AmenityRow.
📋 Passenger-facing backlog
- ✈️ Air: all six screens complete ✅
- 🚆 Rail: commuter pass ✅ station guide ✅ tickets ✅ station recommendations ✅ / remaining: refunds and delay certificates
Next up: build log #129
- Close out passenger-facing rail with refunds and delay certificates, including the
AmountBreakdownfee and the application flow, then move to the next mode: staff and passenger screens for bus, taxi, or trucking.
The run
- gunjo.jp / TicketStub / LoyaltySummaryCard / npm
@gunjo/ui/ GitHub / previous rounds #1 through #127 - GunjoUI by UIXHERO
After wandering across three screens, the ticket face settled as the other side of a component the staff toolkit had from the beginning. Reading and presenting the same barcode need different components.
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.