Inspiration
I kept losing my dreams. Not metaphorically - I would wake up with something vivid and complete, reach for my phone to write it down, and by the third sentence the ending was already gone. Typing is too slow for the ninety seconds a dream survives. Speaking isn't.
So the app has exactly one button, in the middle of the screen, and you talk to it. Everything else is what happens afterwards.
What it does
You hold the button and describe the dream out loud. DreamCrane transcribes it - over sixty languages, detected automatically, you never pick one - rewrites the description into an image prompt, and paints it in about ten seconds. The painting lands in a private journal with the transcript underneath.
From there you can ask what it meant, through five interpretive lenses: classic, Jungian, folk, poetic, and plain language. You can animate the painting into a short video. And every seven dreams close into a constellation - the app pulls motifs out of your dreams, groups the recurring ones, and names the group. One dream tells you very little. An atlas of forty tells you what your sleep keeps circling back to.
How I built it
Vue 3 and Capacitor on the client, NestJS and PostgreSQL on the server, RevenueCat for everything involving money.
The generation path is a pipeline, and almost every stage exists because something went wrong without it:
audio → Soniox (speech to text) → moderation → LLM prompt builder → moderation again → fal.ai (grok-imagine) → Cloudflare R2
Images stream inline over SSE, because six to ten seconds is short enough to watch and too long to stare at a spinner - the client gets real progress and the dream id before the picture exists. Video takes minutes instead of seconds, so it goes through a job queue (pg-boss, living in the same Postgres - no Redis, one less thing to run).
There is no WebGL anywhere. The landscape behind the button - sky, water, mountains, islands, stars - is programmatic CSS and SVG driven by your actual local time, so the app is dark at 3am and pale at dawn.
Challenges I ran into
The image model refused ordinary dreams, and every refusal cost money.
A user dreamt he was riding a motorcycle with a woman behind him, undressed. Our moderation passed it - it isn't a policy violation. The image provider's content checker refused it anyway. We were billed for the rejection, the dream failed, the user's daily slot was gone, and he was left looking at an empty gallery with a countdown running. Nudity, blood, death and being chased are not edge cases in dreams. "Naked in public" is one of the most common dreams there is.
The fix was a language model between transcription and generation: it cleans up speech, translates to English, and rewrites sensitive material into something that can be painted. It is a rewriter, not a laundering machine - refusal is part of its contract, and it knows nothing about filters, because asking a model to "make this pass" teaches it obfuscation and moves the picture away from the dream.
Then the rewriter started refusing ordinary dreams too.
A measured run found thirty-four false refusals. "I lost my keys" - refused, five times out of five. "I dreamt about my grandmother" - refused, reason: real person. The instruction demanded a forty-to-eighty word scene with light and colour, and simultaneously forbade inventing anything. From a four-word dream, there was no way to satisfy both.
Prohibitions made it worse every single time. Writing "nudity is never depicted" sent the model into inventing refusal reasons that had nothing to do with the input. Writing "injury is never shown" made it refuse nightmares outright. The same rules stated positively - "fear and violence are not grounds for refusal: write it wide, keep the injury out of frame" - worked immediately. Four iterations of that, and false refusals went to zero with every real block still in place.
Moderation scores are not comparable across languages. The same sentence scored 0.287 in Russian and 0.880 translated into English. Any threshold tuned on one language is meaningless in another - which is why the defence is the rewriter, which translates, rather than a number.
Apple rejected the app four times. Every one taught something specific: a pre-permission screen must lead to the system prompt with no exit sideways; the Sign in with Apple button needs the logo file from Apple's own design resources, not a lookalike; offering reviewers a promo code is itself a violation of the in-app purchase rules; requiring registration before a purchase is not allowed; and disclosing third-party AI processing in the privacy policy is not enough - it has to be in the interface, naming who receives what.
On a real phone the main screen ran at thirteen frames a second. The blurs were the obvious suspect and the wrong one. The actual cause was CSS custom properties used inside keyframe values: that cannot be composited, so every frame went through style recalculation and layout on the main thread. Removing backdrop-filter over the moving landscape fixed the rest - over animated content it repaints every frame and can never be cached.
What I learned
Tell a language model what to do, never what not to do. A prohibition is an instruction to think about the forbidden thing, and models comply by refusing things you never asked about.
Don't tell a model the language when you've given it material in that language - it follows the label instead of the text. Removing the label was the only thing that worked; arguing with it in the prompt did not.
Measure on the device. Every performance assumption I had was wrong, and the profiler said so in about a minute.
What's next
Better retention on the first dream - the data says most people who record one never record a second, and that's a product problem, not a limits problem. Cheaper video. And more of the Atlas: the constellations are the part people come back for, and they are currently the youngest thing in the app.
Built With
- capacitor
- capgo
- cloudflare-r2
- codemagic
- fal.ai
- grok-imagine
- ios
- kling
- nestjs
- nginx
- openai
- pg-boss
- pinia
- pm2
- postgresql
- posthog
- revenuecat
- sentry
- server-sent-events
- soniox
- tailwind
- typeorm
- typescript
- vite
- vue


Log in or sign up for Devpost to join the conversation.