Round #128Score 3.5/5Transport: rail

Three screens kept rebuilding the ticket face until it became TicketStub

Route: /station-outing
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 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:⁠ PageHeader for the app bar, LoyaltySummaryCard for the prominent JRE POINT balance and tier, FilterChips for food, gifts, and cafes, and ListCard for 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, or QrCode. The nearest components, ScanInput and ScanGate, belong to staff and checkout: they ⁠read⁠ a code, the exact opposite of the customer ⁠presenting⁠ one. "⁠The library needs BarcodeCard or PassStub: a value, a format such as Code 128 or QR, a readable label, perforation, and role=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 , the international e-ticket in , 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=img accessible label. Consumers can replace it with a scannable implementation through code.
  • It is the consumer-facing counterpart to ScanInput and ScanGate: present a code with TicketStub, read one with ScanInput. 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 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

  • Close out passenger-facing rail with refunds and delay certificates, including the AmountBreakdown fee and the application flow, then move to the next mode: staff and passenger screens for bus, taxi, or trucking.

The run

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.

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