STRM/2.0
Internal report Workshop operations · Storm Rides

STRM 2.0: what it is, what it replaced, and what it costs.

A full account of the workshop management system now running Storm Rides' job pipeline — its feature set, its architecture, the AppSheet system it replaced, and a cost-benefit analysis grounded in the shop's own production data rather than projections.

Prepared by Caleb Dunthorne Date 19 July 2026 Live since 15 July 2026 (4 days of production use at time of writing) Data source live production database + git history
§ 01 — Executive summary

One system, individually-owned logins, running the whole shop.

STRM 2.0 replaced Storm Rides' AppSheet/Google Sheets setup with a purpose-built web app: one job pipeline, one parts catalog, one customer database, live Shopify invoicing, scheduling, printing and payments, each staff member signed in as themselves. It was designed and directed solo, mostly on unpaid overtime, and has been carrying the shop's live job board since 15 July 2026 — see §02 for who built it and how.

3,248
Jobs unified in one system
3,229 carried over from AppSheet, 19 created natively since go-live
$575,221
Lifetime charged revenue tracked
+$3,836 in real, actionable open quotes across 6 live jobs right now
8 of 12
Individually-owned staff logins
Was 3 shared logins under AppSheet's per-seat pricing
39
Versioned releases shipped
v0.3.0 → v1.3.0 across 17 calendar days
45,516
Lines of application code
318 TypeScript/TSX files, 98 API endpoints, 25 data models
~$5/mo
Hosting cost, all-in
Vs. $30/mo paid to AppSheet, $80/mo it should have cost
151
Peak-day logins, 16 Jul
Up from single digits the week before cutover
33 / 35
Self-filed issues already resolved
94% closed within days of go-live
§ 02 — Why this exists, and how it was built

A long list of pain points, directed into a fix.

This wasn't commissioned. I'm a Bachelor of Media Design student, not a software engineer — but I've been involved in this shop's day-to-day processes for close to four years, long enough to build up a running list of exactly where the old AppSheet system was getting in the way. That list is the actual origin of this project: not a brief handed to me, but friction I'd been feeling myself, workflow by workflow, for years.

~4 yrs
Hands-on shop & process experience
The domain knowledge behind every workflow decision in this build
$180 NZD
Self-funded to reach MVP
Paid out of pocket before approval or reimbursement was guaranteed
Media Design
Background — not software engineering
The build was directed, not hand-coded — see below

How it was actually built: the ~45,500 lines of code weren't hand-written by me — they were written by directing Claude Code, Anthropic's AI coding tool, across 39 releases of iteration. What I brought was everything a contractor would otherwise have had to spend weeks learning from scratch: what the ticket pipeline actually needed to look like, how warranty claims really get handled at the counter, what a technician's day requires, and every architecture and feature call across those 39 releases. I tested every release against real shop workflows, which is why 33 of the 35 issues I filed against it were closed within days of go-live. Claude Code wrote the code; I decided what to build, in what order, and whether it was actually right. That discipline didn't stop at go-live: building the Analytics dashboard (§04) the same way caught three real issues before any of them reached production — a client-side bundle accidentally pulling in the database driver, a staff-activity endpoint locked to a stricter role than its new home needed, and a permission check that had to grant one admin page to a restricted account without quietly opening the door to user management and data recovery alongside it. Each fix was confirmed against the running system before being marked done.

The bet: I put my own money behind it before asking for anyone else's. I self-funded roughly $180 NZD to get the project to a working MVP — enough to demonstrate it was real and worth a proper look — before pitching it for approval. That pitch was approved and the $180 was reimbursed; everything built since then (the AppSheet migration, go-live, and the releases that followed) happened on infrastructure the business now owns outright.

§ 03 — What STRM 2.0 is

The shop's operations, as one application.

STRM 2.0 is a web app (desktop and mobile) covering the full life of a job: a customer drops off a scooter, it's ticketed, quoted, worked on, invoiced through Shopify, and handed back — with every step, comment, and dollar attributed to the staff member who did it. It runs on Cloudflare's global network with a managed Postgres database, and is reachable from any device with a browser, not just the one computer the spreadsheet used to live on.

Core model

The ticket is the unit of work

