Pattern: RF pick
Who uses it: a picker with a gun in one hand and the other on the rack — the highest-volume RF activity in any warehouse. How often: hundreds of times a shift, one task line at a time. What "done" looks like: right bin confirmed by scan, right item confirmed by scan, quantity entered without taking gloves off, and the next line already on screen. The screen answers before the worker asks — task, bin, item, quantity — in the KV header every RF screen here shares. Not for checking stock without moving it — counting what a bin holds is the count pattern, which is blind by design; picking knows exactly what it wants and verifies the worker found it.
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 picker ever holds
(roadmap 131.1).
Scan (type + Enter) A-01-04 or
MAT-4471 — capture flashes green; anything else flashes
red with the reason announced. There is no on-screen
keypad, and that is the point (roadmap 135.4, owner): an RF gun
ships a keyboard, so a grid of number buttons duplicates the platform and
spends the screen's scarcest resource on it.
inputmode="numeric" on the quantity input summons the
device's own keypad and stays the zero-JS floor.
Markup
<!-- Header answers before the worker asks: which task, which bin,
which item, how many. One scan field does bin AND item (the app knows
which it expects next); the flash paints capture, YOUR validation
paints the verdict. No on-screen keypad: inputmode summons the gun's
own keyboard, and the screen keeps the room. -->
<dl class="bo-kv bo-kv--rows"> …Task / From bin / Item / Pick qty… </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>
<div class="bo-quantity"> …picked qty, spacious steppers… </div>
<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">Skip item</button>
<button class="bo-btn bo-btn--secondary">Report short</button>
</div> Anatomy
- Task header — KV rows: task id + position, source bin, item, required quantity. Everything a picker needs to trust the screen, nothing more.
- Scan field — one
scan input for bin then
item; the app knows which it expects next. Capture flashes ok;
your validation calls
flashScanResult('error', why). - Quantity —
quantity at spacious
density — big enough for a gloved thumb, with
inputmode="numeric"bringing up the device's keyboard for anything the steppers are too slow for. - Exception bar — --bar: Back / Skip item / Report short. Intent-named, equal glove targets.
Data contract
| Request | Returns / on failure |
|---|---|
| GET /rf/pick/next | the next task line — this screen, server-rendered; an empty queue returns the task-menu redirect, never a blank pick screen |
| POST /rf/pick/:line/confirm | bin + item + qty; 422 names WHICH check failed (wrong bin / wrong item / qty over need) so the worker fixes one thing, not everything |
| POST … short (qty < need) | requires a reason code (select) — a silent short is tomorrow's stock discrepancy |
| 409 on a re-assigned line | re-render with who took it and the NEXT line — the worker keeps moving, never argues with a screen |
States
| State | What ships |
|---|---|
| Awaiting bin scan | the field names what it expects; the header carries the bin as text — the scan is confirmation, not discovery |
| Wrong bin / wrong item | error flash + the reason in the live region; the field clears and refocuses for the retry |
| Short pick | Report short opens the reason path; the line completes with its shortfall recorded, never silently adjusted |
| Line re-assigned (409) | who took it, then the next line — flow beats blame |
| Queue empty | back to the task menu; an empty pick screen teaches nothing |
| Session lost | the RECONNECT state: on regaining the server, re-render the line as the server has it. Session persistence is infrastructure, not a screen promise |
| No JS | the form still posts on Enter (the wedge's terminator) and the server re-renders with the verdict — the flash is an enhancement; the KV header, field, quantity and bar are markup |
Components used
complexity 2 of 4