A prop that passed type checks was silently discarded at runtime
/taxi-incidentsCaptured 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:
ApprovalWorkflowfit perfectly with no hand-rolling. Its docstring names case management, claims and applications, internal requests, expenses, and any staged administrative review. ControlledvalueandonChange, 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, andTimelinehave 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:
getRowStateonActionDataTablewas a trap. Inheritance throughOmitkept it in the type, so the type check passed, but the component spreadpropsand then overwrotegetRowStatewith its own selection logic. MygetRowState={row => row.id === selectedId ? 'selected' : undefined}compiled cleanly and silently did nothing at runtime. The docs and types promised a prop the component discarded. Atsc-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:
- Missing components, built after the rule of three (14 so far).
- False leads, where the catalog points to the wrong component and needs correction.
- Shallow cross-industry abstractions, where a component covers the foundation but not the domain depth, such as transit
legs[]. - 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
CaseTableorIncidentRowskeleton.ApprovalWorkflowowns the lifecycle, but the case list still rebuilds type, status, priority, and assignee columns every time. I recorded it on issue #399, alongsideLeaderboardas another repeatedly hand-rolledDataTableskeleton. I also noted sorting on derived values and weak guidance aroundMeter.directionand the distinction between trend and tone onStatistic.
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 #146
- 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
- gunjo.jp / ApprovalWorkflow / ActionDataTable / SignedRecord / npm
@gunjo/ui/ GitHub / previous rounds #1 through #144 - GunjoUI by UIXHERO
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.