Rukon
Back to Project Overview
Full Case StudySystems / ERP / WMS

RPOMS Print Engine

Offline-first label printing workstation for the RPOMS refurbishment line

RPOMS Print Engine wordmarkRPOMS Print Engine wordmark
Role
Sole Developer

Hardware Protocol Driver, Canvas 2D Renderer, Offline PWA & Backend

Affiliation / Client
Enterprise Systems & Refurbishment Programme
Timeline
September 2026 – present (deployed; hardware validation in progress)
Documentation Level
10 Engineering Sections

Operational Workflow & State Machine

End-to-End Pipeline
  1. 01Scan the router
  2. 02Detect the model from the serial
  3. 03Resolve and render the frozen template
  4. 04Queue behind whatever is printing
  5. 05Print over the open Bluetooth session

Related systems

Separate systems
  • Established Malaysian technology company inventory system, printing integration liveITMS

    the technology company's inventory management system, developed by another developer and supported by me. It prints device labels through this engine's relay; merged and deployed on 4 October 2026.

    Read its case study
  • Parent system, router labels through the RPOMS linkRPOMS

    RPOMS's Printing page opens one named Print Engine tab and sends each saved scan as a label request over a first-party channel; the tab holding the connected printer answers. Live on the engine side since 19 October 2026.

    Read its case study
01

Overview

On the refurbishment line that RPOMS runs, every processed router gets a sticker with its model name and its serial number as a barcode, and every packed box gets one with its number as a QR code. The printer is a NIIMBOT B1 Pro, a small Bluetooth thermal printer for 50 by 30 mm labels. The Print Engine is the workstation that produces those labels: a Progressive Web App that runs in Chrome or Edge, talks to the printer over Web Bluetooth, and keeps working when the internet does not.

It is built standalone, with an explicit boundary for RPOMS to supply data later, so the printer protocol, the label rendering and the queue could be tested and validated without touching a system in daily use.

The Router page of the RPOMS Print Engine: a large scan field, a status card reading NIIMBOT B1 Pro Not connected with a Connect button, and an empty print queue beside it.
The worker's whole screen: a scan field, the printer state, the queue. Nothing technical.
02

Problem

The vendor's route to this printer is its own phone app, fed in practice by a spreadsheet that worked out the model from the serial. That is one interaction per label, on a phone, with the model logic living in a formula. It does not follow a worker who scans one router after another, and it puts the correctness of every label on transcription.

The requirement was blunt: scan a serial with the USB scanner, and the correct label comes out. Continuously, on an ordinary PC, without a vendor app, a spreadsheet or a server between the scan and the printer.

03

Constraints that shaped it

Web Bluetooth is the only way to reach a Bluetooth printer from a browser without an installer, and it comes with limits: Chromium browsers on desktop only, HTTPS or localhost, and a native device chooser that the page cannot style or open on its own. The app is designed around those limits rather than against them: the chooser is Chrome's, and everything before and after it is the app's.

The label design was locked from the start. The owner had a template that matched the labels already in use, and the brief for every later change was that the physical output must not move. That constraint became a test: the JSON and the rendered raster of each shipped template are hashed against a baseline, and any difference fails the suite.

Printing had to be independent of everything else. The line cannot stop because a server is down or the internet is out, so the critical path from scan to printer is local by construction, and a test proves it makes no network request.

04

How it works

A scanner service bound to one input field completes a scan on Enter, Tab, whitespace, an inserted line break or a short pause after machine-speed characters, so scanners with different suffix settings all work and a person typing is never auto-submitted. The workflow detects the model, resolves the template and renders it in a promise chain, so scans made while a label is printing are processed in order rather than lost.

Detection is a rule table: a serial prefix and an exact length name a model, and the longest matching prefix wins. There is no lookup of the serial in a database, deliberately; the line prints for any router that matches a rule, and an unknown serial produces the message “No matching detection rule.” and nothing else.

The queue keeps one printer session open. Labels waiting while the printer is busy are sent as one continuous printer job with two pages of look-ahead, which is what stops the paper feeding out and pulling back between labels. The driver polls the printer's page counter and reports it; the queue marks each label complete as the counter passes it. If a link drops after the printer accepted a job, the labels the counter did not reach are marked Uncertain and wait for a person to choose Reprint or Discard.

