AI Generation & Media Pipeline
This section explains how DreamStream turns a dream entry into analysis + visuals in the background, how results appear “live” in the UI, and how DreamStream stores images/audio securely.
The promise to the user (plain language)
When you log a dream, DreamStream doesn’t force you to wait.
- The dream saves immediately.
- AI results (analysis and visuals) appear as they finish.
- If something fails, you see a clear message and can retry.
What DreamStream generates
Automatically generated (default after saving a dream)
DreamStream queues these background tasks for most newly saved dreams:
| Output | What it is (user-facing) | Where it appears |
|---|---|---|
| Analysis | Supportive interpretation, key dream signs/tags, summary hook, suggested actions, optional archetype | Dream Detail → Insights |
| Dream image | A single “hero” visualization of the dream | Dream Detail → Visuals |
| Comic | A stylized multi-panel visual representation | Dream Detail → Visuals |
Dream Signs (tags) inside analysis
- The analysis model returns two types of tags:
- Dream Profile patterns: 2–3 broad pattern tags chosen from people, tension, feelings, places, body, pressure (analytics only).
- Dream Signs: 3–5 concrete, user-visible signs (symbols, people, places, feelings, actions).
- Signs are normalized into consistent, human-readable labels for browsing (e.g., Family, Home, Flying).
- Users can edit Dream Signs later in Dream Detail. Legacy/internal tags are filtered from the Dream Signs UI list.
- Analytics (Dream Radar, Deep Insights) uses the stable Dream Profile patterns above. Stress remains a separate numeric metric.
On-demand (user-triggered)
These are typically generated only when the user chooses to:
| Output | What it is (user-facing) | Where it appears |
|---|---|---|
| Insight Lens | A secondary “lens” visual variant / decoder-style image | Dream Detail → Visuals (Lens) |
| Healing visual | A “safer” / comforting re-visualization, often paired with nightmare rewrite work | Dream Detail → Healing |
Insight Lens (Dream Decoder)
The Insight Lens is a unique visualization that reveals hidden meanings behind dream elements.
What users see
- A before/after slider comparing:
- The original dream image
- An annotated "insight" version with symbolic overlays
- Text annotations explaining what key elements represent
Example annotations
| Visual element | Insight annotation |
|---|---|
| Locked door | "Fear of the unknown" |
| Dark water | "Suppressed emotions" |
| Flying | "Desire for freedom" |
Generation behavior
- User taps "Generate Insight Lens" in Visuals section
- A background job creates the annotated image
- Result appears via realtime update
Refinement Chat (Image Iteration)
Users can iterate on their dream images through conversational AI.
What users see
- A chat modal opens when tapping "Refine Image"
- Users type instructions like:
- "Make the sky more purple"
- "Add more stars"
- "Make it look more dreamlike"
How it works
- User sends a refinement request
- AI generates a new image based on the instruction + original context
- New version is saved
- User can continue refining or save the version
Version history
- Each refinement creates a new version
- Users can browse previous versions
- Revert to earlier versions if desired
How the background pipeline works (conceptual)
DreamStream uses a background “job” system. Think of a job as:
- A single piece of AI work (e.g., “generate image”).
- With a clear status (pending → processing → completed/failed).
Job statuses (user-visible meaning)
| Status | What it means to the user |
|---|---|
pending |
The work is queued and will start soon |
processing |
The work is actively running |
completed |
The result is ready and will appear automatically |
failed |
The work did not succeed (user can retry) |
End-to-end lifecycle: from dream → results
flowchart TD
A[User logs a dream] --> B[Dream saved]
B --> C["Background jobs created<br/>(analysis + image + comic)"]
C --> D[Job processor claims work]
D --> E[AI generates result]
E --> F[Media stored securely]
E --> G["Dream record updated<br/>(status + result fields)"]
G --> H[App receives realtime update]
H --> I[Dream UI updates automatically]
Live updating in the app (no manual refresh)
DreamStream subscribes to background job updates.
- If a job changes to processing, the app updates the dream’s UI state (spinners / “generating…”).
- If a job becomes completed, the app fetches the latest dream record and displays the final content.
- If a job fails, the app shows a toast message (e.g., "Image generation failed. Tap the dream to retry.").
Why this matters (product)
- Users can keep scrolling their journal while AI runs.
- Results “pop in” without pulling to refresh.
- Users immediately learn if something failed.
Zero-refresh experience
Users never need to manually refresh to see AI results. The app subscribes to realtime updates and displays content automatically as it completes.
Failure + retry behavior (important for Support)
What users see
- A specific output can fail without breaking the whole dream.
- During automatic retry, the dream typically stays in a loading/spinner state (auto-retry is happening in the background).
- If all automatic retries fail, the UI shows a "Retry" button.
- After ~90 seconds of an active generation state (pending/processing/retrying), Dream Detail shows a prominent timeout state (e.g. "Something seems wrong") with a retry button.
90-second timeout vs stuck job detection
The 90-second timeout UI is a convenience so users aren't stuck watching spinners. It does NOT cancel the in-flight attempt—if the backend succeeds later, the result still appears. The backend stuck-job detection (10-30 minutes) is a separate self-healing mechanism that runs automatically.
Status fields (internal)
There are two related status fields:
generation_jobs.statustracks the job lifecycle in the queue.dreams.<type>_statustracks the dream output status the UI reads (image/comic/analysis/insight/healing).
generation_jobs.status
| Status | What it means |
|---|---|
pending |
Queued, waiting to be picked up |
processing |
Actively generating |
completed |
Finished successfully |
failed |
All retries exhausted, user can manually retry |
dreams.<type>_status
| Status | What it means |
|---|---|
idle |
Nothing queued (or draft dream) |
pending |
Queued, waiting to start |
processing |
Actively generating |
retrying |
A prior attempt failed; the job was automatically re-queued |
completed |
Finished successfully |
failed |
All retries exhausted (user can manually retry) |
Behind the scenes
- Jobs have a retry counter (
retry_count) and a maximum retries limit (max_retries, default: 3). - If a job fails but still has retries remaining, it is automatically re-queued (
generation_jobs.statusreturns topending,retry_countincrements) and the corresponding dream status becomesretrying. - Backend processor scheduler (pickup mechanism): a Cloudflare Worker cron (
dreamstream-job-processor-scheduler) calls theprocess-generation-jobsEdge Function every 2 minutes usingx-job-secret. - This is the only pickup mechanism. The client never invokes the job processor directly.
- Operational requirement:
process-generation-jobsmust be deployed withverify_jwt=falseso the Worker can call it; the function itself enforcesx-job-secret. - Comic delegation (internal helper): comic jobs are delegated to a separate
generate-comicEdge Function. generate-comicis also deployed withverify_jwt=falseand enforcesx-job-secret(it should never be callable by clients).- Backend stuck-job reaper cron (safety mechanism): independently, a backend cron runs the stuck-job reaper every 2 minutes to recover jobs that were claimed but never finished (e.g. crashed worker / timeout).
- The reaper only affects jobs stuck in
processing(it re-queues them aspendingor marks themfailedafter retries) and does not startpendingjobs.
Scheduler health is P0
Job pickup is server-only. If the Cloudflare cron is disabled/misconfigured or process-generation-jobs is deployed with verify_jwt=true, then pending jobs will not be processed automatically.
Common symptoms:
- Many generation_jobs.status='pending' rows that never move to processing
- Edge logs show POST 401 to process-generation-jobs (JWT gate) or show no traffic at all (cron down)
Runbook checks:
- Confirm Cloudflare Worker cron is running (dreamstream-job-processor-scheduler)
- Confirm process-generation-jobs.verify_jwt=false
- Confirm Edge logs show periodic POST 200 to process-generation-jobs
- If retries are exhausted (typically after 3 attempts), the job becomes failed and stays visible as failed.
Stuck job detection (self-healing)
Jobs that get stuck "processing" for too long are automatically recovered:
- A backend cron runs the stuck-job reaper every 2 minutes to recover jobs that were claimed but never finished (e.g. crashed worker / timeout).
- The reaper only affects jobs stuck in
processingand does not startpendingjobs.
| Job type | Timeout | What happens |
|---|---|---|
| Analysis, Image | 5 minutes | Auto-retry or fail |
| Insight, Healing | 5 minutes | Auto-retry or fail |
| Comic | 5 minutes | Auto-retry or fail |
| Deep Insights | 5 minutes | Auto-retry or fail |
| Video | 30 minutes | Auto-retry or fail |
Reaper thresholds vs. expected runtime
These timeouts are stuck-processing detection thresholds (safety net), not the expected runtime. Note that because the reaper cron runs every ~2 minutes, a “5 minute” threshold is typically enforced at ~6 minutes (next cron tick).
When a stuck job is detected:
- If retries remain → job is re-queued (
pending), anddreams.<type>_statusbecomesretrying - If retries exhausted → job becomes
failed, anddreams.<type>_statusbecomesfailed(UI shows retry button)
Auto-retry limits
Jobs automatically retry up to their max_retries limit. If all retries fail, users must manually tap retry in the Dream Detail view.
“Recovery” if the app closes mid-generation
DreamStream attempts to recover partially-generated dreams:
- On load, it scans for dreams still marked as
processing/pending. - If any essential output is missing (analysis/image/comic), it can re-queue the missing work.
This prevents “stuck dreams” after network drops or app restarts.
Deep Insights (multi-dream pattern report)
Deep Insights is a separate user-level background workflow, built to synthesize long-range patterns without slowing the app.
How it’s queued
- The UI enqueues/refreshes a single
deep-insightsjob via the DB RPCpublic.enqueue_deep_insights_job()(no direct client writes togeneration_jobs). - The server job processor picks it up on the next scheduler tick (typically within ~2 minutes).
What gets analyzed
- Pulls the 50 most recent real dreams (samples never reach the database).
- Requires 3+ real dreams; otherwise the job fails fast and the UI stays gated.
- Uses Known People from the profile as grounding context when available.
- Lucidity triggers are only requested if the user has lucid dreams in the dataset.
Output shape (what the AI is asked to return)
Deep Insights is instructed to output a structured report including:
- Mind State summary + dominant archetype
- Mind State evidence (2-3 dream IDs)
- Recurring elements (only if they appear in 2+ dreams, max 6)
- Dream Evolution narrative + evidence
- Emotional Trend (short, evidence-backed)
- Waking Life Correlations (short, evidence-backed)
- Action Plan components (takeaways, prompts, weekly plan, practices, triggers)
- Lucid triggers (only for lucid dreamers)
- Evidence references that point back to dream IDs from the 50‑dream window
The UI hides any sections that come back empty, so the report always feels tailored rather than padded.
Trust and evidence
Deep Insights must show its work. For major claims (Mind State, Dream Evolution, Emotional Trend, Waking Life Connections), the model includes small evidence arrays of dream IDs. If evidence is weak, those arrays should be empty rather than guessed.
Refresh logic & caching
The report is stored on the user profile and reused until stale. It regenerates when:
- There are 3+ new dreams since the last report, OR
- The report is older than 7 days and at least one new dream was logged, OR
- The report format was upgraded (new fields were added)
User-visible behavior
- If a fresh report exists, it opens instantly.
- If a report is generating, users can close the modal and receive “Deep Insights Ready” when it finishes (push notifications must be available).
Media storage model (privacy-first)
DreamStream stores generated media (and some user-uploaded media) in private storage.
Core rules
- Each user’s files are stored under a user-specific folder prefix.
- Media is served through an authenticated proxy so random URLs can’t be shared publicly.
- The app uses signed URLs that can be refreshed automatically.
What file types DreamStream handles
| Category | Examples |
|---|---|
| Images | avatar, digital twin, dream visuals, comics |
| Audio | draft recordings (quick capture) |
Upload flow (user uploads and some AI outputs)
sequenceDiagram
participant A as App
participant F as Upload Helper
participant R2 as Media Storage
participant P as Media Proxy
A->>F: Ask for a pre-signed upload URL
A->>R2: Upload file directly (PUT)
A->>P: Build a signed viewing URL
A-->>A: Store only the file path in the database
Viewing flow (how images load securely)
sequenceDiagram
participant UI as App UI
participant P as Media Proxy
participant R2 as Media Storage
UI->>P: GET /<path>?token=<session>
P->>P: Verify token + confirm path belongs to user
P->>R2: Fetch file
P-->>UI: Stream file bytes
Automatic refresh of signed URLs
Signed URLs may expire over time. DreamStream’s image components:
- Can generate a fresh signed URL using the user’s current session.
- Can retry automatically if an image fails to load.
This reduces “broken image” experiences.
Deletion & cleanup
DreamStream deletes media in two main cases:
- The user deletes a dream (clean up associated image/comic variants).
- The user replaces a profile media item (e.g., new Digital Twin), cleaning up the old file.
There is also server-side cleanup for old background jobs:
- Completed jobs can be removed after a time window.
- Failed jobs can be removed after a longer time window.
- Stuck processing jobs are automatically reset to
pending(with retry increment) or markedfailedif retries are exhausted. Dream statuses are synced accordingly.
Notifications tied to AI completion
DreamStream uses two notification styles:
- Local notifications (device scheduled)
- Example: “Don’t lose your dream” morning recall reminder.
- Push notifications (server-triggered)
- Example: “Dream Ready” when generation finishes.
Push notification events (as implemented)
Currently sent
| Event | Trigger | Purpose |
|---|---|---|
| Dream Ready | When all queued jobs for a dream are finished | Bring the user back to review what's ready |
| Deep Insights Ready | When the Deep Insights report finishes generating | Invite the user to review long-term patterns |
| Streak reminder | If the user hasn't logged a dream recently | Soft encouragement, no heavy gamification |
Defined in code but may not be active
None.
User notification preferences are respected where applicable (for example, Dream Ready can be skipped if the user disabled the relevant preference in their profile).
Troubleshooting signals (Support-facing)
- “My dream is stuck on generating.”
- Likely: job stalled, network drop, or background worker delay.
-
User action: open the dream and tap retry for the failed/stuck item.
-
“Images stopped loading.”
- Likely: signed URL expired or session changed.
-
The app auto-refreshes signed URLs; sign-out/sign-in can fix persistent session issues.
-
“I didn’t get a notification.”
- Check: user disabled notifications, system permission denied, or push not supported on web.
Implementation map (for accuracy checks)
- Job queue helpers (client):
src/lib/jobQueue.ts - Realtime job subscription + UI updates:
App.tsx - Dream Detail generation/retry buttons:
components/DreamDetail.tsx - Deep insights background report:
components/DeepInsightsModal.tsx - Job processor (server):
supabase/functions/process-generation-jobs/index.ts - Comic generator (server helper):
supabase/functions/generate-comic/index.ts - AI proxy (server):
supabase/functions/gemini-proxy/index.ts - Upload helper (server):
supabase/functions/r2-upload-helper/index.ts - Media proxy (Cloudflare Worker):
workers/media-proxy/src/index.ts - Notification cron:
supabase/functions/send-streak-reminders/index.ts - Signed URL + upload utilities (client):
src/lib/storage.ts - Auto-refresh image loader:
components/LazyImage.tsx