Round #145Score 4.5/5Transport: taxi

A prop that passed type checks was silently discarded at runtime

Route: /taxi-incidents
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

A prop that passed type checks was silently discarded at runtime

⁠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 fifth staff-facing taxi round⁠, after dispatch, drivers, revenue, and fleet management, covered ⁠complaints, incidents, and safety⁠: case handling, response workflows, incident-free KPIs, and report confirmation.

Result: 4.5/5. Case management fit another settled foundation

The desktop-density screen passed tsc. The agent reported:

⁠Gunjo was purpose-built rather than adapted for an administrative case-management console.⁠ The two hardest pieces, a case lifecycle and a report that locks after signature, each had a dedicated component and introduced almost no friction.

Previous taxi screens had tested operations, roll call, analytics, and maintenance. This round introduced another family, the support-desk lifecycle of complaints and incidents.

The first core observation: ApprovalWorkflow fit the case lifecycle

The flow ran from intake through fact-finding, response (driver coaching, apology, or compensation), prevention, and completion. The agent found:

ApprovalWorkflow fit perfectly with no hand-rolling.⁠ Its docstring names case management, claims and applications, internal requests, expenses, and any staged administrative review. Controlled value and onChange, return to an earlier stage with a reason and rolled-back history, terminal rejection, and an actor, time, and comment per stage define a support or complaint workflow. RouteStops, Stepper, and Timeline have no actors, timestamps, comments, next, return, or reject action per stage.

A component named for approval also covered the lifecycle of a general case. This clarified another boundary: RouteStops tracks state, while ApprovalWorkflow handles staged review with returns.

The second core observation: a prop that lied through the types

The agent hit a real defect:

getRowState on ActionDataTable was a trap.⁠ Inheritance through Omit kept it in the type, so the type check passed, but the component spread props and then overwrote getRowState with its own selection logic. My getRowState={row => row.id === selectedId ? 'selected' : undefined} compiled cleanly and silently did nothing at runtime. The docs and types promised a prop the component discarded. A tsc-only check could never reveal it.

I fixed it immediately in PR#398. The component now extracts the caller's getRowState and combines it with selection: a selected row keeps the selection highlight, and every other row falls back to the caller's danger or warning state. Browser verification confirmed data-state=selected moving from zero rows to one.

This was not a missing component, false lead, or shallow cross-industry abstraction. It was a fourth class of result: ⁠a dishonest type.⁠ The component claimed to accept a prop while throwing it away. Only an agent that trusted and exercised the API exposed a defect left behind by green type and structural checks.

What I took away: cold testing exposes four classes of defect

The build loop now recognizes four kinds:

  1. ⁠Missing components⁠, built after the rule of three (14 so far).
  2. ⁠False leads⁠, where the catalog points to the wrong component and needs correction.
  3. ⁠Shallow cross-industry abstractions⁠, where a component covers the foundation but not the domain depth, such as transit legs[].
  4. ⁠Dishonest types⁠, where a prop exists in the type but is silently ignored at runtime, as with ActionDataTable.getRowState.

The first three come from asking an agent with no prior context to build. The fourth requires it to ⁠trust the API and actually run the result.⁠ A successful compile with no effect is among the hardest failures to notice. The value of cold testing lies as much in trusting the product as in covering it.

What it flagged

  • 🟡 A CaseTable or IncidentRow skeleton⁠. ApprovalWorkflow owns the lifecycle, but the case list still rebuilds type, status, priority, and assignee columns every time. I recorded it on issue #399, alongside Leaderboard as another repeatedly hand-rolled DataTable skeleton. I also noted sorting on derived values and weak guidance around Meter.direction and the distinction between trend and tone on Statistic.

⁠What changed under src this round: the ActionDataTable bug fix.⁠ The 4.5/5 turned a prop that only existed in the type into one that works.

📊 Component scoreboard (⁠14 built⁠, plus shipped fixes)

Earlier components plus Itinerary / TicketStub / Stringline / StatusBoard / ExpiryBadge In progress: DesktopPageHeader at four cases, CaseTable and Leaderboard as DataTable skeletons, BottomActionBar at three of three, and StatusLevel at two of three.

📋 Taxi progress

  • 🚕 ⁠Taxi: five staff screens (dispatch, drivers, revenue, fleet, complaints and incidents)⁠ / no passenger screens yet

Next up: build log

  • After five staff screens culminated in a runtime-only prop defect, I'll decide whether passenger ride hailing will close taxi or whether the mode is already deep enough.

The run

A prop passed the type check and was still ignored at runtime. Cold testing earns its value by trusting the API enough to reveal when that trust is misplaced.

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指示書パック」、先行登録を受付中です。