The Detection rules page: a form to add a rule with prefix, length and model name, a test field showing a serial matched to Enterprise Dual-Band Wi-Fi 6 Gateway with its prefix and length, and a table of the current rules.
Detection rules: prefix plus length, longest prefix wins. The test field shows what a scan would resolve to, without printing.
05

Template editor

Administrators edit templates in a canvas-first editor: the label fills the screen, a floating icon toolbar adds text, images, barcodes, QR codes, lines and rectangles, and layers and properties live in drawers that stay closed until asked for. A contextual toolbar above the selected element shows only that element's controls. Fields appear as human-readable chips, Serial number and Model, with the template's own syntax kept under Advanced.

The editor changes the administrator's draft only. The shipped templates cannot change unnoticed, because the freeze test compares their rendered raster with the accepted baseline on every run.

The template editor with the router label filling the canvas: Leading Malaysian telecommunications provider logo, divider, model text, Code 128 barcode and serial text. A floating toolbar sits above the label and a zoom control below it.
The editor with nothing selected: the label is the product, the tools float.
The template editor with the model text selected: a contextual toolbar with font, size, bold and alignment controls, the layers drawer open on the left, and the properties drawer open on the right showing the Model field as a chip.
Selecting the model text: a contextual toolbar, the layers drawer and the inspector with field chips rather than template syntax.
06

Offline by construction

The first online visit installs a service worker that caches the whole build, including the fonts and the logo the templates need. Templates, detection rules, settings, print history and pending changes live in the workstation's IndexedDB. After that, the employee opens the app and works; with the network unplugged, the app boots from cache, detects, renders and prints from local data.

An automated test proves it: headless Chrome loads the built app online, waits for the worker, cuts the network at the protocol level, reloads, and checks that the Router page boots, the template renders from cache and the diagnostics panel reports Offline Ready.

The administrator's System panel showing Offline Ready: YES, with rows for cached assets, service worker, local database, detection rules, templates, Bluetooth, pending sync and last sync, and an Offline indicator in the header.
The administrator's readiness check, captured with the network cut: everything the print path needs is local.
07

Identity, provisioning and sync

For a fleet of stations, an optional backend on the company's own Ubuntu server provides Google sign-in, RPOMS authorisation, device provisioning and synchronisation. Google is the identity provider; RPOMS decides who may print. A new user starts pending unless allow-listed, an administrator activates them, and the first active login silently provisions the workstation with a device identity and a revocable, hashed token. The local session then keeps the station usable offline for thirty days after its last online check.

Sync is local-first and runs on its own timer, never on the print path: local writes happen first, the engine pushes print history and edits, then pulls configuration when the server has a newer version. The conflict policy is written down: templates last-writer-wins by timestamp with the losing local edit kept beside the winner, rules server-wins with a local backup, settings never synced. Without a backend URL the app runs standalone, which is how the hardware work is done.

The backend was built and tested against a fake identity provider and an in-memory store, then deployed: it now runs on a self-hosted Ubuntu server behind nginx and systemd, with a PostgreSQL store and Google sign-in configured, and its health endpoint reports both. The PostgreSQL store tests also run in CI against a real Postgres 16. A complete production sign-in, activation and provisioning round trip has not yet been recorded.

08

Device labels and a print relay for other applications

A second module came from a different problem. Label printing had become an issue for ITMS, the technology company's inventory management system, and my manager asked me to solve it. The module prints a QR code of each device's barcode SKU with its group SKU and logo. It has its own dashboard, quick and direct print pages, job history, printers, templates and settings, and access to each module is approved per user by a super admin and enforced on the server.

Other web applications print through a relay page rather than talking to the printer. The calling site opens the relay with the label data, the relay hands the job over a same-origin BroadcastChannel to the Print Engine tab that already holds the Bluetooth connection, and a Web Lock makes sure exactly one open tab answers. The relay waits for the printer's actual result and reports it back, and it refuses outright when no printer is connected or the queue is paused, rather than reporting a queued job as printed.

