Home / Ops / More in this area

Cross-device "remember me" for the results hub - Build Brief v1

Updated Jun 29, 2026 · Affirmology_CrossDevicePersistence_BuildBrief_v1.md

Summary. For: the Claude Code session on the Mac (repo + Cloudflare/worker + cloud studio access). Goal: when someone opens their emailed link to result.html<runId on ANY device, the page recognizes them, greets them by name, and shows their finished hub (replayable so

Cross-device "remember me" for the results hub - Build Brief v1

For: the Claude Code session on the Mac (repo + Cloudflare/worker + cloud studio access). Goal: when someone opens their emailed link to result.html#<runId> on ANY device, the page recognizes them, greets them by name, and shows their finished hub (replayable song + the mission/asks/beta) without re-running the film or ritual. Today it only works on the same device because it reads localStorage.

The key idea (no new identity system needed)

Backend change (the only one)

GET /api/status/<jobId> must return the visitor's name alongside the existing fields. The render request (POST /api/render) already receives name and email; persist them on the job record and include name (first name is enough; full name + email optional) in the status JSON. - Worker: affirmology-demo-site/worker/src/index.js -> handleStatus() proxies to the cloud studio /status/<jobId>; just pass through whatever the cloud returns. - Cloud studio (affirmology-studio, the demo bridge /status handler): add name (and optionally email) to the status payload from the stored job. - Resulting shape (superset of today): { state|status, audio_url|mp3_url, pdf_url, blueprint, name, email? }.

Frontend change (affirmology-site/result.html)

Current relevant anchors: runId/filmMode/firstName are parsed near the top (const runId = _rawHash.split('&')[0], const filmMode = _rawHash.indexOf('&film') !== -1, firstName from the localStorage affirmology:<runId> blob); startPolling() already fetches /api/status/<runId>; showReturning() already renders the finished hub; revealAfterListen() sets localStorage 'affirmology:done:'+runId.

Make the status fetch authoritative: 1. In startPolling() (or a small init fetch), when status returns, also read j.name; if present, set the displayed name everywhere firstName is used (update #name-delivered, #audio-title, the email line). Fallback order: backend name -> localStorage intake name -> "Friend". 2. Returning-visitor trigger should be: if ((!filmMode && jobDone) || localStorageDoneFlag) showReturning(); where jobDone = state==='done' || status==='success' || !!audio_url. Since the first status poll may take a moment, run an immediate one-shot fetch on load so a return visit resolves fast (show a quiet "loading your page" placeholder, NOT the old film/ritual, while that first fetch is in flight when !filmMode). 3. Keep localStorage as a same-device fast path (instant), but never REQUIRE it. 4. Make sure the audio src + #download-audio href get set from the status audio_url on return (showReturning already polls; confirm it populates once results.audio_url arrives).

The delivery + drip emails (Affirmology_DemoDripEmails_v1.md, {{page_link}}) must link to https://demo.affirmology.ai/result.html#<runId> (no &film), so every emailed return lands in the hub via the returning path.

Acceptance

Guardrails