Each ServiceTicket carries a sequential job number, a customer, one or more scooters (multi-scooter jobs are a single ticket with per-scooter parts and comments), a status across a 12-value pipeline grouped into 5 visible stages — Quoting → Approval → Working → Ready → Collected — and a full event timeline. Nothing is a static row in a sheet; every change is a recorded, attributed event.

Access model

Everyone signs in as themselves

Four roles — Admin, Technician, Developer, and a restricted "Limited" tier with per-page view/edit grants (e.g. the accountant sees Roster only) — replace the single shared AppSheet login the shop had been using to avoid per-seat licensing. See §08 for what that workaround was actually costing.

§ 04 — Feature tour

Everything the shop touches, in one place.

Grouped by the part of the shop's day it covers. All of the below is live in production, not a roadmap.

Job pipeline

Tickets, quoting & approvals

Quote → charge line states, partial-quantity approval (e.g. raising labour from 1 to 1.5 hours only charges the approved portion), technician notes that print straight onto invoices, and a searchable Service View grouping active jobs, customers, and completed/cancelled history.

Customers & scooters

One record per customer, ever

3,490 customers and 2,239 scooters, deduplicated by name/email/phone, with full service history per machine and a "rebooked" link when a completed job comes back in.

Scheduling

Drag-drop week calendar

Capacity = active technicians × a configurable daily budget (4 hrs/tech by default). Over-budget days flag automatically; jobs left un-started auto-nag, then auto-move to the next working day.

Booking portal

Customers book themselves in

Public /book page, no login: pick a scooter, describe the issue, choose an open weekday slot. Matches against existing customer records without ever leaking one customer's details into another's.

Parts & invoicing

Shopify draft & final orders

519-item parts catalog; charged lines become a Shopify draft or finalized order directly from the ticket — 8 real orders created this way since go-live, no re-keying into a second system.

Warranty

Automatic claim discounting

A YES/PARTIAL warranty claim auto-applies a 100%/50% discount to the Shopify order, linked back to the customer's original purchase picked from their real Shopify order history — no manual discount math.

Printing

Labels, quotes & invoices

QR job labels and status-correct documents (quote / warranty log / receipt / invoice) print through a small Windows helper that connects out to the app — any signed-in device, anywhere, can trigger a print at the shop without opening a port on the shop network.

Payments

EFTPOS from the invoice page

A second outbound-only Windows bridge lets staff start a Purchase transaction on the front-desk Windcave terminal directly from a ticket, with live status (Awaiting Card → Accepted/Declined) streamed to every staff member watching that job.

Realtime

Multi-device, always current

A Cloudflare Durable Object pushes live updates to every open tab — roster, job pages, and the schedule board refresh themselves. No "someone else has this job open" stale-data conflicts.

Roster

Staff scheduling & leave

A standing weekly pattern plus per-week overrides, rotating shift cycles, leave tracking, and a draft → publish workflow so a roster can be edited without going live mid-change.

Mobile audit

QR scanner in the browser

Scan a printed label to jump straight to its job, or run an "Audit" pass — pick an expected status and scan every scooter in that area, with a live count and a red flag on anything in the wrong place.

Customer self-service

A portal link, no account needed

Each ticket has an unguessable link the customer can use to check status, approve a quote, update their own contact details, or leave a note — without a phone call either way.

Admin & safety

Soft delete, recovery, oversight

Deleted jobs sit in Recovery for 7 days before permanent purge. A developer metrics page tracks logins and adoption; a bug-report button feeds a triage queue admins can resolve in-app.

Analytics

A Shopify-style KPI dashboard

Revenue, jobs booked, jobs completed, returning-customer rate, and hours tracked — five tiles with sparklines and period-over-period deltas, one date-range picker driving both a job-pipeline funnel and a top-parts-by-revenue breakdown. The same bar sits above the ticket table and on its own dedicated page — and is now grantable to a Limited account (e.g. the accountant) without opening user management or data recovery alongside it.

§ 05 — Technical architecture

Built to run on the shop's own infrastructure budget.

The stack was chosen to minimise ongoing cost and maintenance burden for a single-developer system, while still supporting realtime multi-device use, payments hardware, and a public customer-facing surface.

