RealTemp Roadmap

Street-true perceived temperature · client-only web · Open-Meteo · PRD v2.0 2026-07-06 · 15 slices · 39 shipped

Street-true temp → visible math → trust → daily habit → hyperlocal premium

FormulaDataUIOps/ShipAI⊘ gated

Now — v0

Rocks

SP5 · Harden native iPhone shellOps/Ship
v0

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.

Done criteria

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.

SC1a · Instrument the ledgerOps/Ship
v2

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.

Done criteria

Two weeks of personal use produce a readable answer to: was the breakdown opened, and did a toggle change a decision?

MC1b · 14 days daily street useOps/Ship
v2

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.

Done criteria

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.

SC2a · Price the Open-Meteo commercial planData
v2

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.

Done criteria

Current commercial pricing recorded in docs/, with a per-user cost floor and a go/no-go on the provider.

MC2b · Resolve the accuracy claimFormula
v2

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.

Done criteria

PRD non-goal and the intended paid claim agree in writing, appended to DECISIONS.md.

Pebbles

SP4 · Run on Jason's iPhone + street checkOps/Ship
v0

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.

Done criteria

All PRD v0 checkboxes checked from a phone browser.

Next — v1

Rocks

MC3a · Five users who are not JasonOps/Ship
v2

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.

Done criteria

5 recruited, instrumented. CONTINUATION RULE: fewer than 3 still using at day 14 means reposition or stop.

Pebbles

SN7 · Real name + domainOps/Ship
v1

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.

closes P3-2 (trademark collision)
SC3b · Positioning: category and value mapOps/Ship
v2

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.

Done criteria

docs/positioning.md: alternatives, differentiated capability, capability-to-value map, best-fit segment, chosen and rejected categories.

Sand

SN6c · Paste Sentry DSNOps/Ship
v1

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.

Later — v2+

Rocks

MC4a · Custom alert rules over ledger termsOps/Ship
v2

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.

Done criteria

A rule (term, above/below, threshold, window) can be created, stored, evaluated by the cron, and delivered.

SC4b · Packaging and priceOps/Ship
v2

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.

Done criteria

Tier list, price, and value metric recorded in DECISIONS.md, with the Open-Meteo cost floor from C2a.

Pebbles

LL4b · Shade precision: PNOA-LiDAR tilesAI
v2

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.

MC4c · Billing and first paying customerOps/Ship
v2

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.

Done criteria

One paying customer who is not a friend or family member.

MC5 · Conversational explanation layerAI
v2

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.

Done criteria

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.

✅ Shipped (39)
MN8 · Official weather alerts (AEMET via MeteoAlarm)Data
v1

GATED: worker deploy now DONE (push routes live). Remaining: CAP-feed mapping spike + province geocoding for AEMET/MeteoAlarm.

Done criteria

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

closes official warning feed mapping for Valencia daily use
MN9 · Native-feeling launch/loading handoffUI
v1

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.

Done criteria

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

closes launch perceived-speed gap and white/default native splash handoff
MN10 · Progressive-disclosure dashboardUI
v1

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.

Done criteria

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

closes first-screen cognitive-load gap
MR1 · Formula module — computeTrueFeel()Formula
v0🔒

Steadman AT (dew-point vapor pressure) + zenith-weighted solar premium + env/activity deltas + sweat efficiency. Pure fn, all coefficients in constants.ts.

Done criteria

Unit tests pass against hand-computed fixtures incl. night (solar=0), high dew point, windy cases. ✓ 2026-07-06

closes P0-1, P0-2 (unimplementable formula, no reference model)
MR2 · Open-Meteo adapter (Zod boundary)Data
v0🔒

One call: temperature_2m, dew_point_2m, wind_speed_10m (request m/s, scale ×0.6 street), uv_index. Errors as values.

Done criteria

Valid payload parses; missing field degrades that premium, never NaN; fetch failure surfaces retry state. ✓ 2026-07-06

closes P0-6 (provider), P1-8/9 (edge cases)
SR3 · Location: geolocation + city searchData
v0🔒

Browser geolocation with Open-Meteo geocoding search fallback; last location persisted locally.

Done criteria

Denied-permission path lands on search; picked city fetches correctly. ✓ 2026-07-06

closes P1-7 (location unspecified)
LR4 · Hero dashboard + transparent math breakdownUI
v0🔒

True Feel dominant; each premium a signed line (base + sun + humidity − wind + urban = X°). The product's whole differentiator.

Done criteria

Breakdown always sums exactly to displayed True Feel; degraded fields hidden with badge. ✓ 2026-07-06

closes Epic 1
MR5 · Microclimate toggles (Exposure/Environment/Activity)UI
v0🔒

Three segmented controls, instant recalc, persisted to localStorage.

Done criteria

Every combination recalculates <16ms and updates the breakdown lines. ✓ 2026-07-06

closes Epic 2
SP1 · Sweat Efficiency widgetUI
v0

0–100% evaporative-cooling gauge from dew point (100% ≤10°C → 0% ≥26°C).

