Coastal Film Lab

Twin Check'n

A Shopify order tells you what someone bought. It says nothing about where their film physically is. Twin Check'n is the system that answers that question — for the lab and for the customer.

Built
October 2025 — present
Status
In daily production
Surface
Shopify embedded app

Image slot

Customer dashboard comp
public/twincheckn/film-status-page.webp

The problem

The gap between a sale and a roll of film

Coastal Film Lab takes orders through Shopify. But a Shopify order is a sales record — one line saying a customer paid to develop four rolls. It has no concept of a roll as a physical object moving through a darkroom.

So the lab tracked them the way labs always have: on paper, on whiteboards, and in a desktop app nobody trusted. A roll's location lived in whoever's head last handled it.

Three things followed from that. Customers had no way to know where their film was, so they asked — constantly. Nobody could say who had processed what, so there was no attribution and no productivity signal. And when a roll went missing, there was no record to reconstruct.

The unit of work in a film lab is the roll. Nothing in Shopify knows that.

Data model

One order becomes many rolls

When an order lands, a webhook parses its line items and explodes them into individual Roll records — each with its own barcode, film stock, chemistry, add-ons, and rush tier. Every status change writes an immutable log entry attributed to the staff member who scanned it. That log is what everything else is computed from.

  1. 01

    Order

    Mirrors the Shopify order. Holds the customer, notes, rush priority, and rolled-up status.

  2. 02

    Roll

    The real unit of work. Barcode, film stock, format, chemistry, add-ons, per-stage value.

  3. 03

    RollStatusLog

    Append-only. Who moved this roll, from what to what, and when. The source of every metric.

Default roll lifecycle

  1. Frame 01Awaiting Check-In
  2. Frame 02Received & Checked In
  3. Frame 03Developed, Awaiting Scanning
  4. Frame 04Scanned, Awaiting Delivery
  5. Frame 05Scans Delivered
  6. Frame 06Ready for Pick Up

The lab floor

Four stations on the floor

Each station is built for one job, done with a barcode scanner in one hand. None of them require typing if the scan works.

Intake

Check In

Scan the receipt barcode or type an order number. Assign film stock per roll from 33 presets, attach add-ons, flag shipping. Then print roll labels straight to a Zebra printer as raw ZPL. Warns if the order changed since labels were last printed.

Advance

Order Updates

Scan a roll, advance its status. Works at roll scope or whole-order scope. Rolls the parent order's status up from its children, auto-fulfills in Shopify past a configurable threshold, and fires the customer email for that milestone.

Look up

Status Flasher

Scan a roll and its status, service, chemistry, and add-ons flash up in large colour-coded type. No navigation, no reading. A lookup gun for a technician holding film in the other hand.

Ingest

Scanning

The scanner workstation. Talks to the local scan agent, picks profiles, previews candidate folders, runs dry runs, polls async job progress, and supports rescans and rollback — with a manual fallback when the agent is offline.

Storefront

The customer watches their own film move

A theme app extension puts two blocks on the storefront. One shows live turnaround times computed from real status logs, with trend arrows. The other is the customer's own dashboard — every order, every roll, and exactly where each one is.

The progress bar is a strip of 35mm film. Each frame is a stage: check in, develop, scan, quality check, receive. It reads instantly to the kind of person who sends film to a lab.

  • MY FILM ORDERS
  • YOUR FILM HAS BEEN DEVELOPED!
  • ORDER NOTES: No Notes! Yay!
  • DOWNLOAD YOUR SCANS

Image slot

Storefront dashboard
public/twincheckn/customer-dashboard.webp

Measurement

What the log makes knowable

Because every status change is timestamped and attributed, the numbers fall out of the data rather than being entered by anyone.

Turnaround

Median time between stages, computed from status-log deltas across rolling 7, 14, 30 and 60-day windows, with trend arrows against the prior period.

Throughput per technician

Checked in, developed, scanned, delivered, cut and sleeved — counted per person, with peak-window highlighting and a printable report.

Revenue by stage

Each roll carries its line-item value, split across the stages it passes through. Production work gets attributed in dollars, not just counts.

Settings

The lab configures it, not a developer

Nearly everything a lab would want to change is a setting rather than a deploy. Six tabs cover it.

Setup

  • Scanner agent
  • Product classification tags
  • Roll title mappings
  • Film stocks
  • Issue types

Statuses

  • Custom order statuses
  • Status colours
  • Active roll configuration
  • Fulfillment threshold

Notifications

  • Per-status email templates
  • Template variables
  • Test send

Client Dashboard

  • Dashboard theme
  • Progress bar stages
  • Visible fields

Metrics

  • Turnaround windows
  • Team productivity
  • Value split

Labels & Forms

  • Label dimensions
  • Label layout
  • Order form template

Architecture

Two problems worth the detail

01

The browser as a bridge

The app runs on Heroku. The Zebra label printer and the scanner's network storage live on the lab's LAN. There is no route between them, and there shouldn't be — punching a tunnel from a cloud host into a lab network to print a label is not a trade worth making.

So device work goes through the one machine already on both networks: the technician's browser. The page holds a session with the server and speaks to hardware on localhost. That means per-route CSP carve-outs, a CORS surface on the local agent, and handling Chrome's Private Network Access preflights — but the lab network stays closed, and the deployment stays a plain twelve-factor app.

02

A scan pipeline that can prove it didn't lose anything

Runtime
.NET 9, cross-platform
Surface
Local HTTPS API on :3625, API-key auth
Shell
Avalonia GUI — Windows, macOS, Linux
Imaging
BMP to TIFF with verified pixel equality

Scans are irreplaceable. The negative can be re-scanned, but the customer's film has already left, and a roll that vanishes between the scanner and the delivery folder is not recoverable by trying harder.

So file movement is a companion .NET 9 agent with an explicit safety contract, not a file move. Copy first, verify SHA-256, and only then delete the source. Never overwrite: identical bytes are an idempotent no-op, differing bytes become -v2, -v3. Files the pipeline doesn't recognise go to a _review/ folder rather than being deleted. Source folders are archived to _processed/ rather than removed. Every committed roll folder gets a JSON manifest written beside it.

It ships scanner profiles for Frontier polling, Frontier sentinel files, and Noritsu daily-folder watching. All of them fail closed on timeout — a stalled scanner produces an error, never a partial commit.

Under the hood

Stack

Application

Framework
React Router v7, SSR
Platform
Shopify embedded app
UI
Polaris v13, App Bridge v4
Build
Vite 6, Node 20+

Data

Database
PostgreSQL
ORM
Prisma 6
API
Shopify Admin GraphQL
Migrations
35 and counting

Hardware & delivery

Labels
Zebra BrowserPrint, raw ZPL
Barcodes
JsBarcode
Documents
jsPDF
Email
SendGrid

Companion agent

.NET
9.0
GUI
Avalonia
Transport
Local HTTPS, API key
CI
GitHub Actions

Today

Where it stands

Twin Check'n has run Coastal Film Lab's production floor since October 2025. It is not a prototype and never had a launch — it went in one station at a time, and the lab stopped using paper.

commits
179
schema migrations
35
admin surfaces
11
months in production
10