LayerTechnologyWhy
ApplicationNext.js (App Router) + TypeScript, Tailwind, shadcn/uiSingle codebase for UI, API routes, and server logic; strong typing across 318 files.
HostingCloudflare Workers via OpenNextGlobal edge network, no server to patch or reboot; ~$5/mo Workers Paid plan covers the shop's whole load.
RealtimeDurable Objects (one hibernating room per channel)Live roster/job/schedule updates and the label + EFTPOS bridges, at near-zero idle cost — rooms sleep when no one's connected.
DatabasePostgreSQL (Neon, ap-southeast-2) + Prisma ORM25 relational models, 40 migrations, transactional integrity for money-handling operations. Free tier at current scale.
AuthNextAuth v5 — email+password, and Employee ID + PIN for the shared shop terminalIndividual accountability without slowing down quick tech switching on the workshop floor.
CommerceShopify Admin GraphQL APIInvoicing and warranty claims write directly into the shop's existing Shopify store — no second ledger.
EmailResend HTTP APITransactional booking/password-reset/portal email; works on Workers' edge runtime (no SMTP).
Hardware bridgesTwo custom Windows tray apps (label printer, EFTPOS terminal)Outbound-only WebSocket connections — no open ports on the shop network, works from any device anywhere.
CI/CDGitHub Actions → Cloudflare Workers on every push to mainMigrations and deploys are automatic and repeatable, not a manual checklist.

Scale, in numbers: 25 database models, 12 enums, 40 applied migrations, 98 API endpoints, 318 TypeScript/TSX source files, ~45,500 lines of application code. Security posture includes role-based access control enforced at the middleware layer (pages and API methods), soft-delete with a 7-day recovery window on destructive actions, brute-force lockout on PIN login, and a full immutable audit-event log distinct from human comments.

§ 06 — Scalability & what else this could plug into

The cost curve and the revenue curve don't grow at the same rate.

Everything about how this is built — serverless compute, a managed database that scales with usage instead of a fixed server that has to be pre-sized, integrations added as independent bridges rather than rewrites — means growth doesn't carry the cost most software growth stories assume. The architecture has room to grow into a much bigger shop, and into systems well beyond the ones it already talks to.

Cost vs. revenue at increasing scale

NZD/month, log scale — hover a point for the Cloudflare/Neon/Resend cost breakdown
Revenue Infrastructure cost
The y-axis is logarithmic — each gridline marks a 10× step rather than an equal dollar amount — so both lines stay legible on one chart instead of cost flattening against revenue's much larger range. Compare the slope of each line, not the visual gap between them; a log axis narrows that gap by design. In real terms: at 50× today's volume, cost runs to roughly $850/month against roughly $1.15M/month in revenue — under 0.1% of it, and that holds at every checkpoint shown. Figures are NZD throughout (cost converted from USD vendor pricing at ~1.7 NZD/USD); revenue is the real average charged value per completed job (§09) times volume, cost is an illustrative band per stack layer, not a vendor quote.

What's actually assumed to scale here: job/data volume — not headcount, and not logins. Cloudflare's and Neon's costs are driven by requests and compute, which track how many people are actively using the system and how often, not how many records exist. This model assumes a bigger shop processes more jobs per person rather than hiring proportionally for every unit of growth — the same way it already runs today — so request volume (and the cost that follows it) grows much more slowly than raw job count. If staff headcount scaled 1:1 with job volume instead, the cost side would move up faster than shown here, though still nowhere near revenue at these dollar amounts.

Integration is already a proven pattern, not a plan

Three real systems are already wired in, each added without touching the others: Shopify (commerce, direct API), a Windows label printer (hardware, outbound bridge), and a Windcave EFTPOS terminal (payments, the same outbound-bridge pattern again). Same shape both times — a small standalone bridge that dials out to STRM, rather than STRM reaching into shop hardware. Whatever plugs in next follows the same playbook: an API where one exists, an outbound bridge where it doesn't.

Concrete example: the phone system

The owners have mentioned wanting STRM to work with WildJar, the shop's inbound call-tracking system. Phone numbers are already stored digits-only and matched the same way everywhere in STRM, which is exactly the join key a call-tracking integration would need — a call comes in, WildJar identifies the number, STRM matches it to the customer record automatically. Nothing's scoped or built yet; this is a real example of the kind of ask this architecture is set up to say yes to, not a promise of a specific delivery date.

§ 07 — The AppSheet migration

Four and a half years of shop history, carried forward — not restarted.