Done criteria

Renders with label + value; color not the only encoding (a11y). ✓ 2026-07-06

SP2 · Scaffold: Vite + React + TS strict + Vitest + ESLintOps/Ship
v0🔒

git init, first commit with planning docs + rockmap.

Done criteria

npm run typecheck/test/build/lint all runnable. ✓ 2026-07-06

SP3 · Degraded & error statesFormula
v0

Night zenith → solar 0; stale data badge; offline/fetch-fail with retry; never NaN on screen.

Done criteria

Each state reproducible via test and visible in UI. ✓ 2026-07-06

SS1 · Visual pass from Mobbin moodboardUI
v0

Type scale, palette, spacing tokens per moodboard; dense one-screen layout, no scroll.

Done criteria

Matches moodboard direction; WCAG AA contrast. ✓ 2026-07-06

SS2 · PWA manifest + iconOps/Ship
v0

Installable to home screen for street use; no offline cache yet.

MC1 · iOS shell (Capacitor)Ops/Ship
v0

Capacitor 8 SPM shell around the React app. Simulator build green, app boots and renders on iPhone 16 Pro sim. Location permission string set.

SN5 · °F toggleUI
v1

Unit toggle on the hero — tap °C/°F. Deltas scale ×1.8 (no offset), absolutes convert fully. Persisted.

SN4 · Refresh cadence + cachingData
v1

Refetch on focus, 10-min TTL, stale badge past TTL. Closes audit-2 gap (cadence unspecified).

SN3 · Cold-weather term (wind chill)Formula
v1

JAG/TI wind chill below 10°C; blend zone 10–15°C so the formula is continuous.

MN1 · Hourly Safe Window timelineUI
v1

24h graph of True Feel vs base air temp with comfort windows highlighted; same formula over hourly forecast. Promoted from PARKED.

Done criteria

Toggles apply across all 24 hours; windows visibly bounded. ✓ 2026-07-07

SN6a · Crash boundary (error tracking, local half)Ops/Ship
v1

React ErrorBoundary — render crash shows a recoverable screen, never white. Trace to console until a vendor DSN exists.

ML3 · Acclimatization Matrix + Weather ShockFormula
v2

Rolling 14-day local baseline shifts perceived severity; spike delta flags Weather Shock. Local-only history keeps it GDPR-free.

LL2 · Bio-calibration (height/weight/metabolic) + Clo profilesFormula
v2

Height/weight/metabolism/clothing — bounded thermal-sign heuristic, on-device only, zero-effect defaults. Model in DECISIONS.md.

closes PARKED bio-calibration + Clo
SL5a · LLM weather copy — code completeAI
v2

Haiku via Worker proxy, KV location-hour cache, graceful 503 without key; client line hides when absent.

SN6b-code · Sentry seam — DSN-gated, zero-cost when unsetOps/Ship
v1

Sentry (or equivalent) — needs Jason to create the account/DSN. Boundary hook point is ready in ErrorBoundary.componentDidCatch.

SL1b · realtemp-api deployed to CloudflareOps/Ship
v2

Live at realtemp-api.jason-poindexter.workers.dev — D1 schema applied, KV bound, endpoints verified in production. User-authorized deploy.

SP4w · Web app deployed — phone URL liveOps/Ship
v0

https://realtemp-rho.vercel.app — API base baked in, report loop verified end-to-end in production. User-authorized deploy.

ML8a · Heat-warning push — code completeUI
v2

Payload-free web push: VAPID ES256 (crypto round-trip tested), daily cron heat check, SW + threshold panel. Deploy = runbook §2.

LL4a · Shade map v0 — OSM shadow castingAI
v2

Figure/ground SVG: Overpass footprints + levels → live sun-cast shadows (azimuth+zenith). Verified against real Valencia center. Beta-labeled.

closes PDF v1.0 §3.2 + Tier-2 CV
SBUG1 · Fix: Open-Meteo transient failure masked as CORS blockData
v0

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.

MDEC1 · Design-engineering audit: contrast fixes, touch targets, alert urgency, About/Settings splitUI
v0

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.

SGRID1 · Grid & spacing audit: off-token drift, dead inline styles, inconsistent panel rhythmUI
v0

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.

SDEC2 · Final design-skill sweep: disabled states, real icons, dark-mode gap namedUI
v0

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).

MDEC3 · Dark mode + rem-based type/space tokensUI
v0

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.

SL8b · Push routes deployed — subscribe/unsubscribe liveOps/Ship
v2

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.

MBUG2 · Fix: background weather refresh was blanking the populated dashboardUI
v0

/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.

MBUG3 · Fix: shade map failed 100% of the time — Overpass needs a User-Agent browsers cannot sendData
v0

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.

MWX1 · Weather factors + short forecastData
v1

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.

MTAB1 · iPhone dashboard tabsUI
v1

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.

SUI1 · Header icon polish: real settings gearUI
v0

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.

SBUG4 · Fix: open dashboard could drift behind live weatherData
v0

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.