Water log mobile field experience (PIN → Headgates → Log → Success):
* Brand rename: "Tuxedo Ditch Co." → "Tuxedo Corn" everywhere it
was a stale reference (page metadata + admin Today tab caption).
Constants already say Tuxedo Corn; this just aligns the stragglers.
* Tab bar (Headgates | Time): pill pair → real UISegmentedControl.
MiniSegmented lives next to TabBar in MobileWaterApp.tsx and uses
the same ResizeObserver-measure + spring-timing pattern as the
existing SegmentedControl — feels like one widget at two sizes
(this one at 32pt, the unit selector at 44pt).
* Liquid-glass chrome (recipe applied 5×, mirrored on the bottom
submit bar): linear-gradient background + blur(24px) +
saturate(180%) + inset top highlight + inset bottom hairline.
TabBar, HeadgateList top nav, LogForm top nav, LogForm sticky
submit bar, FieldPinScreen + SuccessScreen brand strips.
* ThresholdMeter (new component, mobile/ThresholdMeter.tsx): the
screen's signature. A live "blip" indicator under the 44pt
measurement input that ties the form to its real domain (a
worker reading a real gauge) instead of being a decorative
gauge trophy. Track + optional colored zones (red·green·red)
+ thumb that slides on Apple's spring curve + color-coded
zone label. Mirrors Apple's Volume Limit / Screen Time sliders.
* i18n: 9 new zone labels per language (zoneBelowNormal,
zoneAboveAlert, zoneInRange, zoneBelowAlert, zoneAboveMin,
zoneReading, zoneTypeToLog, meterNormalRange + ES twins).
Legacy normalRange/alertAbove/alertBelow signatures widened
to accept pre-formatted strings so integer units render clean.
* Updated water-log-i18n unit test for the wider signatures.
All 27 tests pass; tsc + eslint clean.
The mobile `/water` worker experience (Tuxedo-only, matching the
existing water flow) now exposes a "Time" tab alongside "Headgates"
so irrigators can clock in/out from the same phone they use for
water entries — no second PIN.
- MobileWaterApp: new `activeTab` state, sticky TabBar rendered only
after PIN, screen='time' case embeds <TimeTrackingFieldClient>
with hardcoded Tuxedo brand constants.
- handlePinSubmit best-effort calls `verifyTimeTrackingPin` after
the water PIN succeeds, so the Time tab never re-prompts a worker
who is also set up in `time_tracking_workers`.
- handleLogout clears BOTH `wl_session` and `time_tracking_session`
cookies in independent try/catch blocks.
- Tab switching to Time drops in-flight water flow state; switching
back to Headgates restores the list without reload.
- i18n: added `tab.{headgates,time,logout}` strings (en + es); lifted
Tuxedo brand display constants into @/lib/water-log/brand so the
standalone /tuxedo/time-clock page and the embedded Time tab share
one source of truth.
- Session-expired bounce-to-PIN branch now matches the actual strings
returned by requireFieldSession (was 'Session expired or invalid'
which never matched). Added 'Session not found' and 'User is inactive'
to the intercept list. The user-visible 'Your session expired. Please
sign in again.' string is now in the dictionary (en + es).
- LangProvider moved up to WaterFieldClient. The orchestrator (which
was sitting OUTSIDE the provider in b29caa0) now calls useLang()
directly, so the loadHeadgates fallback error string is always
rendered in the user's chosen language — even if they toggled
after signing in.
- getClientLangSnapshot now memoizes the cookie value (React 19
enforces getSnapshot purity stricter than 18).
- LanguageToggle switched from aria-pressed (toggle pattern) to
role="radiogroup" + role="radio" + aria-checked (mutually-exclusive
selector pattern). Per-button aria-labels dropped — the visible
EN/ES text is read by SR as the radio name. Group label switched
to t.common.a11yLanguage for Spanish parity.
- Notes textarea now has aria-label={t.log.notes} (was unlabeled —
SR would announce 'Edit text' with no context).
- 1 new test: lock in that wl_lang=<script>...</script> falls through
to the navigator (injection guard on the cookie regex).
Refs /tmp/refactor-water-log.md (review followups to Commit B)
- 'holes' / 'hoyos' added to MOBILE_UNITS alongside CFS/GPM/gal/ac-in/ac-ft
with integer-only validation: submit button greys out and the keyboard
raises the numeric keypad (no decimal) when the unit is selected.
- Language toggle in the headgate list header (next to logout) flips
every screen between English and Spanish. Choice is persisted to the
existing 'wl_lang' cookie for 1y, so the user's last selection
sticks across logout/relogin.
- i18n strings lifted into shared src/lib/water-log/i18n.ts (single
source of truth, en/es parity enforced by tests). All mobile screens
(Pin, HeadgateList, LogForm, Success) plus the loadHeadgates error
in the orchestrator consume the dictionary. Brand strip, aria-labels,
segmented control labels, success recap, and submit-button labels
all go through getT()/formatUnit()/displayUnit().
- 26 new unit tests (tests/unit/water-log-i18n.test.ts) cover: en/es
dict parity, holes catalog + hoyos translation, all the
detectInitialLang() branches (SSR/cookie/navigator), setLangCookie
read/write, label interpolation helpers, and the unit catalog.
Refs /tmp/refactor-water-log.md (Commit B plan)
Phase 1 of the water-log auth refactor. No behavior change.
NEW:
- src/lib/water-log/brand.ts — single source of TUXEDO_BRAND_ID
- src/lib/water-log/session.ts — cookie name constants + shared types
(FieldSession, AdminSession, FieldSessionAuthed,
FieldSessionResult). Safe to import from client
components.
- src/lib/water-log/session-server.ts — server-only cookie I/O helpers
(setSessionCookie, clearSessionCookie,
readSessionCookie).
MODIFIED:
- src/lib/water-admin-pin-auth.ts — re-export TUXEDO_BRAND_ID, use helpers
- src/actions/water-log/auth.ts — use shared cookie constant + types
- src/actions/water-log/field.ts — drop local TUXEDO_BRAND_ID copy
- src/app/api/water-photo-upload/route.ts — use shared cookie names
- src/components/water/mobile/MobileWaterApp.tsx — drop local
TUXEDO_BRAND_ID copy;
use string ops over
regex for cookie peek.
FieldSession retains its union shape (back-compat) — FieldSessionAuthed
is the narrowed success branch.
Makes the currently-signed-in irrigator visible while data is being
entered and confirmed, so the operator can verify the reading is
attributed to them at a glance.
- LogForm nav bar: title + initials avatar + name as a subtitle,
sign-out button moves from the form footer into the nav bar
(more discoverable, frees vertical space for the Submit button)
- SuccessScreen recap: 'Logged by {name}' row with avatar below
the timestamp, matches the log form styling
- MobileWaterApp threads irrigatorName through to both screens
The headgate list still shows 'Signed in as {name}' as a small
caption — left untouched to avoid duplicate UI.
Validation:
- tsc --noEmit clean
- eslint clean on all modified files
Replaces the legacy 1000+ line WaterFieldClient with a polished
three-screen state machine tuned for phones and tablets in the field:
PIN -> Headgates -> Log -> Success
Design (Apple Human Interface Guidelines):
- System font stack (-apple-system, BlinkMacSystemFont, 'SF Pro Display')
instead of the admin's Fraunces/Manrope for native iOS feel
- iOS systemGreen (#34C759) primary success, forest-900 brand accent
- iOS systemGroupedBackground (#F2F2F7) surface, white cards,
rgba(60,60,67,0.x) text greys
- 44pt minimum touch targets, 58pt primary buttons, 68pt PIN boxes
- env(safe-area-inset-top/bottom) respected on every screen
- 100dvh shell so iOS Safari chrome collapse doesn't reflow
Screens:
- PinScreen: 4 iOS-style digit boxes with blinking caret, big
'Unlock' button (disabled until 4 digits entered), branded
'TUXEDO WATER LOG' header, error shake on bad PIN
- HeadgateList: grouped list of large tappable cards showing
name + geocode (notes) + unit + last-logged relative time,
pull-to-refresh, empty state with refresh CTA
- LogForm: sticky headgate card + 44pt numeric input +
SegmentedControl for units (CFS / GPM / gal / ac-in / ac-ft)
+ live 'Logged now' timestamp + optional 500-char notes +
green Submit Log button with inline measurement/unit preview
- SuccessScreen: animated ring + checkmark draw-on, recap card,
'Log Another Entry' (green) and 'Back to Headgates' (secondary)
Reusable primitives (mobile/):
- SegmentedControl: iOS-style sliding pill with spring timing
- PullToRefresh: callback-based (no router.refresh) with resistance
- icons.tsx: inline SVG icon set (Lock, Droplet, Gauge, Refresh, etc.)
Backend:
- getWaterHeadgates extended to include notes (the 'geocode') and
last_used_at for richer card display
- No new routes, no DB migration - reuses verifyWaterPin,
submitWaterEntry, logoutWater server actions unchanged
Quality gates:
- tsc --noEmit clean
- eslint clean on all new files
- npm run build succeeds (/water rendered as static)
- prefers-reduced-motion respected (existing global guard)