Greenridge — A trustworthy mirror for an analog campground board.

Green Ridge campsites are first-come, first-served — ground truth lives on a clipboard at HQ. This case study documents how the app mirrors that board with visible freshness, human review, and a production-linked design system.

Live product · Open PWA · Product case study · Interactive design kit

01 — The Problem

Ground truth lives on a physical sheet.

Green Ridge doesn’t expose a reservation API — the Forest Service manages a first-come, first-served signup sheet at headquarters, staffed during limited hours. The operational ground truth lives on that physical board. Campers want planning context (what kind of site is this?) and situational context (what did the board say recently?). A polished “availability” UI that pretends to be authoritative would erode trust faster than no app at all.

The design problem became: help people browse and compare sites honestly, surface freshness of clipboard-derived state, and make contribution at HQ feel fast and cooperative — not extractive.

The clipboard at headquarters is a single physical artifact. It lives inside one building, during posted hours, and it can only be read by someone standing in front of it. There is no digital backup, no historical record, and no way to query it remotely. If you want to know what’s open, you drive there. That constraint shaped every product decision that followed.

First-come camping roster on a clipboard at Green Ridge headquarters.
HQ clipboard — see on product case study

02 — Design Process

Full ownership, no handoffs.

The build wasn’t sequential phases — design, then engineering, then ops. It was concurrent decisions made by the same person. Three choices shaped everything.

Greenridge site detail — planning context before you drive in.

The artifact defined the architecture

The physical signup sheet at HQ didn’t just supply data — it supplied the mental model campers already use on-site. That constraint drove the information architecture more than any wireframe: the grid they reason about at headquarters had to be the grid they see in the live app.

Greenridge map with campsite pins and freshness.

Honesty as a design primitive

Every availability state — fresh, stale, warn, archived — had to be designed twice: once for browsing (map and sites list) and once for contributing (the clipboard flow). Uncertainty is the product condition, so freshness language became a first-class design element everywhere.

Greenridge design kit — semantic color tokens across day, sunrise, sunset, and night phases.

Coherence without handoffs

The semantic design token system and functional design kit weren’t luxuries; they were the mechanism that kept the story consistent across map, admin, marketing, and native shells while surviving pivots. Explore the interactive kit — it imports real production components and can’t drift from the app because it is the app.

03 — Field & Flow

Clipboard capture.

Photography is the refresh mechanism: a camper at HQ captures what’s on the board so everyone else gets a more current picture — without claiming an official reservation.

Two entry points: the in-app update-from-clipboard flow for signed-in users, and the standalone photo-clipboard landing for social/share campaigns — same API host, consistent outcome semantics.

Field constraints: copy favors “photo” over “scan,” mobile-first layouts, and server-side checks that reinforce trust — validating image bytes and dimensions, optional EXIF capture date, and an HQ proximity / GPS gate when metadata supports it.

Outcomes users see: uploads can become live availability when parsing is confident; land in pending review when the model or validator is unsure; reject when the image isn’t usable; or join the historic archive when the sheet isn’t current-cycle.

The operational artifact in the field: the product mirrors this object—not a reservation database. Tap the card to open the full clipboard photo flow.

04 — Vision & Backend

Clipboard pipeline and vision model.

The vision layer doesn’t “guess reservations.” It extracts structured rows from a photo; the server validates JSON, applies privacy rules, and routes uncertain work to humans.

01Upload

Multipart image

POST /api/sheet; Cognito-linked identity where required.

02Hygiene

Validate bytes

Magic-byte check, strip/re-encode, dimension limits, optional preprocess before the model call.

03HQ Gate

Proximity check

EXIF GPS vs headquarters radius when present; skip when metadata doesn’t allow a fair check.

04Vision Parse

Structured JSON

Multimodal call returns rows, sites, dates — retries with backoff on bad output.

05Validate

Schema + rules

Server schema validation; low confidence routes to pending review, not silent publish.

06Store

Redact + archive

Redacted public archive image; duplicate detection via content hash.

Human-in-the-loop: uncertain parses surface in admin review — trust is a workflow, not only a model score. Admin-validated snapshots feed back into the model as a curated training corpus.

05 — Accuracy Loop

ML feedback loop.

Every admin-validated snapshot is a training signal. Corrections made in the review UI feed directly into re-parse quality and the fine-tuning corpus — no separate annotation workflow.

ML training export admin screen: snapshot list, HQ clipboard photo with zoom, editable parsed rows, checkpoint save, and JSON/ZIP download.
ML training export — inspect snapshots against the archived HQ photo, correct rows, checkpoint sets, export JSONL for fine-tuning.
01Admin Verify

Row-by-row editor

Approve, correct dates, split confident vs. uncertain. Archived photo with zoom/pan.

02Guided Re-parse

Anchor + re-run

Validated rows injected as human-verified anchors; model surfaces missed sites.

03Curate

Build the corpus

Snapshots marked for export build the corpus with production preprocessing.

04Fine-tune

JSONL → deploy

JSONL export → OpenAI fine-tune → model ID via OPENAI_SHEET_MODEL.

Dynamic few-shot also runs in parallel — top-N validated snapshots injected as live examples into every prompt, no training run required.

06 — Surfaces

The product, four ways.

Time-of-day phases remap the same semantic CSS variables on :root — components don’t fork, tokens do. The interactive kit imports real components and global CSS so the explorer can’t drift from production.

Longform

Product case study

Hero, pipeline diagrams, ML loop, kit explorer, and bookmarks — the full narrative on the product host (companion to this studio page).

UX & Design Kit

Tokens, not forks

Production-linked kit: semantic tokens power the live app; phases remap CSS without forking components.

Engineering & Ops

Cognito → Caddy → Node

Sheet uploads, vision parse, snapshot lifecycle, and admin review on a Cognito-protected API; Caddy → Node on Lightsail. Pipeline detail on the product case study.

Marketing

Same host, different door

Standalone HTML landing with OG cards, hero narrative, and TestFlight interest capture on the same host API.

Live product

PWA + Capacitor

Map, sites, guide, and clipboard capture — web deploy reaches TestFlight / Play internal users via Capacitor shells loading production.

07 — Takeaways

Five lessons that generalize.

State where authority lives

Booking stays on the sheet; the app describes last known state and freshness. The architecture refuses to overclaim what it actually knows.

In uncertain domains, provenance beats polish

Stale, warn, fresh, and archived semantics align map, list, and upload outcomes. Users can act on the data because they can see how old it is.

Domain feedback loops beat generic dashboards

Parsed rows, favorites, and historic stays answer “is this working?” — concrete domain signals replace abstract usage metrics.

Cleanup is design

Renaming flows, trimming chrome, and fixing mobile keyboards changed trust as much as new features.

AI as collaborator and runtime

Assistants accelerated shipping; vision parsing makes the analog board legible — within guardrails.

08 — Reflection

Uncertainty is the product condition.

Most product work assumes a stable source of truth and designs the interface around it. This one didn’t have that luxury. The truth lived on a piece of paper that only a few people could see at a time. Designing for that meant treating uncertainty as a feature of the system, not a bug — and building every layer, from UI copy to vision pipeline, to honor it.