Case Study · Personal · 2026

Guest Relations Rounds

A hotel guest-relations team ran their daily rounds off a 131-task, 11-section checklist that had already survived one rebuild, from a paper PDF into a single-file web app. That app worked, but only on one phone at a time: close the tab and the day's ticks and remarks were gone, and a second phone on the same shift saw an empty list, not the one a colleague had already worked through an hour earlier. The brief wasn't a redesign, it was to make the same app agree with itself across every phone reading the same link, on hotel wifi, without turning a staff tool into something that breaks the moment the network does. It was also built almost entirely through an AI coding agent: the merge strategy, the Cloudflare Functions, and a second automated test suite proving two browser contexts actually converge were all specified and reviewed rather than hand-written. That part usually stays out of a case study. It's in here because the live deploy is where an AI-built backend stops being a demo — an invalid API token, a macOS permission wall blocking git, a native binary built for the wrong CPU architecture. None of those are design problems, and all of them had to be diagnosed and fixed before any of this was actually live.

Progress & Remarks
Daily Summary
Today's Round
The Problem

A hotel guest-relations team ran their daily rounds off a 131-task, 11-section checklist that had already survived one rebuild, from a paper PDF into a single-file web app.

My Role

End-to-end: research, flow architecture, high-fidelity UI, design system contribution.

The Approach

Patch-Level Merge, Not Full-State Overwrite

The Outcome

4/4: Cross-device sync checks, proven not assumed

Scope, Constraints & Reality

One shared link, no accounts

Everyone on a shift opens the same URL and sees the same checklist. Real per-user accounts would have added a login flow to a tool used with wet hands and a phone propped on a cart — the access gate deters casual entry, and the sync layer doesn't try to be more than that.

Offline is the default state, not a fallback

The original single-device app worked with zero backend. Sync had to be strictly additive: every fetch degrades silently on failure, so a bad wifi patch means the checklist keeps working exactly as it did before sync existed, just without other phones' edits until the network comes back.

No infrastructure to maintain

Cloudflare Pages Functions plus one KV namespace, chosen specifically because there's no server to patch, scale, or forget about, for a solo-maintained staff tool where 'someone keeps this running' was never going to be a real answer.

Built and shipped through an AI coding agent

Every line of the sync engine, the two Cloudflare Functions, and the cross-device Playwright test came from specifying intent and reviewing the result. The same agent then created the KV namespace, set the secrets, and worked through a live deploy failure by failure until it actually shipped.

The Work
Slide 01, uploading soon
Slide 02, uploading soon
Slide 03, uploading soon
Slide 04, uploading soon
Slide 05, uploading soon

Design Decisions

1

Patch-Level Merge, Not Full-State Overwrite

Two phones editing the same day at once ruled out “last full save wins” — one device's photo count would silently erase another's just-typed remark. The server merges per field instead: a tick, a remark, a summary line each write independently, so two people editing different parts of the same round within the same second never step on each other. One KV namespace and two small Functions handle this; Durable Objects would have added real-time guarantees this app, at hotel-shift scale, never needed.

2

A Pending Edit Always Beats A Stale Poll

Every device polls the server every 20 seconds. Without a rule, a poll landing mid-typing would overwrite a keystroke with whatever the server last had. The fix: any field with an edit still queued to send wins over incoming server data for that same field, unconditionally, until the send confirms. It's the one rule the whole sync engine depends on, and the one thing every mutation site in the app had to be threaded through consistently.

3

Photos Stay On The Phone That Took Them

Only a photo's id, timestamp and size sync, never the bytes. Uploading real images would mean object storage, a signed-upload endpoint, and a real answer for “whose photo is this” across devices, none of which the brief actually needed. What syncs is enough for every device to know a photo exists and when it was taken; opening it means being on the phone that took it — a deliberate boundary, not a missing feature.

Impact

4/4
Cross-device sync checks, proven not assumed

A second Playwright suite opens two separate browser contexts as a stand-in for two phones and checks that a tick and a remark made on one appear on the other, in both directions, plus that a day's data is visible server-side the way the archive depends on. All four checks pass against the real Functions + KV stack, not a mock.

0 bytes
Photo data ever leaves the device

Only id, timestamp and size sync for a photo. The image itself never crosses the network — verified by design rather than by omission, since there's no upload endpoint for it to reach.

800ms → ~1s
Edit to visible on another phone

An 800ms debounce on the writing device, then a network round trip to Cloudflare KV. The other phone sees it on its next 20-second poll, or immediately if it's mid-unlock, which triggers an out-of-cycle fetch.

Screens5
Click to expand · ← → to navigate