AI に入荷検品を作らせてみた — 3回見たスキャンをコンポーネントに、前回の2つも複利で返ってきた(やってみた #46)
/receiving375px のビューポートで撮影。縦長のページはフレーム内をスクロールします。
解説記事
AI に入荷検品を作らせてみた — 3回見たスキャンをコンポーネントに、前回の2つも複利で返ってきた(やってみた #46)
やってみたシリーズ: 自作のデザインシステム
@gunjo/ui(群青)を、群青のことを一度も見たことのない AI に、実際の画面で組ませていく連載。小売5枚目——入荷検品 / 受領(発注書照合 × バーコードで1品ずつスキャン照合 × 入荷予定vs実績 × 不足/過剰/不良 × ロット/賞味期限 × 在庫計上)。
「1業界は最低3〜5枚・可能な限り違う内容・業界特有UIを最優先」という方針。#42 売る・#43 数える・#44 締める・#45 巻き戻す に続いて、今回は 受け入れる——納品をバックヤードで検品して在庫に計上する、入荷の物流。小売5枚目で、ようやく業界の判断が立体的に見えてきました。
結果 — 4.5/5
tsc/build 緑・console 0・375px(ハンディ端末想定)でカード化・スキャン→該当行+1+読み上げ・対象外コードはエラー読み上げ・差異が符号+text+矢印・差異理由が無いと確定ブロック・受領完了 read-only・h1 1個(CardTitle as で階層整え)。
今回の本題 — 「スキャン」を3回見たので、コンポーネントを作った
予備知識ゼロの AI が最大の欠落に挙げたのは、バーコードのスキャン入力でした。そして毎回、同じつなぎのコードを手で書いていました:
上位のコンポーネントが足りない:
ScanInput(スキャンして照合)だ。スキャン用のコンポーネントもonScanも、一致/不一致の処理も、スキャン履歴も無い。ループ全体が手作り。これは小売で繰り返し必要になる(POS #42・棚卸 #43・返品 #45・入荷 #46 が全部これを作り直している)。
#220 の系譜で 3回到達(棚卸 SKU#43・返品レシート#45・入荷 JAN#46)。しかも今回が最も純粋な形——スキャンガンは「コードを高速でタイプして Enter を送るキーボード」なので、Input + Enter のハンドラ + 読み上げ領域 + 再フォーカスのループ + スキャン履歴、を毎回作り直していました。3回ルール発火で ScanInput(#220)(PR #225)を出荷:
function handleScan(code: string): ScanResult {
const line = lines.find((l) => l.jan === code)
if (!line) return { ok: false, message: `発注に無い商品です(${code})` }
increment(line) // 消費側が状態を更新
return { ok: true, message: `${line.name} を1点 検品` }
}
<ScanInput label="バーコード / JAN をスキャン" inputMode="numeric" onScan={handleScan} showFeed />
onScan(code)が Enter で発火。使う側が{ ok, message }を返すと、読み上げ・色調・履歴が動く(ライブラリは使う側のデータ形に依存しない)。- 全スキャン結果を、常設の 控えめな読み上げ領域(
role=status・polite)で読み上げ——強制割り込み(assertive)ではなく控えめ(polite)なので、連続スキャンが自分を遮らない。 - スキャン後に自動クリア+再フォーカス(スキャンガンのループ)、
lockMsで二重発火を抑制、showFeedで上限つきの履歴(新しい順)。 - バーコードのアイコン・label/description のアクセシビリティ・
aria-invalid対応・キーボードだけで完結(タイプして Enter)。
ブラウザ実証: 4901234567894 をスキャン→role=status が「有機ほうじ茶 500ml を1点 検品(検品 1 / 発注 24)」+ OK 履歴+行が+1、対象外 9999999999999→「発注に無い商品です」の destructive 行+対象外 履歴、入力は自動クリア、高速リピートは lockMs で正しく抑制。
そして複利が2つ同時に返ってきた
今回、予備知識ゼロの AI は、直近2回で埋めた"足りないコンポーネント"を、両方とも自力で発見して使いました:
-
Delta— 差異(over/short)に使った、手こずらず。「-23 不足」「+n 過剰」「一致」をセル・フッタ・受領ダイアログで正しく描画。tonesの上書きで過剰=警告に。-
CardTitle as="h2"— 見つけて使った、見出しの階層を飛ばさないために(確認: H1 → H2 で、h1→h3 の飛びなし)。
Delta は #44 で3回ルールにより作り、CardTitle as の docs での可視化は #45 で反映——その両方が、まっさらな #46 の予備知識ゼロの AI に発掘されて効きました。#45 が h1→h3 を踏んだ、まさにその欠落を、docs を直したことで #46 は踏みませんでした。
横断的なコンポーネントも全採用: EditableDataTable(検品数量+理由 Select+自動計算の差異・renderFooterCell で合計・375px カード化)・NumberInput(stepper)・DatePicker(賞味期限)・RevealSection(差異処理パネル)・formatCurrency(仕入原価)。
課題に記録だけした欠落(3回ルール未達)
- 🟡 スキャン履歴のコンポーネント(
ScanInputのshowFeedで吸収したが、独立したScanFeedを望む声)。今回showFeedで内包したので、一旦解消扱い。 - 🟡
DatePickerの value がDate型(ISO 文字列で扱う設計だと、境目で変換が要る)。string モードがあれば、つなぎのコードが減る。1回目。 - 🟡
EditableDataTableのセル内のSelectが、既定h-9で背が高い(h-8で手当て)。
学び — 小売5枚で、業界の地図が描けた
#42 売る : 釣銭/レシート/¥%割引(POS 複合)
#43 数える : スキャン①/差異表示①/Progress の名前
#44 締める : 過不足③→Delta 作成/支払区分別サマリ
#45 巻き戻す: 複利①(Delta 発掘)/CardTitle as 可視化
#46 受け入れる: スキャン③→ScanInput 作成/複利②(Delta+CardTitle as)
1業界を5枚・内容を散らして回すと、(a) 横断的なコンポーネントは全画面で効き、(b) 画面特有の欠落が各回で出て、(c) 3回再発した"足りないコンポーネント"がコンポーネントに育ち、(d) 育てたコンポーネントが次のまっさらな AI に発掘される——この4つが同時に観測できました。小売は ScanInput と Delta という2つの小売向けコンポーネントを残して、判断がつきました。金額系・グリッド・スキャン・差異——小売/fintech/会計/給与を貫くコンポーネントが揃ってきました。
次回予告(やってみた #47)
- 別業界へ(医療/物流/不動産など業界特有UI)。小売で育てた ScanInput が、物流(入出庫・ピッキング)で即発掘されるかも観測ポイント。
試す
まだ alpha。小売5枚目で、3回見たスキャンをコンポーネントにし、前回の投資が2つ同時に返ってきた回。
この連載は、作者が AI(Claude と Codex)と協働で制作しています。実験・検証の設計、判断、公開前の事実確認は人間が行い、実作業と下書き執筆は AI が担っています。
使用した @gunjo/ui コンポーネント
この画面のソースが直接 import しているコンポーネントです。
cold AI が組み上げた実コード
ファイル名をクリックでソースを展開できます。