I am also building the other side of that integration in ITMS, a Next.js and Supabase application developed by another Established Malaysian technology company developer, which I support and maintain: a small print service that validates the SKUs and opens the relay, and print buttons on the new-device form, the device details and the device drawer. The Print Engine itself, including this module, is my own independent development.

The ITMS changes are on a branch and have not been merged or deployed in ITMS yet.

09

Things that went wrong, and what they taught

The printer fed the paper out and pulled it back between labels. The cause was one printer job per label; the fix was multi-page jobs with look-ahead, and continuous printing became the default. Matching the device's own session model mattered more than any timing tweak.

A scanner did not send Enter, so six serials arrived as one value and were rejected. A scanner-test panel that records raw key events showed what the device actually sent, and the scan service now ends a scan on any of the suffixes scanners use.

The offline build cached nothing, silently. The worker was active, the cache was empty, and the reload showed Chrome's error page. Attaching to the service worker over the DevTools protocol exposed a Workbox error about a duplicate precache entry for the logo, listed once by a glob and once explicitly. Verify what the worker cached, not whether it registered.

A performance gate flagged regressions on metrics of a few milliseconds for identical code. Single runs of that size are timer jitter; the gate now compares medians of three runs and ignores anything under a ten-millisecond floor.

Labels slowed from about four seconds to thirteen or more whenever the workstation's tab was in the background. Chrome throttles timers in hidden tabs, and the Bluetooth pacing and status polls were plain timers. Those waits now run in a dedicated Web Worker, which is not throttled the same way: fifty ten-millisecond waits in a hidden tab went from 49.5 seconds to 0.84, and labels returned to about four seconds.

10

Testing and verification

218 app tests and 26 backend tests run without a printer: a simulated B1 Pro speaks the documented protocol, so framing, identification, the print sequence, error codes, disconnect recovery, the queue, both workflows, storage, offline readiness, auth, sync and the backend are all exercised in Node. The template freeze test and the no-network guard run with them, a GitHub Actions workflow fails if the freeze baseline changes, and the PostgreSQL store tests run against a Postgres 16 service in CI.

Two headless-Chrome scripts check what unit tests cannot: the offline boot, and the editor's behaviour at desktop, tablet and phone widths. A benchmark tool runs the pipeline at the pre-build baseline and at the current tree and fails on regressions.

On the real printer, the B1 Pro connected, identified itself as model 4097 with a 576-dot head, and printed a router label with every acknowledgement in a session on 24 September 2026. The multi-label, scanner and offline runs on hardware are the next validation step, and the app records the timings for them itself.

The template editor with the template list collapsed to a narrow icon rail on the left, giving the label canvas the full width.
One of the states the headless UI check verifies: the template list collapsed to an icon-only rail, with the canvas reclaiming the width.

What's live, and what isn't

“It exists in the repository” and “it runs in production” are different claims. This is the difference, stated rather than left to be assumed.

  • ImplementedScan-to-print, detection, queue, templates, editorComplete and covered by the automated suite. Verified on the real printer once: connection, identification and one router label with every acknowledgement.
  • Built, not liveContinuous printing on the physical printerImplemented and tested against the simulated printer. The 10, 25, 50 and 100-label runs and the scanner-order runs on the B1 Pro are the next validation step; the Hardware test page records their timings.
  • Built, not liveOffline boot and printingBoot from cache and IndexedDB is proven by the automated headless test. The same run with the printer connected has not been recorded yet.
  • Built, not liveGoogle sign-in, provisioning and syncDeployed with a PostgreSQL store and Google sign-in configured, and tested in CI against Postgres 16. A full production sign-in, activation and provisioning round trip has not been recorded yet.
  • ImplementedDevice-label module and print relayDeployed with the engine: QR device labels, per-module access approved by a super admin, and a relay page that reports the printer's real result back to the calling site.
  • ImplementedITMS printing integrationPrint from the new-device form, device details and the device drawer, through the relay. Merged into ITMS and deployed on 4 October 2026 (commit 0f67de6).
  • ImplementedProduction deploymentLive since 29 September 2026 at print.rukon.dev, and since October also at print.neksdev.tech with sign-in on each hostname, on a self-hosted Ubuntu server: nginx, a systemd service and PostgreSQL. The health endpoint reports the PostgreSQL store, Google sign-in and the deployed commit, 9d46377 of 19 October 2026.
  • ImplementedDashboard, gated deploys, approved migrationsPer-account statistics dashboard with RPOMS sections; deploy gates, migrations as a separately approved step, a never-drop restore and an update checker; sync pushes refused with 413 are split and oversized events parked. In the 19 October production build.
  • ImplementedIntegration into RPOMSThe RPOMS link page and tab entry answer router label requests from allow-listed RPOMS origins, live since 19 October 2026; RPOMS's Printing page uses it since its 20 October release.