Nothing was thrown away. Legacy AppSheet/Google Sheets exports (jobs, parts logs, notes, technician time) were imported by a purpose-built, non-destructive script that resolves broken legacy reference codes, recovers technician timer data the original export never captured cleanly, and de-duplicates against whatever had already been entered natively. It was run twice against the still-live AppSheet system to reconcile the new database ahead of time, then a final pass on 15 July 2026 completed the switch-over.

Shipped release history

Every tagged version from first commit to today — 39 releases across 17 calendar days
v0.3.0
3 Jul
Ticket pipeline, statuses, scheduling UX foundation
v0.5.0
5 Jul
Shopify draft-order invoicing goes live
v0.7.0
6 Jul
Developer role, feature flags, leaderboard
v0.8.0
6 Jul
Warranty ↔ Shopify integration, customer merge
v0.9.0
7 Jul
Staff rostering: schedule, rotations, leave, publish
v0.9.3
8 Jul
Realtime layer: live roster, Limited accounts
v0.9.13
14 Jul
Job workflow overhaul, label print discipline
v0.9.17
14 Jul
Email templates, customer portal, EFTPOS, news feed
v1.0.0
15 Jul
Go-live. Email fully wired, cutover from AppSheet
v1.1.0
16 Jul
Customer communication overhaul
v1.2.0
17 Jul
Bulk actions from Overview
v1.3.0
19 Jul
Self-service email change, warranty portal pricing
§ 08 — Advantages over AppSheet

The shared-login problem, and what it was actually costing.

AppSheet charges per user. To avoid paying for the whole team, the shop ran on three shared logins for a staff of up to a dozen people. That workaround kept the subscription bill down, but it quietly gave up almost everything a multi-user system is supposed to provide:

Under the shared-login workaround: attribution existed only as a manual convention, not a system guarantee — staff picked their own name from a dropdown when logging an action (e.g. writing a comment after calling a customer), but nothing enforced it. A skipped step, a wrong name picked, or an edit left unattributed altogether was invisible to the system, and there was no login-level record of who was actually behind the screen at the time regardless of what the dropdown said. No way to give the accountant read-only access without also giving them edit access to jobs. No individual technician time tracking, so labour hours and productivity were never separable per person. No personal state — draft notes, saved filters, notifications — because "the account" wasn't really any one person. And a single leaked or forgotten password sat behind everyone's work at once.

Under STRM 2.0: 8 staff members sign in as themselves today (12 accounts total across Admin/Technician/Developer/Limited roles). Every status change, part added, quote approved, or order created is attributed automatically to the real, authenticated person who did it, in an immutable event log — no dropdown to remember, nothing to fake or skip (357 such events logged in the first four business days alone — see §09). The accountant's account is scoped to exactly the views they need. Technicians get individual timers feeding a leaderboard. A PIN-based quick-switch keeps the shared shop terminal fast without going back to a shared identity.

CapabilityAppSheet (as run)STRM 2.0
User accounts3 shared logins for the whole teamIndividual login per staff member, PIN quick-switch on the shop terminal
Who did whatSelf-reported per action (a name dropdown), unenforcedAutomatic, tied to a real login, permanently
PermissionsAll-or-nothing per shared account4 roles + granular per-page view/edit grants
Realtime multi-device syncSheet-style refresh/locking frictionLive push updates via a dedicated realtime layer
Shopify invoicingManual, separate stepDraft/final orders created directly from a ticket
Warranty discountingManual order discount mathAutomatic 100%/50% discount, linked to the original order
Customer self-serviceNone — phone/email onlySelf-service portal link: status, approvals, contact updates
Online bookingNonePublic booking page with live capacity
Label & EFTPOS integrationManual, device-boundPrint/charge from any signed-in device via outbound bridges
Deleted dataTypically immediate, unrecoverable7-day recovery window before permanent purge
ExtensibilityBounded by AppSheet's platformFully custom — new features ship as needed (39 releases in 17 days)
Monthly cost$30 USD (workaround) / ~$80 USD if licensed for real headcount~$5 USD, all-in
§ 09 — Usage data

What the production database actually shows.

Two honest categories here, kept separate throughout: inherited history — 4.5 years of real AppSheet-era job data, now unified into one system — and native activity — what's happened since STRM 2.0 itself went live on 15 July. The system has been carrying live production traffic for four business days at the time of writing; the numbers below are early, not mature.

Job volume, January 2022 – July 2026

