Browser scanner inventory implementation plan
Strategy
Implement browser scanner support as a workflow-local deep module with one completed-scan interface. Keyboard-wedge input is the cross-browser baseline; Web Serial is a progressive enhancement for Chromium desktop and scanners configured as CDC or virtual COM devices. WebHID is excluded until a named scanner requires a vendor-specific HID report adapter.
A scan selects a product in the existing Receive Inventory dialog. It never receives or adjusts inventory automatically. The existing quantity field and Receive confirmation remain the only mutation path.
Status
Implemented on 2026-08-05 for the Receive Inventory workflow. Focused automated coverage and a browser smoke with real key events plus an injected Web Serial capability are complete. Receive and product lookup require both store.inventory.write and store.products.read; inventory-only callers do not issue product-list requests. Physical scanner enumeration, operating-system drivers, enterprise policy, and per-model settings remain external hardware-validation dependencies; WebHID and adjacent inventory workflows remain out of scope.
Scope
Deliver:
- focused manual and USB HID keyboard-wedge barcode capture;
- Enter-terminated scan submission with leading-zero and alphanumeric preservation;
- optional Web Serial connection with user-controlled serial settings;
- incremental bounded serial framing across arbitrary stream chunks;
- explicit serial connection, denial, error, unplug, disconnect, and cleanup states;
- exact selected-store lookup through
api.products.byBarcode; - stale lookup protection across scans, dialog lifecycle, and store changes;
- recoverable no-match UI;
- a reusable capture interface that can later be mounted in transfers, purchase-order receiving, and serialized inventory;
- privacy protection: raw scan values are never logged or sent to analytics.
Do not deliver:
- scan-triggered inventory mutations;
- offline queues, cycle-count sessions, or retryable bulk adjustments;
- a global route-aware scanner listener;
- WebHID or vendor-specific report parsing;
- backend authorization or endpoint changes;
- unvalidated vendor/product chooser filters.
Module seams
These are the TDD test seams. Tests exercise only their public interfaces.
ScannerCaptureinterface- Props:
label, optionaldisabled, andonScan({ value, source }). - Observable behavior: focused manual/wedge input, Enter submission, modifier/composition exclusion, feature-detected serial controls, accessible status/errors, duplicate suppression, and unmount cleanup.
- Props:
ScanFramerinterface- Accepts timestamped
Uint8Arraychunks and returns zero or more complete strings. - Observable behavior: partial/multiple frames, CR/LF/CRLF/Tab delimiters, Unicode chunk boundaries, bounded overflow discard, partial timeout, empty-frame discard, and reset.
- Accepts timestamped
connectSerialScannerinterface- Accepts a serial-capability adapter, validated connection options, and frame/state callbacks; returns a disconnect handle.
- Observable behavior: chooser/open/read lifecycle, arbitrary chunks, cancellation/denial, fatal read errors, EOF/unplug, reader-lock release, port close, and idempotent disconnect.
- Receive Inventory workflow seam
- Observable behavior through
InventoryPage: a completed scan performs exact barcode lookup for the active store, selects the result, reports no match, ignores stale responses, and does not callapi.inventory.receiveuntil the user presses Receive.
- Observable behavior through
Invariants
- Barcodes remain strings; never parse or coerce them numerically.
- Scanner capture emits only non-empty completed values.
- Buffered serial input is bounded and cleared after timeout, overflow, disconnect, or unmount.
- Direct serial access is requested only from a user action.
- Unsupported browsers retain the complete wedge/manual path and do not show unusable serial controls.
- A duplicate from the other transport may be suppressed briefly; repeated scans from the same transport remain distinct.
- Workflow context owns lookup, authorization, stale-response handling, staging, and mutation.
Implementation plan
Follow these slices in order. Each behavior uses a red → green TDD cycle before moving to the next slice.
Slice 1 — keyboard-wedge capture
- Add a failing interactive component test for manual/keyboard-wedge entry ending in Enter.
- Implement the minimal
ScannerCaptureinterface and focused input. - Add failing cases for leading zeros, alphanumeric values, modifiers, IME composition, empty values, and explicit manual submission; implement each case minimally.
- Run the focused scanner capture tests.
Slice 2 — bounded serial framing
- Add a failing framer test for one CR-terminated frame split across chunks.
- Implement the minimal incremental framer.
- Add one failing case at a time for multiple frames, CRLF, LF, Tab, empty frames, Unicode chunk boundaries, overflow, timeout, and reset; implement each minimally.
- Run the focused framer tests.
Slice 3 — Web Serial lifecycle
- Add a failing lifecycle test using a fake serial-capability adapter and fake readable stream.
- Implement chooser, open, read, frame delivery, and disconnect.
- Add one failing case at a time for chooser cancellation, permission/open failure, fatal read error, EOF/unplug, lock release, close, and idempotent cleanup; implement each minimally.
- Connect the adapter to
ScannerCapturebehind runtime feature detection. - Add serial-setting controls for baud rate, data bits, stop bits, parity, and flow control. Use explicit conservative defaults and do not claim a device is supported without hardware validation.
- Add cross-transport duplicate coverage and implementation.
- Run scanner capture and serial tests.
Slice 4 — Receive Inventory integration
- Add
products.byBarcodeto the existing page API mock. - Add a failing interactive workflow test: scan
00123ABC, resolve the active-store product, show it selected, and proveinventory.receivewas not called. - Mount
ScannerCapturein the Receive Inventory dialog and implement exact lookup with request-sequence and store-scope guards. - Add failing cases for 404/no-match, non-404 error, rapid scans with stale completion, store change, and dialog close; implement each minimally.
- Preserve the existing explicit Receive mutation test.
- Run inventory behavior and scanner tests.
Slice 5 — documentation and operational policy
- Add the contextual research report and this implementation plan to the documentation index.
- Document keyboard-wedge configuration, Chrome/Edge-only direct serial behavior, serial defaults/settings, permission recovery, privacy behavior, and the unsupported-hardware boundary in the implementation report.
- Confirm the deployment
Permissions-Policydoes not denyserial; change it only if validation proves necessary.
Slice 6 — self-validation
- Run focused Bazel tests after every slice.
- Run
//apps/websites/portals/retail:test,:typecheck, and:lint. - Start the retail portal through Bazel/local-validation and smoke the wedge path with real keyboard events. Inspect serial unsupported-state behavior in a browser; physical Web Serial enumeration remains a named hardware-validation dependency.
- Run broad relevant targets on BuildBuddy Remote Bazel, including the retail build and repository tests required by CI.
Slice 7 — review and delivery
- Commit only isolated scanner and documentation changes on
feat/browser-scanner-inventory. - Push with the
kenobi1337GitHub account and create an informative PR containing scope, architecture, screenshots/evidence, test commands, known hardware limits, and post-merge risks. - Run the required two-axis code review against
origin/main: Standards and Spec in parallel. - Correct every actionable finding, rerun affected validation, and repeat review when behavior changes.
- Wait for PR checks, diagnose failures from their primary logs, and revise until all required checks are green.
- Assess post-merge readiness against merge-result-sensitive workflows, generated docs/build inclusion, deployment headers, and the current base branch. Report any external physical-hardware validation separately; do not present it as completed without a device.