← Back to Blog

Building in the Fog, Part 6: Field Ready

March 9, 2026

Making provisioning software usable by supply officers on phones in a warehouse. 320px minimum, 20px text, light/dark themes, bilingual — and what 'mobile-first' means when your users don't think in software terms.

Part 6 of "Building in the Fog" — a series about building Ridgeline, a provisioning decision engine for Outward Bound Hong Kong. Part 5 covered the structural redesign from anonymous dietary counts to named participants with auto-substitution.

The warehouse

Picture the supply room at OBHK's facility. Shelves of canned goods, dried staples, condiment packets. A packing station. A phone in someone's hand — the supply officer checking what to order, how much, for which program.

This is where the software has to work. Not at a desk with a monitor. Not in a meeting room with a projector. On a phone, in a warehouse, used by someone who's been packing expedition supplies for years and doesn't need an app to tell them how to do their job.

The app has to earn its place by being faster than the spreadsheet for the things the spreadsheet can't do — scaling, dietary substitution, cost aggregation across suppliers. If it's slower, clunkier, or harder to read than a printout, it's dead on arrival.

320 Pixels

The smallest common phone screen is 320px wide. The iPhone SE, still in use. Every page in Ridgeline is designed for this width first (6eadcb7, d72c5a0).

This means:

No horizontal scrolling. Multi-item rows use flex-wrap. Chips, badges, data labels — anything that could overflow — wraps to the next line. The rule in ADR-002: "Never use inline-flex without flex-wrap on content that could exceed screen width."

Text stays readable. The minimum for any user-facing content is 20px — Tailwind's text-xl. Not 16px (the web default), not 18px (which we tried and rejected as too small in practice). Input fields are 24px (text-2xl) because fat fingers on small screens need generous touch targets. The only exceptions are decorative chrome: the nav brand in uppercase monospace, footer credits, data hint chips. These must be visually distinct from body content so nobody mistakes them for something they need to read.

Content stacks. Side-by-side layouts on desktop become vertical stacks on mobile:

flex flex-col sm:flex-row items-center justify-between

Centered text on mobile, left-aligned on desktop:

text-center sm:text-left

Responsive padding that doesn't eat the screen on small viewports:

px-3 sm:px-4       // tight containers
px-6 sm:px-10      // page-level padding

The verification checklist before shipping any page: 320px, 375px, 428px, 768px, 1024px+. Check for: no horizontal scrollbar, no text clipping, adequate touch targets (44x44px minimum), data displays that wrap instead of overflow.

Two Languages in 696 Lines

Rocky's team operates in Cantonese. The OBHK facility runs bilingual — English for documentation, Cantonese for conversation. Not every staffer reads English comfortably. The app needed to work in both.

next-intl handles the routing — every page lives under /en/ or /tc/. The translation files are 696 lines each (messages/en.json, messages/tc.json), covering everything from "Spaghetti Bolognese" to dietary flag labels to nutrition warning messages. Server components use setRequestLocale(), client components use useTranslations().

The bilingual requirement shaped decisions that would have been simpler in English alone. Ingredient names carry both name_en and name_zh. The UI shows whichever matches the locale, with fallback to English. Warning messages template participant names into translated strings: "Pork Burger in D1 is not halal — affects Ahmad" becomes "豬肉漢堡扒 (D1) 不是清真 — 影響 Ahmad."

The i18n was there from the first commit (46e1014). This was deliberate. Adding translation after the fact means hunting through every component for hardcoded strings. Starting bilingual means the translation key is the natural way to write text.

Light and Dark

The app ships with a theme switcher — light and dark. The light theme turned out to have better legibility in practice, especially for data-dense screens like the workbench where you're scanning ingredient quantities and cost columns. Dark theme still works well for low-light conditions, but the warehouse has fluorescent lighting, and light-on-dark text can be harder to read under bright ambient light.

The reason the switch was trivial to add: every color in the app references a CSS variable. No hardcoded Tailwind grays or whites anywhere.

--bg-deep          // deepest background
--bg-surface       // card/panel background
--bg-elevated      // elevated elements
--text-primary     // main text
--text-secondary   // supporting text
--accent           // highlight color

One set of variables for dark, one for light. The theme toggle flips between them. Every component picks up the change automatically because none of them reference colors directly — they all go through the variables. This is the kind of decision that pays off later: what started as a "dark-only" aesthetic constraint became a switchable system the moment someone asked for light mode.

Building for People Who Don't Think in Software Terms

The hardest design constraint isn't screen width or language. It's building for users who have deep domain expertise but no interest in learning software conventions.

A supply officer knows that Day 3 needs hardy vegetables because leafy greens won't last. They know curry powder doesn't scale linearly. They know which supplier carries halal sausage. They don't know what a "scenario" is, or why they should care about "portion factor," or what the difference between per_person and per_group means in a dropdown.

The help system (8e7b4cb) uses page-level drawers — a small button that expands an explanation of what this page does and why the numbers look the way they do. Inline concept cards explain terms where they appear. Progressive disclosure (fae6263) hides technical detail behind expandable sections so the default view is clean and the complexity is available but not mandatory.

The workbench doesn't show "scaling = per_person" to the user. It shows quantities that change when you change the group size. The implementation detail is invisible. The behavior is obvious.

The accessibility audit (4b86199) caught 16 files worth of issues — missing ARIA labels, focus states that didn't work with screen readers, toast notifications without proper roles, tab interactions that trapped keyboard users. None of these were visible in normal use. All of them mattered for the kind of inclusive access that an organization like Outward Bound should model.

Field Ready, Not Field Tested

The software is ready for the warehouse. It handles the scenarios the spreadsheet couldn't — variable group sizes, dietary intersections, cost aggregation, nutrition analysis. It works on a phone at 320px. It works in Cantonese.

What it hasn't done yet is survive contact with the supply officer on a Monday morning, packing for a group of 22 with three dietary restrictions and a budget. That test is coming. The spreadsheet that started this project will be the benchmark — not because it's wrong, but because it represents years of institutional knowledge that the software needs to match before it can extend.

The fog hasn't lifted. But the ridgeline is walkable.1

Footnotes

  1. The project name: a ridgeline is the line you walk along the top of a mountain range — the path of highest exposure, where every decision matters because you're committed. Five episodes later, that still feels right.