Technical stack

Workstation app

TypeScriptReact 19Vite 8Tailwind CSS v4Lucide icons

Print engine (framework-free core)

Web BluetoothNIIMBOT B1 Pro protocolCanvas 2D renderingCode 128 encoderqrcodePrint queueWeb Worker timersBroadcastChannel relayStructured tracing

Offline & storage

IndexedDBService worker (vite-plugin-pwa / Workbox)Web app manifest

Backend (optional)

Node.js 24 httpPostgreSQL (pg)Google OIDC with PKCEHMAC-signed sessionsDevice tokens

Hosting

Self-hosted Ubuntu servernginxsystemdDeploy, backup and restore scripts

Verification

Vitestfake-indexeddbSimulated printerHeadless Chrome (DevTools protocol)GitHub Actions

Limitations

  • Web Bluetooth means Chrome or Edge on desktop or Android; no Firefox and no iPhone.
  • Real labels have been printed on the B1 Pro, but no 10, 25, 50 or 100-label run, scanner run or offline run on the hardware is recorded yet.
  • A device token in the workstation's IndexedDB can be read by anyone with local access to that PC; the mitigation is OS login and server-side revocation.
  • The repository is private. Access can be granted on request.

Finished the technical breakdown?

Return to the concise HR overview or discover other projects in the portfolio.

Writing from this work

Google Sign-In Without a Client Library: Server-Side PKCE and Grants Read on Every Request

RPOMS added Google sign-in through its Supabase project's provider with no client library in the browser bundle. Google proves who someone is and grants nothing; a Super Admin grants access per service and capability, and revocation takes effect on the next request.

One Live Record per Router per Session: Partial Unique Indexes Instead of Per-Scan Locks

A barcode scanner can fire the same serial twice in a few milliseconds, and two workstations can scan the same router at once. RPOMS's Refining workstation catches both with one partial unique index, and decides whether to print from what the save recorded.

Sending Labels From One Web App to Another Tab: BroadcastChannel, COOP and window.opener

How RPOMS sends a router label to the Print Engine tab that holds the Bluetooth printer: why a hidden iframe could never work across sites, why one header had to be removed from one path, and what a lost answer must say.

The Service Worker Was Active and the Cache Was Empty: A Workbox Duplicate-Entry Bug

An offline PWA that registered its service worker, cached nothing, and failed silently. The cause was one file listed twice in the precache manifest by vite-plugin-pwa's includeAssets.

Vercel or a Self-Hosted Ubuntu Box? How I Chose Hosting App by App

Eight deployed apps, three hosting shapes: Vercel for pages and serverless APIs, a self-hosted Ubuntu box for a stateful API, and a hybrid for a model Vercel can't run.

Theme-Specific Logos Without a Flash: Let CSS Pick, Not useTheme()

A logo with an opaque white background became a white block on the dark theme. Swapping it in CSS on data-theme, not with useTheme(), fixed it on the first frame.

Testing Without Paid APIs or Hardware, and the Places Fakes Lied

Fake LLM providers, a simulated label printer and an in-process Postgres keep my test suites free and offline. What each proved, and three places a fake fell short.

Proving a PWA Boots Offline: A Headless Chrome Test Over the DevTools Protocol

