#46スコア 4.5/5小売・EC

AI に入荷検品を作らせてみた — 3回見たスキャンをコンポーネントに、前回の2つも複利で返ってきた(やってみた #46)

ルート: /receiving
デスクトップ表示
モバイル表示

375px のビューポートで撮影。縦長のページはフレーム内をスクロールします。

解説記事

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回ルール未達)

  • 🟡 ⁠スキャン履歴のコンポーネント⁠ScanInputshowFeed で吸収したが、独立した 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 が組み上げた実コード

ファイル名をクリックでソースを展開できます。

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