Product Story

Hum Website Editor

A Content Seam for a Live Restaurant Site — Then the First Write-Capable Module Inside a Multi-Tenant Hospitality Platform

At a Glance

Role Product Designer + AI-Directed Developer
Ownership Problem framing, tenant-isolation model, editor IA, publish-safety architecture, demo-scope call
Implementation Claude Code — safety-critical behavior independently verified before shipping
Timeline August 2026 — two connected phases across roughly two weeks
Stack HTML · CSS · Vanilla JavaScript · JSON (site) · Python/FastAPI · PostgreSQL · React (Hum module)
Tools Claude · Claude Code
Key Outcomes A production CMS module inside an existing multi-tenant SaaS platform, with real database-level tenant isolation · The platform's first write-capable external integration (GitHub) · A two-layer publish-safety system, built and adversarially tested in direct response to a real incident

Overview

The client runs Hum, a multi-tenant SaaS platform for her hospitality group, and The Bend NWA, a trailside restaurant in Bentonville, Arkansas, running on Squarespace. She was locked into Squarespace's content model with no way to bring the site under her own platform's control — and even after a migration, still no way to edit a live public site without going through a developer for every small change.

She needed ownership of a live site without a developer in the loop. Two phases: rebuild the site around a single content seam, then hand that seam to a real editor she could use herself.

Live Site

View Live Site

The Problem

For the business owner, the actual problem was never the restaurant's homepage. It was ownership: she couldn't change a photo, fix a typo, or add an event without asking someone else to touch code — and once handed a real CMS, she'd have no way to trust that publishing a change wouldn't break something she couldn't see.

The core insight

Build a clean seam between content and code, so the system in front of a non-technical operator can change without anyone touching the system underneath it. Phase one built that seam. Phase two proved it could be handed to someone else.

Constraints

  • Scope discipline, phase one: strict 1:1 recreation. No design changes, copy preserved as-is. A new brand guide existed mid-project and was deliberately not applied — this was a migration, not a redesign.
  • Scope discipline, phase two: built inside a live, multi-tenant production codebase — existing tests, an established security model, other tenants' data in the same database.
  • Stakes: publish triggers a real deployment to a real external system. No staging environment to hide a mistake in.
  • Budget: informal, trust-based engagement. Scope grew past the original estimate once real safety requirements surfaced mid-build — named directly with the client, not absorbed silently.

Approach

Phase one: the seam

Every string, image path, and form field lives in one content.json, read by a small vanilla-JS data-binder — nothing hardcoded into markup. Getting there required a full headless-Chrome scrape: the site's events calendar and contact form were JS-rendered and invisible to a static crawl. That file became the contract phase two built on, without a rewrite.

Phase two: three problems, one module

Isolation

The natural way to reference a brand in the new tables was a numeric foreign key. During review, that choice was caught as a real gap: a query scoped only by that ID would have returned another tenant's site content instead of this client's — the class of bug that produces cross-org leakage. The fix was corrected before any code shipped, and the isolation guard itself was proven by deliberately breaking it, confirming the test caught the break, and restoring it.

Editor IA

The schema is large and unfamiliar — theme colors, navigation, image galleries, multi-field forms. Two views handle it, because the business owner thinks about the site two different ways depending on the task: a page-based structure that matches "the menu page" or "the calendar," and a content-type view whose list is scoped to the page in hand — Content, Images, Links, Colors, Typography, and Radius at the site level, narrowing to Content, Images, Forms, and Colors on Party Inquiry — matching "swap the hero photo" or "fix the hours everywhere they appear." Both read and write the same underlying draft, so switching views never resets what she's doing.

Adding a new item follows the same logic instead of dropping her into a blank form. Adding a gallery photo infers its alt text, caption, and slot from the photos already there — not a blank twelve-field form she has to guess her way through.

Publish safety

Every edit stages as a draft with zero live effect until an explicit publish action — including newly uploaded images, held in the database and discardable before ever reaching the live repository.

The deploy target is where this stops short of finished: the Vercel preview domain is confirmed working end to end, while cutting the production domain over to it remains an open item — which is why the live link at the top of this page points at the .vercel.app address rather than thebendnwa.com.

The Incident

Mid-build, a screenshot of the editor's own interface was accidentally uploaded and published as a live image on the site.

  1. A local development environment held a live, production-capable credential
  2. The upload path had no gate distinguishing "this is a test" from "this is live"
  3. Publishing from that environment was therefore real, not simulated
  4. Caught the same session — traced through the exact GitHub commit history, restored byte-for-byte, verified by checksum
  5. Closed with two independent protections: an environment-level switch defaulting to off everywhere except real production, and a per-site pause for day-to-day safety — both adversarially tested by deliberately trying to bypass them

The response was the boundary, not the file.

Verification

Every security-critical guard — tenant isolation, credential handling, publish-safety logic, and a later-discovered privilege-escalation path in an unrelated feature — got the same mutation-testing treatment as the isolation guard above: broken on purpose, confirmed the test caught it, restored. One of those checks turned out to be passing for the wrong reason — it only failed correctly once the code it was meant to protect was broken first.

Results

Shipped

  • A static migration with content fully separated from presentation
  • A full-schema editor for a non-technical operator
  • Database-level tenant isolation
  • The platform's first write-capable external integration
  • A two-layer publish-safety system, incident-tested
  • Hum already had a marketing calendar; the site was made to read from it instead of keeping a second ledger. Events flow to the live calendar automatically and render as shareable cards with links that deep-link straight to the event — the customer-facing proof that the seam works in both directions.

Not yet measured

  • Whether the business owner or her staff actually use the editor day-to-day once handoff is complete
  • Time from "new special" to live, compared to the old email-a-developer workflow

The instrumentation for both already exists in the platform (publish timestamps, version history) — it just hasn't been measured against real usage yet.

Reflections

The hardest discipline across both phases wasn't technical — it was staying honest about what "done" meant. Refusing to apply an available redesign during a migration that wasn't supposed to change anything is one version of that. The incident is the clearer one: not that the system never made a mistake, but that it caught its own mistake, traced the real cause, and proved the fix instead of just moving on.

Whether this pattern is worth extending to the client's other restaurant brands — or whether the cost of generalizing outweighs the value at this size — is still an open business question, not a metric this project failed to capture.