# As-Built Spec - Pollinator Oasis (Crown Bees Pollination App) > **Durable document** (designated Session 310, 2026-07-05). One of the three canonical project > documents per the Hub methodology **[[10-Three-Document-Product-Canon]]** (`~/Documents/Claude/ > Vaults/The Hub/25 Methodology/10-Three-Document-Product-Canon.md`). This is the **As-Built** - > it answers **"what is live right now?"** and is the **first document a session reads** to > establish product state, before the git log. > > **Version: v1.0.10** (v1.0 MVP + BT-094 security response headers + the S322 GDD-chart cache-invalidation fix + the BT-062 in-app "Ask" / AI BeeBot feature + BT-106 native guest sign-in + v1.0.5 login/nav polish + v1.0.6 forecast-day pollen & GDD/Ask UI + v1.0.7 evening-pollen fix + v1.0.8 server-hardening fixes + v1.0.9 cache-log coordinate redaction + v1.0.10 weather setup-form disabled-button visual; current production). **Production coordinates: btt-server v61, client EAS build 24, product `main` `69ed4b5`.** Living copy: this file. Dated, version-stamped > snapshots under `documents/snapshots/`. Companions (the canon is **four documents**): **UX Flow > Atlas** (`ux-flow-atlas.md` - the desired experience, persona-by-flow), **To Build Spec** > (`next-build-spec.md` - what we build next + the plan: roadmap, LOE, prioritization), and > **Design System** (`design-system.md` - the look-and-feel basis, referenced from Section 4). > Fidelity: **the built product = production truth**, reconciled against the running code - never > speculative. **Fidelity note.** This v1.0 captures the **current-state map** (what ships, the auth reality, the routes, the design system) at a trustworthy summary fidelity, reconciled against the code. The **per-screen pixel-grade As-Built detail** (every shipped screen's exact layout, tokens, and components) is deepened from the design-system reference and per-screen recon in a later pass; the summary below is authoritative for "what is live" today. **v1.0 / MVP gate met (2026-07-05, Session 314).** v0.9 + the in-flight auth work shipped to production at **btt-server v50**: the email-OTP flow is now **wired into the app UI** (welcome-hero- first, email-step-first) and the login captcha carries the canonical circle-less bee icon. The deploy is **machine-verified** - the v50 bundle served from `app.mybeecloud.com` has the prod Supabase host, the real Turnstile site key (not the test-key), and the same-origin `/trpc` base all baked; the active machine's health check passes; root serves HTTP 200. The **interactive on-device e2e** (Turnstile renders + Guest anonymous sign-in + email-OTP all complete on the real host) is Scott's confirmation step, pending at the time of this fold - not asserted here. --- ## Section 1 - Version and deploy state | Fact | Value | |---|---| | **Version** | **v1.0.10** (v1.0 MVP + BT-094 security response headers + S322 GDD-chart cache-invalidation fix + BT-062 in-app "Ask" / AI BeeBot + BT-106 native guest sign-in + v1.0.5 login/nav polish + v1.0.6 forecast-day pollen & GDD/Ask UI + v1.0.7 evening-pollen fix + v1.0.8 server-hardening fixes + v1.0.9 cache-log coordinate redaction + v1.0.10 weather setup-form disabled-button visual) | | **Production server** | Fly.io app `btt-server`, **v61** (deployed 2026-07-12, Session 364 - BT-023/E2 cache-log coordinate redaction; v60 = v1.0.8 server-hardening fixes from the S359 full-build review, 2026-07-12, Session 363; v59 = BT-115 evening-pollen fix, 2026-07-11, Session 357; v58 = BT-115 forecast-day pollen, 2026-07-11, Session 355; v57 = BT-106 native guest sign-in, 2026-07-10, Session 349; v56 = BT-113 fail-closed rate limiters, 2026-07-10, Session 346; v55 = BT-101 native email-OTP server-mediated send, 2026-07-09; v54 = BT-062 in-app "Ask" / AI BeeBot, Session 325; v53 = the S322 GDD-chart fix; v52 = BT-094 headers, Session 317; v50 = the v1.0 MVP, Session 314) | | **Product repo** | `~/projects/the-bee-team/`, main @ `69ed4b5` | | **Production host** | `app.mybeecloud.com` | | **v1.0 (MVP) gate** | **MET** - the in-flight auth work (email-OTP wired into the app UI) shipped at v50 | | **v1.0.1 delta** | **BT-094 security response headers** at the Fly origin - **enforcing** CSP (`@fastify/helmet`, config-derived source lists) + HSTS 1yr/includeSubDomains (no preload) + X-Frame-Options DENY + nosniff + Referrer-Policy strict-origin-when-cross-origin + Permissions-Policy (geolocation=self, rest disabled). Shipped Report-Only (v51) -> walked to zero live prod violations -> flipped enforcing (v52). Server suite 370/370. | | **v1.0.2 delta** | **GDD-chart cache-invalidation fix** (btt-server v53, Session 322): `addPlant`/`removePlant` now invalidate `garden.yardAwareness` so the My Yard chart refreshes without a remount. | | **v1.0.3 delta** | **BT-062 in-app "Ask" / AI BeeBot** (btt-server v54, Session 325): the center orange "Ask" FAB opens `/(app)/ask`. **Native** (iOS/Android) embeds the Crown Bees / Rep "AI BeeBot" assistant in a WebView (device-smoked green, S322). **Web** renders a graceful `ask.web.tsx` state (the canonical `BeeIcon` + "AI BeeBot", framed by the app chrome) - an app-owned `.web.tsx` split that keeps native-only `react-native-webview` (which has no web build) out of the web bundle, so `/ask` on web shows the badge, not rnw's "does not support this platform" error. Merge integrated cleanly with v53; QA green (typecheck 0 / eslint 0 / client jest 143 / server 370 / build:web ok); the deployed `/ask` web render machine-verified (BeeIcon "AI BeeBot" state, no red error, 0 page errors). Rep boundary: **BT-037** (no hosted AI - Rep is a partner-provided webview assistant), **BT-056** (single-click-out), **BT-061** (revenue separability). | | **v1.0.4 delta** | **BT-106 native guest sign-in** (btt-server v57, Session 349): the native "Guest" button now works. Previously `supabase.auth.signInAnonymously()` hit Supabase's project-wide captcha gate (Turnstile is web-only), so native Guest always returned "Could not start a guest session". Fixed the BT-101 way - a server-mediated `auth.guestSignIn` tRPC mutation authenticated as service-role (bypasses captcha to GoTrue source), guarded by a new fail-closed per-IP guest limiter (`btt:rl:guest-ip`, 5/hr, via the BT-113 `withFallback` composition); native calls it then `supabase.auth.setSession(...)`. Web unchanged (Turnstile still gates it). A wire-transport defect caught at delivery (the `z.void()` mutation over `httpLink` sent an empty body Fastify 400'd before the resolver ran) was fixed by moving the native auth client to `httpBatchLink` + a new SP-7 HTTP wire test (`auth-guest.http.test.ts`). QA green (server 424/424, client 159/159, typecheck 0 / eslint 0 / ASCII clean); on-device verified on iOS TestFlight build 17 (Guest AND email OTP both work). | | **v1.0.5 delta** | **Login send-feedback + first-tap fix + bottom-nav sizing** (client EAS build 20; server unchanged at v57). The login send button shows a "Sending . . ." in-flight label and `keyboardShouldPersistTaps="handled"` fixes the swallowed first tap (`b38666e`, Session 351); bottom-nav icons + labels enlarged 24->28 / 10.5->12 with the center AI FAB frozen, RED-first test guarding the sizes (`7c20794`, Session 353). Client-only, no server deploy. | | **v1.0.6 delta** | **BT-115 forecast-day pollen + GDD off the Weather daily card + Ask label spacing** (btt-server v58; client EAS build 22, superseding 21). Weather forecast pollen is now per-date Google Pollen (`days=5`) aligned to the selected forecast day by ISO date (`79fe2c3`, Session 355, server v58); GDD removed from the Weather daily card (GDD is the Yard's readout; plants/detail untouched) and all five nav labels aligned to 0.1 letterSpacing (`f61c7a0`, Session 355, build 22). RED-first tests both. | | **v1.0.7 delta** | **BT-115 evening-pollen fix** (btt-server v59; build 22 unchanged). `pollen-service.ts` rebuilt as a rolling per-location day-union cache that survives Google's 00:00-UTC forecast rollover, plus a Redis persistence tier (`pollen:days:`, 48h TTL, best-effort). RED-first 9 new tests; server 439/439, client 168/168, tsc + eslint clean (`ab829c7`, Session 357, server v59). SHIPPED - awaiting Scott's evening-window verification (2026-07-12). | | **v1.0.8 delta** | **Server-hardening fixes from the S359 full-build review** (btt-server v60, Session 363; client build 22 unchanged). Nine server-side fixes, each RED-first: coordinate-free weather error surface (svc-2, a live BT-023 fix - errors no longer leak raw lat/long), graceful SIGTERM/SIGINT shutdown so deploys stop cutting in-flight work (dat-2), postgres `prepare:false` for the Supabase transaction pooler (dat-3), an AbortSignal timeout on the LocationIQ geocoder (svc-1), a fire-and-forget best-effort pollen Redis write (svc-4), `SUPABASE_SERVICE_ROLE_KEY` shape validation at config load (BT-103), and a `UNIQUE (user_id, plant_key)` constraint on `garden_plant` with an idempotent `onConflictDoNothing` `addPlant` so a double-tap no longer creates duplicate cards (rtr-1). Migration **0023** (dedupe-before-constrain) applied to prod: **0 rows deleted** (no existing dupes), constraint **validated/enforcing**. Gates: server **467/467**, typecheck + lint clean. Pre-deploy key gate passed (the live prod service-role key passes the new shape guard). Fast-forward merge `fa95cce`..`7019363`; folds the non-deployed dat-1 dev-seed guard (`fa95cce`). | | **v1.0.9 delta** | **Cache-log coordinate redaction** (btt-server v61, Session 364; client build 22 unchanged). The weather (`service.ts`, DEBUG) and pollen (`pollen-service.ts`, WARN) best-effort Redis cache helpers logged the raw cache key - which embeds a 2dp (~1.1 km) coordinate (`wx:forecast:,` / `pollen:days:,`) - and the cache-op timeout embedded that key in its `Error` message, so the coordinate also rode out inside the logged `err`. A shared `redactCoordKey` (in `coords.ts`) now replaces the coordinate tail with `` across all **6 log/error vectors** (both services: timeout message + get log + set log), keeping the useful key namespace. Defense-in-depth extending BT-023 (whose letter is user surfaces) to operator logs (E2). **Zero end-user impact** - the real cache key is unchanged, so cache behaviour, forecast data, and API responses are byte-for-byte identical; only what is logged is redacted. No migration, no client change. RED-first (10 tests failed for the right reason -> GREEN); gates server **475/475**, typecheck + lint clean. Commit `bd8e102` (pushed, deployed); reversible to v60 (`deployment-01KXC6PH...`). | | **v1.0.10 delta** | **Weather setup-form disabled-button visual** (client EAS build 24, Session 368; btt-server v61 unchanged). Both Weather location-setup action buttons (`set-address-button` + `use-device-location-button` in `apps/client/app/(app)/weather.tsx`) now apply an inline `style={{ opacity: 0.5 }}` when disabled, matching the shared `LocationSetup` component's `styles.btnDisabled` - previously they rendered solid `bg-cb-green` while inert, so an empty-address / mid-mutation button looked tappable (the S366 A2 Phase 2 native-verify defect, BT-117/wx-1). Inline style, not a NativeWind `opacity-50` class (which does not resolve to an inspectable style on raw RN components). RED-first (3 tests); client **177/177**, typecheck + eslint clean. Commit `69ed4b5`, client EAS **build 24**. Client-only, no server deploy. Shipped to TestFlight via the altool direct-upload bypass - the EAS submission queue stalled undispatched ~16 min and was cancelled (CANCELED, no double-upload), then the binary uploaded to App Store Connect in 5.8 s (Delivery UUID `e12e8959...`), processingState VALID. | --- ## Section 2 - Authentication (the corrected record) **Shipped auth = email one-time-code (OTP) / magic-link via Supabase, delivered via Resend.** - The auth flow is a **Supabase email magic-link / OTP** flow, **proven in production** across Sessions 299-301. **Resend** is the transactional email service (decision D3, S161) and the likely SMTP delivery path for the auth emails (delivery detail to confirm). - **Shopify OIDC was scaffolded** (Plan 4) **but never shipped.** Any impression from the git history that production auth is Shopify is wrong - it is email-OTP. Consult this document, not the git log, for the auth reality. - Login itself was **verified working at v47** (S290 cw end-to-end; S291 Scott on device - "login works and the banner is gone"). Production is now **v50**. - **Email-OTP is now WIRED into the app UI and deployed (v50, Session 314).** The entry flow opens welcome-hero-first, then email-step-first (`37e4caa`); the login captcha carries the canonical circle-less bee icon (`f293004` + `69e7b18`). This wiring + deploy is exactly the **v1.0 / MVP** work that closed the gate. The v50 bundle is machine-verified (prod Supabase host + real Turnstile key + same-origin `/trpc` all baked; health passing). The **interactive on-device e2e** - Turnstile renders, Guest completes the anonymous sign-in, email-OTP completes end-to-end on the real host - is Scott's confirmation step, pending at fold time (the plain preview cannot exercise it: test-key Turnstile tokens are server-rejected by Supabase and the static preview has no backend - an environmental artifact proven in S312/S313, not a product defect). - The auth sender identity renders as **"Pollinator Oasis"** (the app's name; MVP ships Crown Bees visual branding - independent branding is BT-095, post-MVP). --- ## Section 3 - What ships today (the shipped app) The shipped app is the **post-login product** the onboarding funnel (Atlas) will feed into. **Pre-auth:** a login / Welcome screen (`login.tsx`) with the wired email-OTP entry (welcome-hero- first -> email-step-first) and a web captcha (Turnstile) captioned with the canonical circle-less bee icon. Guest (anonymous sign-in) and email-OTP are the two entry paths. **Authenticated app (routes):** | Screen | Route / note | |---|---| | **Home** | `/(app)/welcome` - weather box, a GO/WAIT/HOLD release-decision banner, top alert, this-week's-advice, yard-awareness, Shop. **This is the post-login Home, not the pre-auth welcome** (that lives in `login.tsx`) - keep the two straight. | | **Weather** | forecast + pollen + location setup | | **Ask** | `/(app)/ask` - the center orange "Ask" FAB. **Native:** embeds the Crown Bees / Rep "AI BeeBot" assistant in a WebView (device-smoked green, BT-062). **Web:** a graceful `ask.web.tsx` state (canonical `BeeIcon` + "AI BeeBot"), the `.web.tsx` split keeping native-only `react-native-webview` out of the web bundle. Partner-assistant boundary: BT-037 / BT-056 / BT-061. | | **My Yard** | release decision, seasonal GDD / bloom chart, plant cards, add/remove | | **Advice** | species to-dos | | **Plant detail** | per-plant detail | | **Diary** | seasonal diary | | **Alerts** | alert feed | | **Notifications** | **currently orphaned** (no in-app way to reach it) | | **Profile** | account / profile | | **Paywall** | present but **inert / unreachable in-app** | **Facts that shape the next builds:** - **Notifications** is orphaned (BT-117) and **Paywall** is unreachable (BT-024) - the funnel must wire their entry points. v1.0 / MVP shipped **auth-only**; wiring these entry points is deferred to a later version (BT-117 notifications entry; BT-024 paywall / subscription). - **Native captcha is deferred** (web-only Turnstile today) - the native auth path uses the server-mediated captcha bypass instead (BT-113 owns the abuse-control question). - The app boots **mock-backed, zero backend** in the iOS / Android sims (`cd apps/client && pnpm dev`, press `i` / `a`). --- ## Section 3a - Per-screen code pointers (at product `main` `69ed4b5`) Per BT-102 Section 1a #2 / the Hub SOP: the route file, key `@btt/ui` components, and backing server procedures per screen, at the shipped commit. tRPC namespaces map to `apps/server/src/routers/.ts` (barrel: `apps/server/src/router.ts`); `@btt/ui` components live in `packages/ui/src/components/` + `charts/` (see the Design System doc, Section 5). | Screen | Route file (`apps/client/`) | Key `@btt/ui` | Backing procedures | |---|---|---|---| | **Home** | `app/(app)/welcome.tsx` -> `components/WelcomeScreen.tsx` | DecisionBanner, AlertCard, YardAwarenessCard, WeeklyTipSheet, Card, Button, Typography | `garden.getReleaseDecision`, `garden.yardAwareness`, `weather.forecast`, `weather.myLocation`, `alerts.list`, `tip.current` / `markSeen` / `maybeLater`, `subscription.status` | | **Weather** | `app/(app)/weather.tsx` | Card, PollenGauge, Typography | `weather.forecast`, `weather.myLocation`, `weather.pollen`, `weather.setLocation` | | **Ask** | `app/(app)/ask.tsx` (+ `ask.web.tsx`) | colors (WebView-based; `Ionicons`) | none - embeds the Rep "AI BeeBot" WebView (BT-062); web = `ask.web.tsx` graceful state | | **My Yard** | `app/(app)/plants.tsx` | PlantCard, AddPlantCard, BloomConfirmRow, DecisionBanner, GddYearChart, Pill, Card, Button | `garden.listGardenPlants` / `addPlant` / `removePlant` / `getProfile` / `upsertProfile` / `getReleaseDecision` / `yardAwareness` / `confirmBloom` / `deferBloom`, `weather.gddYear` / `myLocation` | | **Advice** | `app/(app)/advice.tsx` | SpeciesAccordion, ActionDetailSheet, Typography | `garden.adviceFeed`, `garden.markAdviceDone`, `weather.myLocation` | | **Plant detail** | `app/plant/[key].tsx` | DetailHero, GddBar, KvList, Card, Button, Typography | `garden.getProfile`, `garden.getReleaseDecision`, `weather.myLocation` | | **Diary** | `app/(app)/diary.tsx` | Card, Pill, Button, Typography | `diary.list`, `diary.create`, `diary.remove` | | **Alerts** | `app/(app)/alerts.tsx` | AlertFeedRow, Card, Typography | `alerts.list`, `weather.myLocation` | | **Notifications** (orphaned) | `app/(app)/notifications.tsx` | Card, Button, Typography | `notification.list` / `markRead` / `markAllRead` | | **Profile** | `app/(app)/profile.tsx` | DetailHero, KvList, Card, Button, Typography | `user.me`, `garden.listGardenPlants`, `notification.getPreferences` / `setPreference`, `weather.myLocation` | | **Paywall** (inert) | `app/(app)/paywall.tsx` -> `components/PaywallScreen.tsx` | Card, Pill, Button, Typography | none wired | | **Login** (pre-auth) | `app/login.tsx` | Hero, StepCard, Card, Button, Typography | `session.authStatus`; auth itself = Supabase email-OTP + `auth.guestSignIn` (native guest, server-mediated; Section 2 + `routers/auth.ts`) | | **Entry** | `app/index.tsx` | (Redirect) | - | **Chrome** (every signed-in screen): `SiteHeader` + `BottomTabBar` in `apps/client/components/`, wrapped by `app/(app)/_layout.tsx`. See the Design System doc Section 5. **Note - captures pending.** The pixel-perfect production screen captures (BT-102 Section 1a #1 -> `documents/as-built-screens/`) are the remaining half of Unit 2c; they need an iOS Simulator pass (Scott drives - `simctl` cannot tap) and are deferred to a live session. This code-pointer map is the authored half. --- ## Section 4 - Design system (the live skin) The app's live skin **is** the shipped Crown Bees Design System - Inter throughout, the exact green / orange tokens, `@btt/ui` components. Building new screens is compose-from-the-kit, not a reskin. - **Color - two brand colors only.** Green `#004322` = frame / heading / structure; orange `#e69100` = the ONLY call-to-action color, used sparingly. Status (GO / WAIT / HOLD) `#28a745` / `#eb9247` / `#e95050`. Warm neutrals only (never cold grey). - **Type - Inter only** (400-800). Hero 36, h1 30/800, h2 22/700, h3 16/700, body 15/400. - **Chrome:** `SiteHeader` (green app bar, orange-bee CROWNBEES wordmark, notifications bell, avatar, orange `TopProgressBar` underline) + `BottomTabBar` (Home / Weather / center orange "Ask" FAB / Yard / Advice; the FAB is the Rep-AI entry point - **live at v54**, BT-062). - **Canonical DS reference:** the design system is the **4th canon document**, `documents/design-system.md` (v1.0) - the reference-hub over the brand authority (`~/.claude/skills/crown-bees-design/colors_and_type.css`) and the implemented tokens (`packages/ui/src/tokens/*.ts`), with the full narrative skin write-up at `outputs/s305/THE-BUILD-SPEC.md` Appendix A. Read the DS doc for the token/component vocabulary. --- ## Section 5 - Provenance Reconciled against the code (`apps/server/src/lib/email.ts`, the expo-router tree) and the Session 305 build spec (`outputs/s305/THE-BUILD-SPEC.md` Part 2), not asserted from memory or the git history. This document supersedes any product-state inference from git. --- *As-Built Spec v1.0.10 - The Bee Team. v1.0 (MVP): Session 314 - the in-flight auth work (email-OTP wired into the app UI + the canonical bee captcha icon) deployed to production at btt-server v50 on 2026-07-05, closing the MVP gate (v0.9 -> v1.0). v1.0.1: Session 317 (2026-07-05) - the BT-094 security response headers at the Fly origin (v51 Report-Only, walked to zero live prod violations, then v52 enforcing). v1.0.2: Session 322 (2026-07-06) - the GDD-chart cache-invalidation fix (btt-server v53). v1.0.3: Session 325 (2026-07-07) - the BT-062 in-app "Ask" / AI BeeBot feature (btt-server v54). v1.0.4: Session 349 (2026-07-10) - BT-106 native guest sign-in (btt-server v57). v1.0.5: Sessions 351/353 (2026-07-10) - login send-feedback + first-tap fix + bottom-nav sizing (client EAS build 20). v1.0.6: Session 355 (2026-07-11) - BT-115 forecast-day pollen (btt-server v58) + GDD off the Weather daily card + Ask label spacing (build 22). v1.0.7: Session 357 (2026-07-11) - BT-115 evening-pollen fix (btt-server v59). v1.0.8: Session 363 (2026-07-12) - nine server-hardening fixes from the S359 full-build review, migration 0023 applied to prod (0 rows deleted) (btt-server v60). v1.0.9: Session 364 (2026-07-12) - BT-023/E2 cache-log coordinate redaction across the weather + pollen Redis cache helpers (btt-server v61). v1.0.10: Session 368 (2026-07-13) - the weather setup-form disabled-button visual (BT-117/wx-1; client EAS build 24, shipped to TestFlight via the altool bypass); product `main` @ `69ed4b5`. Production truth for "what is live"; machine-verified at deploy. Change history lives in git + the LMA records + the dated snapshots, never this footer.*