AI に入荷検品を作らせてみた — 3回見たスキャンを primitive に、前回の2つも複利で返ってきた(やってみた #46)
/receiving375px のビューポートで撮影。縦長のページはフレーム内をスクロールします。
解説記事
AI に入荷検品を作らせてみた — 3回見たスキャンを primitive に、前回の2つも複利で返ってきた(やってみた #46)
やってみたシリーズ: 自作のデザインシステム
@gunjo/ui(群青)を、文脈ゼロの cold な AI に実 UI で作らせる連載。小売5枚目——入荷検品 / 受領(発注書照合 × バーコードで1品ずつスキャン照合 × 入荷予定vs実績 × 不足/過剰/不良 × ロット/賞味期限 × 在庫計上)。
「1業界は最低3〜5枚・可能な限り違う内容・業界特有UIを最優先」という方針。#42 売る・#43 数える・#44 締める・#45 巻き戻す に続いて、今回は 受け入れる——納品をバックヤードで検品して在庫に計上する inbound logistics。小売5枚目で、ようやく業界の判断が立体的に見えてきた。
結果 — 4.5/5
tsc/build 緑・console 0・375px(ハンディ端末想定)でカード化・スキャン→該当行+1+読み上げ・対象外コードはエラー読み上げ・差異が符号+text+矢印・差異理由が無いと確定ブロック・受領完了 read-only・h1 1個(CardTitle as で階層整え)。
今回の本題 — 「スキャン」を3回見たので primitive を起こした
cold AI が最大の穴に挙げたのは、バーコードスキャン入力だった。そして毎回同じ glue を手で書いていた:
Missing higher-order primitive: a
ScanInput(scan-to-match). There is no scan component, noonScan, no match/notFound handling, no scan feed. The whole loop is hand-rolled. This is a recurring retail need (POS #42, stocktake #43, returns #45, receiving #46 all re-invent it).
#220 系譜で 3回到達(棚卸 SKU#43・返品レシート#45・入荷 JAN#46)。しかも今回が最も純粋な形——スキャンガンは「コードを高速タイプして Enter を送るキーボード」なので、Input + Enter ハンドラ + 読み上げ region + 再フォーカスのループ + スキャン履歴、を毎回再発明していた。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 }を返すと、読み上げ・トーン・履歴が駆動される(ライブラリは消費側のデータ形に依存しない)。- 全スキャン結果を常設の polite live region(
role=status)で読み上げ——assertive でなく polite なので、連続スキャンが自分を遮らない。 - スキャン後に自動クリア+再フォーカス(スキャンガンのループ)、
lockMsで二重発火を debounce、showFeedで上限付き履歴(新しい順)。 - バーコード icon・label/description の a11y・
aria-invalid対応・キーボードネイティブ(type-then-Enter)。
ブラウザ実証: 4901234567894 をスキャン→role=status が「有機ほうじ茶 500ml を1点 検品(検品 1 / 発注 24)」+ OK 履歴+行が+1、対象外 9999999999999→「発注に無い商品です」の destructive 行+対象外 履歴、入力は自動クリア、高速リピートは lockMs で正しく debounce。
そして複利が2つ同時に返ってきた
今回 cold AI は、直近2回で私が埋めた穴の部品を、両方とも自力で発見して使った:
Delta— USED for 差異 over/short, not fought. 「-23 不足」「+n 過剰」「一致」をセル・フッタ・受領ダイアログで正しく描画。tones上書きで過剰=警告に。CardTitle as="h2"— FOUND and USED to keep skip-free heading order(verified: H1 → H2, no h1→h3 jump)。
Delta は #44 で 3回ルールにより build、CardTitle as の docs 可視化は #45 で land——その両方が、まっさらな #46 cold AI に発掘されて効いた。#45 が h1→h3 を踏んだまさにその穴を、docs を直したことで #46 は踏まなかった。
横断 primitive も全採用: EditableDataTable(検品数量+理由 Select+導出差異・renderFooterCell で合計・375px カード化)・NumberInput(stepper)・DatePicker(賞味期限)・RevealSection(差異処理パネル)・formatCurrency(仕入原価)。
起票だけした穴(3回ルール未達)
- 🟡 スキャン履歴 component(
ScanInputのshowFeedで吸収したが、独立したScanFeedを望む声)。今回showFeedで内包したので一旦解消扱い。 - 🟡
DatePickerの value がDate型(ISO 文字列ドメインだと境界で marshal)。string モードがあれば glue が減る。1回目。 - 🟡
EditableDataTableセル内Selectが既定h-9で背高(h-8手当て)。
学び — 小売5枚で、業界の地図が描けた
#42 売る : 釣銭/レシート/¥%割引(POS 複合)
#43 数える : スキャン①/差異 atom①/Progress 名前
#44 締める : 過不足③→Delta build/支払区分別サマリ
#45 巻き戻す: 複利①(Delta 発掘)/CardTitle as 可視化
#46 受け入れる: スキャン③→ScanInput build/複利②(Delta+CardTitle as)
1業界を5枚・内容を散らして回すと、(a) 横断 primitive は全画面で効き、(b) 画面特有の穴が各回で出て、(c) 3回再発した穴が primitive に育ち、(d) 育てた primitive が次の fresh AI に発掘される——この4つが同時に観測できた。小売は ScanInput と Delta という2つの retail primitive を残して、判断がついた。金額系・グリッド・スキャン・差異——retail/fintech/会計/給与を貫く部品が揃ってきた。
次回予告(やってみた #47)
- 別業界へ(医療/物流/不動産など業界特有UI)。小売で育てた ScanInput が、物流(入出庫・ピッキング)で即発掘されるかも観測ポイント。
試す
まだ alpha。小売5枚目で、3回見たスキャンを primitive にし、前回の投資が2つ同時に返ってきた回。
<!-- 公開前: 相互URL差込/スクショ確定/EN(dev.to)ミラー -->
使用した @gunjo/ui コンポーネント
この画面のソースが直接 import している部品です。
cold AI が組み上げた実コード
ファイル名をクリックでソースを展開できます。