An operations portal for out-of-home transit advertising — 860+ campaigns, 1.3 MB, zero build tools.
The system a transit advertising operation actually runs on: campaign tracking, installation scheduling, hold management, material logistics, proof-of-posting audits, and multi-carrier shipment tracking.
It ships as a single-page app with no build step at all — React and Tailwind come from a CDN, JSX is transpiled in the browser by Babel Standalone, and deployment is a file upload. No bundler, no Node on the server, no dependency tree to keep patched. That was a deliberate architectural choice: the app has to outlive my attention, and a build pipeline is the thing that rots first.
On top of that sits Victor, an AI concierge with deterministic intelligence — it answers from live campaign state (“tell me about the shipment for that Western Union run”) rather than hallucinating from a prompt.
The decisions that carry the app.
IndexedDB for bulk records, localStorage for settings and overrides, a Cloudflare KV mirror for cross-device sync, and Google Sheets as the upstream source of truth. Merge-on-read, so a stale layer heals instead of winning.
The primary navigation is a hand-rendered HTML5 canvas gear assembly, not a component library. It draws in one pass and costs nothing on re-render.
Two roles gate every mutation. The mobile surface was deliberately retired once its only gate turned out to be a forgeable localStorage flag — shipping less was the correct fix.
Pulled straight from the production codebase — unedited except for trimming.
SETTINGS_KEYS list covered only 18 of the ~50 keys actually in use, because curated lists drift as features land. Sweeping by prefix and excluding two keys by name inverts the default — new state is backed up automatically, and the session token is explicitly denied so a login never rides along to another machine. // localStorage capture is a PREFIX SWEEP, not a curated key list. A curated
// list is what drifts — the existing SETTINGS_KEYS covers only 18 of ~50 keys
// actually in use. Everything this app writes is stap_/STAP_ prefixed.
var LS_PREFIXES = ['stap_', 'STAP_'];
var LS_EXCLUDE = [
'STAP_SESSION', // login session + role — must never transfer machines
'stap_field_last_push' // meaningless off-machine
];
function isBackupKey(key) {
if (!key) return false;
if (key.indexOf('demo_') === 0) return false; // demo app state
if (LS_EXCLUDE.indexOf(key) !== -1) return false;
for (var i = 0; i < LS_PREFIXES.length; i++) {
if (key.indexOf(LS_PREFIXES[i]) === 0) return true;
}
return false;
}
function collectLocalStorage() {
var out = {};
try {
for (var i = 0; i < localStorage.length; i++) {
var k = localStorage.key(i);
if (!isBackupKey(k)) continue;
var v = localStorage.getItem(k);
if (v != null) out[k] = v;
}
} catch (e) {
console.warn('localStorage sweep failed:', e);
}
return out;
deleteDatabase() only completes once every connection to the database closes — including this page's own — so calling it alone can silently no-op and leave the data sitting there. Clearing every store first guarantees the data is gone regardless, and enumerating the databases means a store added next year is still wiped.// 1. IndexedDB — clear every store first, THEN drop the database. Clearing
// first guarantees the data is gone even if deleteDatabase() is blocked by
// this page's own open connection (deleteDatabase only completes on close).
try {
if (window.STAP_IDB && window.STAP_IDB.listStores) {
var stores = await window.STAP_IDB.listStores();
for (var i = 0; i < stores.length; i++) {
try { await window.STAP_IDB.clear(stores[i]); } catch (e) {}
}
}
} catch (e) { console.warn('Wipe: IDB store clear failed:', e); }
try {
if (window.indexedDB) {
if (indexedDB.databases) {
// Enumerate — never a hardcoded subset. A store added later must not survive.
var dbs = await indexedDB.databases();
dbs.forEach(function (d) { if (d && d.name) indexedDB.deleteDatabase(d.name); });
} else {
indexedDB.deleteDatabase('stap_ops_hub');
}
}
} catch (e) { console.warn('Wipe: IDB delete failed:', e); }
From the running application.


The repository is private, but I'm glad to walk through the codebase live — architecture, tradeoffs, the parts that went wrong first.