Saltar al contenido principal

Desktop Printing Troubleshooting

Operational runbook for generic ESC/POS printing on FlowPOS Desktop (055-desktop-generic-printing), including follow-up advertisement and bundle-update behavior from PR #746 / PR #747.

Architecture: Desktop generic printing. Runtime: Desktop runtime. Local launch: Run locally.

Lab spikes, planted-defect procedures, and hardware-record writing stay in specs/055-desktop-generic-printing/quickstart.md. This page is what to do when a till misprints.


Scope​

Primary codepaths:

  • apps/desktop/src-tauri/src/transport/ — five kinds, retry boundary
  • apps/desktop/src-tauri/src/printers/mirror.rs — printer_mirror
  • apps/desktop/src-tauri/src/executor/ — outbox state machine
  • apps/desktop/src-tauri/src/discovery/ — scan + printer_discovery_event
  • apps/desktop/src-tauri/src/verification/ and build.rs — counting records
  • apps/desktop/src-tauri/src/bridge/handlers/updates.rs — web bundle apply
  • apps/backend/src/printer-config/ — saved printers, claims, server designation

1) Symptom triage​

SymptomLikely layer
Kind missing from Find printers on one OSNot compiled for that OS (windows_spooler on macOS), or Registry did not load
Kind present but handshake unverified_transports names itNon-production build with FLOWPOS_UNVERIFIED_RELEASE; Find printers still offers it
"Requires newer version" after a shell updateOld web bundle; the native shell already knows the kind
Ticket printed twiceRetry after LostDuringWrite — transport boundary, never a merchant error
Station silently stops printingPrinter not ready for a minute → claims released; Windows queue paused
Serial "port in use"Warm session of a held station owns the exclusive serial port
Direct USB fails on WindowsWinUSB not bound; setup screen driver-step text
Held Windows queue printed later, runtime also retriedRuntime must not cancel or reprint; look for a boundary defect
About → Check for updates did nothing / status: errorNeedsNewerShell, unsigned/dev session, or webVersion older than this installer
Scan finds nothingOS permission, radio off, USB driver — not a missing hardware-verification record

2) Advertisement vs verification​

Do not treat a missing hardware-verification/records/{kind}.{platform}.json as "this till cannot print over that kind."

QuestionAnswer in this build
What does Find printers offer?Every compiled merchant-selectable kind (Registry::advertised())
What does the handshake transports list?The same list
What do counting records do?Honesty: FLOWPOS_VERIFIED_KINDS, crash reports, flowpos-hwcheck
What hides a kind?It is not compiled for this OS, or an older web bundle does not know the kind name

cargo test --test hardware_gate is still useful: OFFERED-WITHOUT-RECORD: and NOT-COUNTING: explain lab evidence. They do not explain a merchant scan.


3) Retry boundary (duplicates)​

Before the first byte reaches the printer, a failure is Connection or Timeout and the executor retries. At or after that moment the failure is LostDuringWrite and the job goes to needs_attention — never an automatic retry.

KindThe moment the runtime can no longer prove nothing printed
tcpfirst byte handed to the socket
blefirst chunk handed to the link (MTU − 3, twenty bytes if nothing negotiated)
serialfirst byte handed to the exclusive port; done only after drain
windows_spoolerfirst WritePrinter — the queue's bytes are the printer's
usbfirst bulk transfer beginning

When a ticket printed twice:

  1. Status window / tray attention count, job attempt_phase.
  2. outbox_counter.double_completions if the counter exists on that build.
  3. Transport mapping to LostDuringWrite — a duplicate is a boundary defect.

Never advise "just retry" on needs_attention without the duplicate-risk acknowledgement the runtime requires.

Windows queue extra rule: after submit, the job is polled every 500 ms for up to 30 s. Error or still present at the bound is LostDuringWrite naming the Windows job id. The runtime never cancels, restarts, or deletes that job. Crash during the wait uses spooler_recovery.


4) Mirror, claims, and silent stations​

The runtime copies saved printers into printer_mirror (beside the outbox) so routing works with no web app present. It is never the source of truth.

Stations claimed automatically: printers bound to this installation (Bluetooth, serial, installed queue, direct USB). Shared network printers are claimed only when this machine is the designated server.

A printer that is not ready for a minute releases its stations. Check:

  • tray attention count
  • printer_mirror.last_ready_state
  • backend kds_device_station_claim rows for this installation_id

Paused / offline / error / out of paper / door open on a Windows queue refuses before submit (Connection) so the executor can retry. After submit, do not expect the runtime to print a second copy when the printer returns.


5) Serial and USB​

  • Serial ports are opened exclusively. "Port in use" usually means this machine already holds a station on that printer, or another program has the port. Release the station or close the other program.
  • Direct USB on Windows needs WinUSB bound to the device. The setup screen driver-step text is the merchant-facing instruction; nusb will not talk to the vendor driver.

6) Web bundle vs native shell​

Two update paths, two keys — see Desktop runtime. Printing symptoms that look like "the kind disappeared" after an update are usually the web bundle.

PathWhoWhen it takes effect
Unattended retrieve (spawn_unattended)Background after the window is upStages pending.json; activates on next start
About → Check for updates (updates.apply)Merchant confirmed not mid-ticketActivates now and reloads the WebView
Native installerTagged releaseNext start of the new executable

tauri dev reports development and does not fetch a signed bundle.

NeedsNewerShell: the bundle verified, was staged, and must not activate because requires.min_bridge_contract or a required capability exceeds this installation. Unattended logs bundle: {version} needs a newer application. Merchant-initiated apply returns status: "error" with the current webBuildVersion. The merchant needs a newer desktop app, not another web refresh.

latest.json names a git SHA; the About chip is webVersion. Retrieve will not activate a pointer whose webVersion is strictly older than this installer, so Check for updates cannot walk 0.0.384 back to 0.0.383.


7) Telemetry (desktop scans)​

After a scan, identifiers stay hashed; no name/host/MAC in findings.

SELECT
client_kind,
findings->'client' AS client,
findings->'unavailableKinds' AS unavailable_kinds
FROM printer_discovery_event
WHERE client_kind = 'desktop'
ORDER BY occurred_at DESC
LIMIT 1;

Expect client_kind = 'desktop', installation id / role / OS present, and unavailableKinds only for kinds this OS cannot compile (for example windows_spooler on macOS).

More survey queries: Installed-base view.


8) Verification checklist (merchant flows)​

Use this when a till is in the room. Full lab procedure: specs/055-desktop-generic-printing/quickstart.md.

  1. Settings → Printers → Scan: Ethernet, Windows queue (Windows), paired Bluetooth as serial or "found but unusable — no serial port", BLE, USB (Mac, or Windows with no queue for that device).
  2. Test print with the printer off: failure text must name the condition (busy port, paused queue, nothing answered).
  3. Complete a sale; pull the cable mid-receipt: needs_attention, duplicate warning, no automatic second copy.
  4. Spooler: power off after accept; within 30 s the job needs attention naming the Windows job id; power on prints the first copy only.
  5. Headless: pair, assign a bound printer, designate server for a shared network printer, close the POS window, restart. Tickets print once. Unplug bound printer → station released; plug back → re-claimed.