An automated test that installs a PWA under production headers, cuts the network at the protocol level, reloads, and checks the app still works, with no test framework beyond a WebSocket.

Detecting a Device Model From Its Serial Number With Prefix Rules

How RPOMS and its print engine tell a router's model from its serial: prefix and length rules stored as data, longest prefix first, unknowns flagged.

Self-Hosting a Node and PostgreSQL Service With nginx and systemd on Ubuntu

The deployment behind print.rukon.dev: one origin for a PWA and API, a hardened systemd unit, atomic releases, nginx header gotchas and a backup design.

Security Headers for a Web Bluetooth PWA, Pinned by a Test

The CSP, Permissions-Policy and COOP for a PWA that drives a Bluetooth printer, why each source is there, a separate policy for the API, and a test that keeps nginx and the app in step.

A Written Conflict Policy for a Local-First Workstation App

How an offline-first PWA writes locally first, syncs on its own timer with backoff, and resolves conflicts with a rule per data type: last writer wins, server wins, append-only, never synced.

Where Does a 4-Second Label Go? Instrumenting a Scan-to-Print Pipeline

Stage-by-stage tracing of a Web Bluetooth label print, an audit of every sleep in the code, and the rule of not claiming a speed-up until the hardware has measured it.

Implemented, Available, Not Connected: Writing Honest Status Into Project Docs

"It exists in the repository" and "it runs in production" are different claims. The three-state status model I use across my projects, with real rows from each.

Google Sign-In for Shared Workstations: PKCE, Pending Users and Revocable Device Tokens

Google OIDC with PKCE on a framework-free Node server, users keyed by sub, accounts that start pending, and workstations provisioned with hashed, revocable device tokens that keep working offline.

Freezing a Label Design in CI by Hashing Its Rendered Raster

A Vitest test that hashes each label template's JSON and its exact 1-bit print raster, so any change that would move a single printed dot fails the build and the deploy.

Letting Another Web App Print Through an Open Tab: BroadcastChannel, Web Locks and COOP

How a tiny relay page lets another web application print on a Bluetooth printer held by an already-open tab, with one tab answering via a Web Lock and the printer's real result reported back.

Continuous Label Printing Without Duplicates: Page Counters and "Uncertain" Jobs

Why one printer job per label made the paper feed and rewind, how multi-page jobs fixed it, and how a print queue decides between a safe retry and a label that may already exist.

Chrome Throttled My Bluetooth Printer: Moving Timers Into a Web Worker

Labels slowed from about 4 seconds to 13 or more whenever the print tab was in the background. The cause was Chrome's timer throttling; the fix was a tiny Web Worker that owns the waits.

A Performance Gate That Stopped Crying Wolf: Medians and a Noise Floor

A baseline-versus-current benchmark flagged regressions on identical code. Medians of three runs and a rule that ignores tiny metrics made it a gate worth trusting.

Adding Label Printing to an Existing Next.js App Without Touching the Printer

The calling side of a print integration: validating SKUs the way the printer app will, passing them in a URL fragment, detecting blocked pop-ups, and refusing to print unsaved changes.

From Millimetres to Dots: Rasterising a 50×30 mm Label for a 576-Dot Printhead

How a label template in millimetres becomes a 1-bit raster for a 300 dpi thermal printhead: geometry, thresholding, whole-dot barcodes and why some designs cost more Bluetooth packets.

The NIIMBOT B1 Pro Bluetooth Protocol, Packet by Packet

The frame format, checksum, connect packet, identification reads and the full V4 print sequence I use to drive a NIIMBOT B1 Pro over Web Bluetooth, with what is verified and what is not.

Generating Barcode and Group SKUs in Excel From Spec Columns

How an Excel stock tracker built a per-brand serial that rolls past 99, a barcode SKU and a variant group key from spec columns with LET and TEXTJOIN, and where formula IDs break.

Printing Labels From the Browser Over Web Bluetooth: The RPOMS Print Engine

A scan-to-label workflow that drives a NIIMBOT B1 Pro directly from Chrome, with no vendor app and no backend. What the protocol work looked like, how 116 tests run without a printer, and what the first real print taught.