Pattern: RF putaway
Who uses it: whoever moves what receiving just accepted into its storage bin — the second half of every inbound delivery. How often: per handling unit, all day while trucks unload. What "done" looks like: the HU physically in the bin the system chose, proven by scanning that bin's label — the verification idiom: the screen names the destination in bold, and the scan must match it. A wrong bin flashes red before the pallet is set down, which is precisely when a putaway error is still free to fix. Not the receiving step — goods receipt captures what ARRIVED against the PO; putaway moves what receipt accepted. Merging them makes dock workers do binning decisions with a truck waiting, which is how both jobs end up done badly.
The screen — try it
Shown the way the scanner gets it: an isolated 360×640 document
styled by ONLY the rf-essentials profile
(Chrome/WebView 108) — not the docs bundle. One screen, not two:
this page used to render the same component a second time at desktop
width, which showed the reader a size no putaway worker ever holds
(roadmap 131.1).
Scan (type + Enter) B-07-12 to
confirm — anything else flashes the error verdict with the expected
bin in the announcement. The compare is three consumer
lines on bo:scan: verification is validation,
and validation is yours; the framework paints capture and verdict.
Markup
<!-- The verification idiom: the system NAMES the destination and
the scan must MATCH it. Matching is three consumer lines — the
framework paints capture and verdict, never decides them. -->
<dl class="bo-kv bo-kv--rows"> …HU / From / To bin (bold — the one fact that matters)… </dl>
<input class="bo-input bo-input--code" data-scan-input data-scan-flash
aria-describedby="scan-status" />
<p id="scan-status" data-scan-status aria-live="polite" class="bo-visually-hidden"></p>
<script type="module">
document.addEventListener("bo:scan", (e) => {
if (e.detail.value !== expectedBin) flashScanResult("error", "Wrong bin: …");
else confirmPutaway();
});
</script>
<div class="bo-btn-group bo-btn-group--bar">
<button class="bo-btn bo-btn--secondary">Back</button>
<button class="bo-btn bo-btn--secondary">Bin full</button>
<button class="bo-btn bo-btn--secondary">Wrong HU</button>
</div> Anatomy
- Move header — KV rows: HU, where it came from, and the destination in bold — the one fact the whole screen exists to confirm.
- Verification scan — scan input + flash; the label says what to scan, the announcement carries the verdict.
- Confirmation line — a badge + text, revealed on match; the next HU replaces the screen in a real app.
- Exception bar — Back / Bin full / Wrong HU: the two real exceptions this move has, intent-named.
Data contract
| Request | Returns / on failure |
|---|---|
| GET /rf/putaway/next | the next HU + its system-chosen bin, server-rendered |
| POST /rf/putaway/:hu/confirm | the scanned bin; the server RE-CHECKS the match (the client compare is UX, never the control) and returns the next HU |
| POST … bin-full | returns an alternative bin for the same HU — the worker never chooses a bin freehand |
| 409 (bin occupied since assignment) | a new destination with the reason — never a silent reroute; reroutes without reasons are audit holes |
States
| State | What ships |
|---|---|
| Awaiting verification | destination bold in the header; the field names it again in its label |
| Wrong bin scanned | error flash + expected bin announced; field clears for retry — the pallet is still on the truck of the forklift, where the fix is free |
| Confirmed | badge + text, then the next HU; the flash alone is never the record |
| Bin full | the exception button, not a freehand override — the server picks the alternative |
| Session lost | reconnect re-renders the HU as the server has it; persistence is infrastructure, not a screen promise |
| No JS | Enter posts the scan; the server's re-render carries the verdict. Header, field and bar are markup |
Components used
complexity 2 of 4