Monthly job count by original entry date · dashed line marks the AppSheet → STRM 2.0 cutover
Hover any point for the exact month. July 2026 is a partial month (through the 19th) — not a real drop-off. The run-rate through H1 2026 (110–120 jobs/month) is now flowing through STRM 2.0 rather than AppSheet.

Jobs in vs. jobs completed, monthly

August 2025 – July 2026 · the one window where completion dates are reliably recorded
Jobs in Jobs completed
Two caveats apply: reliable completion dates only exist from August 2025 onward, so this can't be shown against the full history in the chart above, and the last 1–2 months are still filling in — some of June and most of July's intake hasn't had time to finish yet, so those bars will keep rising. Even accounting for that, intake grew ~52% from the Aug–Dec 2025 average (~76/month) to the Feb–Jun 2026 average (~115/month), while completions over the same windows grew only ~39% (~61/month to ~84/month) — a monthly shortfall that has grown both in absolute terms and as a share of volume. That's a trend to watch, not a diagnosis of its cause.

Daily logins since go-live

8 Jul – 19 Jul · 439 logins total across 7 distinct staff so far
The ramp from single digits to 151/day tracks cutover week (14–17 Jul) training and rollout, settling into steady daily use.

Audit-trail activity, first 4 business days

Attributed events logged since 15 Jul — a category AppSheet's shared logins never produced
357 attributed events in total across 17 types.
Native tickets

19 jobs created in STRM 2.0

3 on cutover day, 7 the next day, 6 the day after — created directly in the new system rather than imported, in the first five calendar days.

Shopify integration

8 real orders generated

1 draft + 7 finalized Shopify orders created directly from tickets since go-live — invoicing with zero re-keying into a second system.

Dogfooding

35 issues filed, 33 resolved

28 filed in a single concentrated testing session on cutover day; 94% were resolved within days — a real-time bug bar, not a support backlog.

Data quality

A legacy date error, caught and fixed

Building the Analytics dashboard surfaced a still-active job (Roger Daniel) with an entry date stuck in 2022 from the original import — invisible in a spreadsheet, obvious the moment it showed up alongside real, current jobs. Corrected in the local database; the same fix against production is still pending.

§ 10 — Cost-benefit analysis

What it cost, what it costs now, and what it replaced.

AppSheet is fully sunsetted: billing cancelled, no longer the operational system in any capacity. The original app stays reachable, but only as a read-only reference for cross-checking a handful of very old jobs where the initial data port didn't carry across cleanly — not a parallel system, and no ongoing cost. The $30/month figure below is what the shop no longer pays.

Monthly cost comparison

USD, as actually billed / as it would need to be billed for honest multi-user access
Actually paid Would-be cost, honestly licensed STRM 2.0, actual
"Honestly licensed" assumes AppSheet's ~$10/user/month rate applied to the shop's 8 currently-active staff, rather than the 3-account workaround actually in use. STRM 2.0's figure is the Cloudflare Workers Paid plan ($5/mo); Neon Postgres, Resend email, and GitHub Actions are all still within their free tiers at the shop's current data volume.

Direct savings

$30 → $0 per month: AppSheet's subscription is cancelled, not just superseded by daily use of STRM. STRM's own ~$5/month hosting is the only line item left, so this ~$300/year swing against the old workaround is already realized.

$80 → ~$5 per month = ~$900/year versus what AppSheet would have cost to run honestly, with one login per person, instead of the 3-account workaround.

Build investment

Reconstructed from commit timestamps: 28 distinct working sessions across 12 active days within a 17-day span, clustering to an estimated 40+ hours of directing and testing the build — almost certainly more once design thinking and review between commits is counted. Some of that time fell inside working hours; most was unpaid overtime, on top of regular duties. $180 NZD of that investment was also personal money, risked before there was any approval or guarantee of reimbursement (see §02).

Market reference point (not an actual quote, and not a like-for-like one): a NZ software contractor or agency building equivalent bespoke workshop-management software — ticket pipeline, RBAC, realtime sync, Shopify integration, custom payment-terminal and label-printer bridges, and a public booking/self-service portal — would typically be quoted in the $15,000–$40,000+ NZD range for the build alone, before ongoing hosting, and before the AppSheet migration and data-cleanup work this project also did. This is a benchmark for the scope of what got built, not a claim that directing an AI is equivalent to hiring a contractor. A contractor's price also buys accountability — someone to hold to a warranty, someone to call if it breaks — and that doesn't transfer the same way when the code is AI-written. That's a real difference, not a footnote.

