Street-true temp → visible math → trust → daily habit → hyperlocal premium
GATED: physical-device proof. Code fixes are in place; run npm run verify:ios-prereqs, then build/sync and install on Jason's physical iPhone.
Fresh install on Jason's physical iPhone opens RealTemp without a black screen or white safe-area bars; Home Screen icon is the RealTemp icon; npm run build, npx cap sync ios, and xcodebuild for the physical device succeed.
The whole thesis is that people value seeing the math. Nothing currently measures whether the breakdown is ever opened. Emit ledger-open, toggle-change, and day-2/day-7 return events.
Two weeks of personal use produce a readable answer to: was the breakdown opened, and did a toggle change a decision?
GATED: Jason. Home Screen, Valencia summer, every day for two weeks, with C1a instrumentation running. This is the first real evidence the product would ever have.
14 consecutive days of use logged. STOP RULE: zero ledger opens across 14 days falsifies the transparency thesis - stop and treat RealTemp as a personal tool, not a product.
The free tier is non-commercial: the first payment taken is a licence breach. Get current commercial pricing and confirm the cost floor per user. Blocks every paid step.
Current commercial pricing recorded in docs/, with a per-user cost floor and a go/no-go on the provider.
GATED: Jason decision. PRD Non-goals disclaims meteorological accuracy while the pitch says the temperature your body actually meets. Free that is an honest heuristic; paid it is selling a number the PRD says you cannot stand behind. Either validate the coefficients against real observation, or reposition the paid claim onto transparency and decision support - then amend the PRD to match.
PRD non-goal and the intended paid claim agree in writing, appended to DECISIONS.md.
GATED: Jason physical street check. Open the deployed app or native shell on your iPhone in Valencia, use/search location, walk outside, record realtemp-street-check.json, and run npm run verify:street-check.
All PRD v0 checkboxes checked from a phone browser.
GATED: Jason recruits. Narrow heat-exposure niche - outdoor workers, cyclists/runners in hot climates, heat-intolerant (MS, POTS, pregnancy). Not general weather users; there you lose to free preinstalled Apple Weather.
5 recruited, instrumented. CONTINUATION RULE: fewer than 3 still using at day 14 means reposition or stop.
GATED: final taste call + domain purchase + formal trademark clearance. Current evidence lives in docs/naming.md; run npm run verify:naming to refresh RDAP domain status. Recommended: StreetFeel / streetfeel.app.
Competitive alternatives (Apple Weather free+preinstalled, CARROT, Windy, doing nothing), the capability only RealTemp has (verified: zero of CARROT 3201 strings break down Feels Like), and the category that makes it legible. Weather app is the wrong category.
docs/positioning.md: alternatives, differentiated capability, capability-to-value map, best-fit segment, chosen and rejected categories.
GATED: private DSN. Create Sentry project, set VITE_SENTRY_DSN locally and in Vercel production, run npm run verify:sentry, rebuild/deploy. See docs/setup-remaining.md.
The candidate value metric. CARROT gates custom notifications behind their TOP tier (~$39.99/yr) - the strongest willingness-to-pay signal in their pricing. RealTemp already has the worker cron, D1 and push deployed; this generalises the single hardcoded heat check to any ledger term.
A rule (term, above/below, threshold, window) can be created, stored, evaluated by the cron, and delivered.
GATED: Jason decision, irreversible-ish. Do NOT gate the ledger - it is the acquisition hook and the only verified differentiator. Gate recurring value: alert rules, weather-station input, history, multi-location. RealTemp sells interpretation not feed access, so COGS are far below CARROT and it can win below their $19.99 tier.
Tier list, price, and value metric recorded in DECISIONS.md, with the Open-Meteo cost floor from C2a.
GATED: Jason go/no-go on docs/heatmap-prd.md option 1 (free IGN LiDAR → precomputed tiles). Run npm run verify:heatmap-sources to refresh official PNOA/CNIG/datos.gob source evidence before deciding.
GATED: Jason. Stripe on web, or App Store subscription via P5. App Store needs an Apple Developer account and the physical-device path verify:ios-prereqs still fails.
One paying customer who is not a friend or family member.
GATED on C1 evidence + a costed decision. The ledger explains a number statically; conversation is the natural extension of a product whose whole thesis is explaining, and it is the most genuinely 2026 surface available. The plumbing exists: /api/copy, worker, KV cache, Haiku. The objection is commercial, not technical - per-session LLM calls reintroduce variable COGS, the exact cost structure that caps CARROT margin and that RealTemp sub-$19.99 pricing depends on avoiding. PRD constraint holds either way: AI may explain the ledger, never compute it.
Either a DECISIONS.md entry with a measured per-session cost and a tier that covers it, or the card is closed as a permanent non-goal. Not started before C1b proves the static ledger is opened at all.
GATED: worker deploy now DONE (push routes live). Remaining: CAP-feed mapping spike + province geocoding for AEMET/MeteoAlarm.
Production Worker /api/alerts returns current AEMET-issued MeteoAlarm alerts for Valencia by matching EMMA_ID warning regions; dashboard renders official alerts in a collapsed AEMET panel; worker and web tests pass. ✓ 2026-07-14
Reduce perceived startup delay: make the iOS launch screen visually match the app, render an immediate loading state, cache the last good weather snapshot, then refresh in the background.
Cold launch shows branded RealTemp chrome immediately; warm launch paints the last successful reading before network fetch finishes; stale/refresh state remains visible and never blanks the dashboard. ✓ 2026-07-14
Cut extraneous load on the iPhone dashboard. First screen stays True Feel, warning, transparent math, and core microclimate toggles; timeline, body profile, push, and shade map move behind compact sections or modes.
On an iPhone viewport, the initial screen communicates the current True Feel decision without scrolling through secondary tools; all existing secondary features remain reachable and tested. ✓ 2026-07-14
Steadman AT (dew-point vapor pressure) + zenith-weighted solar premium + env/activity deltas + sweat efficiency. Pure fn, all coefficients in constants.ts.
Unit tests pass against hand-computed fixtures incl. night (solar=0), high dew point, windy cases. ✓ 2026-07-06
One call: temperature_2m, dew_point_2m, wind_speed_10m (request m/s, scale ×0.6 street), uv_index. Errors as values.
Valid payload parses; missing field degrades that premium, never NaN; fetch failure surfaces retry state. ✓ 2026-07-06
Browser geolocation with Open-Meteo geocoding search fallback; last location persisted locally.
Denied-permission path lands on search; picked city fetches correctly. ✓ 2026-07-06
True Feel dominant; each premium a signed line (base + sun + humidity − wind + urban = X°). The product's whole differentiator.
Breakdown always sums exactly to displayed True Feel; degraded fields hidden with badge. ✓ 2026-07-06
Three segmented controls, instant recalc, persisted to localStorage.
Every combination recalculates <16ms and updates the breakdown lines. ✓ 2026-07-06
0–100% evaporative-cooling gauge from dew point (100% ≤10°C → 0% ≥26°C).
Renders with label + value; color not the only encoding (a11y). ✓ 2026-07-06
git init, first commit with planning docs + rockmap.
npm run typecheck/test/build/lint all runnable. ✓ 2026-07-06
Night zenith → solar 0; stale data badge; offline/fetch-fail with retry; never NaN on screen.
Each state reproducible via test and visible in UI. ✓ 2026-07-06
Type scale, palette, spacing tokens per moodboard; dense one-screen layout, no scroll.
Matches moodboard direction; WCAG AA contrast. ✓ 2026-07-06
Installable to home screen for street use; no offline cache yet.
Capacitor 8 SPM shell around the React app. Simulator build green, app boots and renders on iPhone 16 Pro sim. Location permission string set.
Unit toggle on the hero — tap °C/°F. Deltas scale ×1.8 (no offset), absolutes convert fully. Persisted.
Refetch on focus, 10-min TTL, stale badge past TTL. Closes audit-2 gap (cadence unspecified).
JAG/TI wind chill below 10°C; blend zone 10–15°C so the formula is continuous.
24h graph of True Feel vs base air temp with comfort windows highlighted; same formula over hourly forecast. Promoted from PARKED.
Toggles apply across all 24 hours; windows visibly bounded. ✓ 2026-07-07
React ErrorBoundary — render crash shows a recoverable screen, never white. Trace to console until a vendor DSN exists.
Rolling 14-day local baseline shifts perceived severity; spike delta flags Weather Shock. Local-only history keeps it GDPR-free.
Height/weight/metabolism/clothing — bounded thermal-sign heuristic, on-device only, zero-effect defaults. Model in DECISIONS.md.
Haiku via Worker proxy, KV location-hour cache, graceful 503 without key; client line hides when absent.
Sentry (or equivalent) — needs Jason to create the account/DSN. Boundary hook point is ready in ErrorBoundary.componentDidCatch.
Live at realtemp-api.jason-poindexter.workers.dev — D1 schema applied, KV bound, endpoints verified in production. User-authorized deploy.
https://realtemp-rho.vercel.app — API base baked in, report loop verified end-to-end in production. User-authorized deploy.
Payload-free web push: VAPID ES256 (crypto round-trip tested), daily cron heat check, SW + threshold panel. Deploy = runbook §2.
Figure/ground SVG: Overpass footprints + levels → live sun-cast shadows (azimuth+zenith). Verified against real Valencia center. Beta-labeled.
Root cause: upstream 5xx returns no CORS headers, Chrome reports generic "blocked by CORS policy" instead of the real transient failure — reproduced live, curl confirmed transient (200 OK moments later). Fix: 3-attempt retry with 300/900ms backoff on the primary weather fetch before surfacing an error screen. New test reproduces the exact bug (fail twice, succeed third) and asserts recovery.
Ran all 19 dec-* skills against the shipped app. Fixed: --solar token measured 2.86:1 on paper (WCAG AA needs 4.5:1) — darkened to 5.19:1, verified by computed luminance not eyeballing. Danger warnings now role=alert (assertive) vs caution role=status (polite) — screen-reader users get the same urgency split sighted users get from color. Settings gear + new About/Help icon both brought to real 44px hit areas per the app's own --tap token. About split out of Settings (help vs configuration are different jobs, Nielsen #10). Full report in session log.
Ran grid-and-spacing + layout-and-composition skills. Found: 2 of 3 collapsible panels (Push, Shade) silently overrode the shared .body-panel CSS with a duplicate inline paddingTop, rendering inconsistent internal rhythm vs BodyPanel — removed. One fully dead inline style (About.tsx) overriding CSS that was never set. 9 off-grid magic-number spacing values (10px/6px/2px/14px, none on the 4pt scale) snapped to existing tokens. 2 stray 13px type sizes consolidated into --text-seg. Focus-ring offsets and hairline borders deliberately left as literals — sanctioned exceptions per the skill, not drift. Verified visually: all three panels now share identical rhythm.
Ran the 9 remaining always-on design skills (design-tokens, color-and-elevation, components-and-states, interaction-and-motion, app-motion-and-animation, accessibility-and-inclusive-design, ethical-design-and-dark-patterns, iconography-and-imagery, ux-writing-and-content) — full coverage of all skills tagged always-on for design work. Fixed: zero .btn:disabled/:hover CSS existed despite 6 real disabled= usages (Search/Enable/vote buttons looked identical whether clickable or not — verified computed style). Replaced Unicode gear/info glyphs with proper 24px-grid inline SVG (currentColor, matches skill guidance against icon-font-style rendering risk). Clean audits, no findings: ethical-design-and-dark-patterns (no dark patterns present — checked consent/urgency/cancel/confirmshame), ux-writing-and-content (errors already what/why/how-fix, consistent terminology). Named, not fixed: no dark mode (deliberate — moodboard optimized for sunlight readability, real gap for night use, sized as its own feature); tokens in px not rem (ignores OS text-size zoom, real but broad mechanical risk — flagged for Jason, not auto-applied).
Both flagged gaps from the design-skill audit, now fixed. rem: all type/space tokens converted (16px root baseline) — respects OS/browser text-zoom (WCAG 1.4.4); borders/rule-widths/--tap/--app-width stay px deliberately (physical constants, not text). Dark mode: automatic via prefers-color-scheme, not an invert — dimmed warm-white text, near-black-not-pure-black bg, danger polarity flips (dark text on the now-light scorch fill). All 13 color pairs computed against WCAG luminance formula, not eyeballed — every pair passes AA (worst case 4.77:1, most 6-14:1). Verified visually via injected preview + full 75-test regression, zero layout shift from the unit change.
Redeployed worker with push routes (no secret). Root-caused a real bug during verification: push_subscriptions table existed locally but was never created on remote D1 — INSERT threw, surfaced as opaque Cloudflare error 1101. Created the table remotely, then verified a full subscribe->unsubscribe cycle end-to-end against production. Sending (cron) stays dormant until Jason adds VAPID_PRIVATE_JWK — that secret never transits my tool calls.
/userflow audit (flow-onboarding, flow-permissions, flow-empty-states, flow-errors) found a real bug: refetch() changed the hooks internal key, which the render logic read as no data yet, blanking the entire dashboard back to Reading the street... on every background TTL refresh -- discarding good data and defeating the isStale badge entirely. Rewrote useWeather to key displayed data on location only, with isRefreshing derived separately (never hides content); a failed background refresh now keeps old good data instead of showing an error. Falsification-tested: new regression test fails against the reverted old hook, passes against the fix. Full flow-report.html delivered with the complete 4-skill audit.
Jason reported the shade panel failing every single time, regardless of timing — ruled out transient overload immediately since real bugs are consistent, not random. Reproduced live in Chrome against prod: all 3 Overpass mirrors returned 503/timeout. Root-caused via direct curl A/B test: identical request, only the User-Agent header differs -> 406 without it, 200 with it. Browser fetch() can never set User-Agent (forbidden header, browser-enforced) -- client-side retries/mirrors could never have fixed this, structurally. Built a server-side proxy (GET /api/buildings) on the existing realtemp-api Worker with a proper UA + KV cache shared across all users (upgrade over the old per-device cache). Verified end-to-end in production: 213 real buildings returned for central Valencia, second call served from cache, then re-verified live in the browser on a fresh page load -- real shadows rendering, no error box.
Open-Meteo adapter now requests and parses current humidity, precipitation, rain, showers, weather code, cloud cover, and hourly precipitation probability. Dashboard renders current weather factors plus a next-6-hour forecast with True Feel, condition, rain chance, precipitation, and cloud cover. Full test/build/deploy gate passed and production alias returned 200.
Split the long iPhone dashboard into top-level Now, Forecast, and Tune tabs. Now keeps current factors and transparent math first; Forecast holds timeline, sweat, forecast, and shade map; Tune holds microclimate controls, acclimatization, body settings, and push setup. Tested by dashboard integration coverage and deployed to production.
Settings button was correctly wired but its inline SVG looked like a sun/ray glyph. Replaced the path with a gear-shaped icon while keeping the separate moon/sun light-dark toggle intact. Full test/build/deploy gate passed and production alias returned 200.
Jason compared Apple Weather to RealTemp and found the app lagging. Root cause: RealTemp could sit open on a cached reading and only refetch after focus/visibility changed. Added one auto-refresh per stale reading once the 10-minute TTL passes while the dashboard remains open, with a regression test proving a stale open dashboard fetches and updates the displayed air temperature.