Payback: because the ongoing infrastructure cost (~$5/mo) is already below what was being paid for the old system (~$30/mo), the hosting cost alone pays for itself immediately — every month running STRM 2.0 costs less than the AppSheet subscription it replaced, on day one, regardless of how the build time itself is valued. The larger return is operational: per-user accountability, an audit trail that didn't previously exist, integrated payments and invoicing, and a customer self-service surface — value AppSheet's platform was not going to deliver at any price point without a rebuild of its own.

Ongoing development cost: the ~$5/month figure above is hosting only. Any future feature work beyond what's already shipped — the "can we also add X" requests that come up once a system like this is in daily use — depends on continued access to an AI coding subscription (Claude) to keep building at anything like this pace. Storm Rides already plans to get one regardless of this report, so it isn't a new cost — it's the real ongoing cost of extending this system, separate from hosting. Claude subscriptions start at roughly US$20/month (Claude Pro), scaling up for heavier use — a small fraction of a single hour of contractor time.

§ 11 — Risks, limitations & what's next

Where this is genuinely still early.

Maturity

4 days of live production data

Every usage figure in §09 will firm up over the coming weeks. Treat the login ramp and native-ticket counts as an early trend, not a settled baseline.

Continuity

Less of a bus-factor risk than it looks

The code wasn't hand-built from one person's tacit knowledge — it was written by Claude Code and documented for exactly this handoff scenario. See §11 for what actually would, and wouldn't, be a problem if I stepped away.

Parts catalog

Not yet linked to live Shopify sync

All 519 parts currently come from the imported legacy price list; none are yet linked to a Shopify variant ID, so price/stock sync from Shopify — built and available — hasn't been switched on for the catalog yet.

Booking & EFTPOS

Low volume so far

The booking portal has taken 1 request and the EFTPOS bridge just shipped; both are freshly live features whose adoption will build over the coming weeks, not yet a mature channel.

Domain

Still on the default workers.dev URL

Email itself is fully sorted — a verified sending domain, live since v1.0.0 (noreply@service.stormrides.nz, replies routed to support@stormrides.nz). The real open item is DNS: stormrides.nz isn't hosted on Cloudflare, so the app can't claim service.stormrides.nz as its own hostname yet and sits on strmservice2.stormrides.workers.dev instead.

Insurance

No dedicated quoting workflow

Insurance-claim quotes are still handled outside the normal ticket flow — there's no workflow that produces the format an insurer expects or tracks a claim distinctly from a regular customer job.

Shop-owned jobs

Demo & showroom scooters aren't separated

Servicing the shop's own demo/showroom stock currently runs through the same pipeline as real customer jobs, under a generic "Storm Rides" customer record — no separate pool, so it can collide with genuine customer bookings, and technician time on this work needs to keep counting toward their output rather than disappearing into an edge case.

Roadmap

Near-term next steps

Move stormrides.nz's DNS to Cloudflare for a proper hostname, run the Shopify parts sync to link the catalog live, build an insurance-quoting workflow and a separate shop-owned/demo job pool, and let 30–60 more days of production data mature before revisiting these figures.

The bigger gap: fairness in how output gets compared. Every technician stat in this report — jobs completed, hours logged, the Leaderboard — is a raw total, and a raw total flatters full-time staff over part-time ones by construction. Troy works roughly half the hours Jordan does; on every current metric that makes Troy look like he's produced half as much, when the honest comparison is output against the hours he was actually rostered for. As it stands, a part-time technician has to work twice as hard per rostered hour just to land in the same place on the board as a full-time one. The data to fix this already exists — rostered hours are tracked per person per week — the missing piece is a fairness-adjusted view (output per rostered hour, not just output) on the Analytics dashboard and the Leaderboard, so the comparison is against each person's actual availability, not everyone else's.

§ 12 — Continuity: what happens if I'm not here

The honest version of "what if I got hit by a bus."

Short version: STRM 2.0 doesn't stop running without me, and it doesn't need me specifically to keep being extended. A couple of concrete things are still worth tidying up now, while they're an easy fix, rather than being discovered the hard way later.

Keeps running, no question

The live system doesn't depend on me

STRM 2.0 runs on Cloudflare's infrastructure and a managed Neon database, deployed and live right now. It doesn't sit on my laptop or need me signed in to keep serving the shop's job board day to day.

Low risk

The code isn't locked in my head

It wasn't hand-built from tacit knowledge only I have — it was written by Claude Code, documented along the way (this report, plus the in-repo AGENTS.md/CLAUDE.md conventions file), with a full decision trail across 39 versioned releases of git history. Anyone with repo access and a Claude subscription could point Claude Code at it and pick up exactly where I left off — that's genuinely how this project itself started.

Already handled

Every core account is company-owned

GitHub, Cloudflare, Neon, and the Shopify app credentials were all set up under Storm Rides' own support@stormrides.nz from day one of the project, not a personal account — the Cloudflare account itself confirms this directly. Not a single-point-of-failure the way it might look from the outside.

Real risk

Business knowledge, not code knowledge

The harder-to-transfer part isn't the codebase — it's the four years of shop-floor context behind why things work the way they do. Some of that is now captured in this report and in the product decisions already shipped; the rest is worth writing down as it comes up, the same way AGENTS.md already captures the technical conventions.

What's actually left to do: keep AGENTS.md updated as a living document alongside future changes, so the next person — or the next AI-directed session — inherits the same context this one did. Beyond that, the honest answer is that this system was built by directing an AI coding tool that's available to anyone, not by writing code only one person can read — which makes continuity a smaller problem here than it would be for a traditionally hand-coded system maintained solo.

§ 13 — Beyond Storm Rides: a SaaS possibility

Speculative, not a plan — but the architecture already has the bones for it.

Everything below is a "what if," not a proposal in motion. STRM 2.0 was built for one shop. But nothing about the way it's built is Storm Rides-specific by necessity: the centralised access-control middleware, the single coherent data model, and the channel-based realtime layer are exactly the seams a second tenant would hook into, rather than a rewrite. This section names that possibility; it isn't a recommendation to act on it now.

What already points the right way

Every page and API route already passes through one central permissions layer (src/middleware.ts), not scattered checks — a tenant boundary slots into the same place. The 25-model schema is already one coherent design, not years of organic sprawl. The realtime layer is already channel-based ("roster", "label-print", "eftpos") — extending a channel name to "roster:<shop-id>" is a small change, not a new subsystem. None of this makes it a weekend job — it makes it a retrofit instead of a rebuild.

What would actually have to change

Every one of the 25 data models and 98 API routes needs a tenant boundary added and audited. Shopify integration moves from one hardcoded store token to a per-tenant OAuth connection. The label-printer and EFTPOS bridges move from secrets baked in at build time to a per-shop provisioning flow. Billing, self-serve signup, shop-specific settings (hours, tax rate, branding), and support/documentation for people who aren't already living in this codebase all need to exist.

WorkstreamEst. hours
Multi-tenant data isolation across all models & routes70–120
Self-serve signup & subscription billing35–60
Shopify OAuth (per-tenant store connections)25–45
Hardware-bridge provisioning (label + EFTPOS, per shop)20–35
Configurable branding, tax rate, shop hours per tenant15–25
Docs, onboarding, basic support tooling15–25
Security & data-isolation hardening for other businesses' customer data20–30
Total, to a sellable multi-tenant MVP~200–340 hrs
Rough, top-down estimate at a similar AI-directed pace to this build — not a scoped quote. At a sustainable part-time pace alongside other work, that's roughly 3–6 months.

Illustrative revenue scenarios

Purely hypothetical — no market sizing or customer validation has been done. Shown to illustrate shape, not to forecast.
Paying shopsAt $99/moAt $199/mo
5 (a first pilot cohort)$5,940/yr$11,940/yr
20$23,760/yr$47,760/yr
100$118,800/yr$238,800/yr
Reference point: comparable vertical shop-management SaaS for small service retailers (auto/powersports repair shops, bike shops) typically prices in the $100–$400/month-per-location range. STRM 2.0's natural first market is other e-scooter, e-bike, and micromobility repair shops — a small, specific niche where the workflow (ticket → quote → warranty → invoice → schedule) transfers directly; broader small-workshop retail would be a later, larger, less-validated step.

Intent: this section is an observation of what's technically possible, not a pitch. STRM 2.0 was built on Storm Rides' funding and data, so anything beyond internal use is entirely the business's call — this section exists only to name that the option is there.