When the Description Is Blank, AI Writes It for You
Generating the description body from a task's title, intent, and expected outcome. Short for casual titles, detailed for serious work — and the result appears one line at a time.
When the Description Is Blank, AI Writes It for You
The field people leave empty most often when creating a task is the description. They jot down a title and put off the body. Even when they sit down to fill it in, it’s hard to know where to start.
So when the description field is empty, we added a button that lets AI fill it in for you. One dana per call.
How It’s Built
The input is what the task already knows about itself: the title, plus — if you wrote them — the current situation (target), the expected outcome (expectation), and the obstacles. The AI weaves these four into a body about how to proceed.
At first the prompt was simple. “A concrete, actionable plan in 3–5 sections, organized by approach, focus area, risks, and checkpoints.” The output looked reasonable enough.
Then I tried it with the title “What a wonderful morning!”. This is what came back:
Approach — Begin with a gentle sequence that shifts attention from sleep to action…
Immediate actions to start the day — Hydrate and open a window or bring in natural light…
Sustaining momentum and focus — Work in a single prioritized direction…
Risks, obstacles, and checkpoints — Grogginess: Rely on movement and hydration…
A self-help chapter in response to a single exclamation. Actions that weren’t in the input — “drink water”, “open a window” — got invented. The “Risks, obstacles, checkpoints” section was the exact structure the prompt had forced on it.
Rewriting the Prompt
Two problems.
First, forced structure. The “approach/risks/checkpoints” four-act format got stamped on every task, so even a short emotional note ended up in a heavy container.
Second, padding empty input. When the input is thin, the model fills the container with generalities. That’s where “Hydrate”, “open a window” came from — generic self-help with no real connection to the task.
I rewrote the prompt so it branches on the character of the title.
- Actionable task (“Write report”) → concrete plan, 3–5 sections.
- Learning/research (“Study Rust”) → what to look into, 2–4 sections.
- Feeling/state (“What a wonderful morning!”) → short notes, observations, or encouragement, 1–2 sections.
And I spelled it out: “Don’t invent action items that aren’t in the input — no generic self-help padding like ‘drink water’ or ‘open a window’ that isn’t grounded in the task.”
Same title again, “What a wonderful morning!”:
Morning mood — Savor the gentle light and the calm of this hour…
One section, one short paragraph. Light tone. Not bigger than the input deserves.
The Result Appears One Line at a Time
Having the body show up all at once is awkward. The user stares at a blank field for 3–5 seconds and then suddenly there’s text. So we piped the OpenAI streaming response through SSE all the way to the client.
The server publishes the accumulated HTML on every token. The client swaps the editor’s content with the latest accumulated value on each chunk. To the user, the body grows line by line.
Compared to the subtask streaming post, where we tracked brace depth in a JSON stream to pull out completed objects, description is much easier — it’s just HTML. You can send the accumulated text as-is, and the editor renders the partial state fine, even with unclosed tags mid-stream.
A Race That Hid in Plain Sight
It seemed done — until mobile started showing one weird symptom. If you press the AI button while there’s already content in the description, the screen doesn’t update. The chunks arrive, React state updates, but the native editor doesn’t redraw. Leave the page and come back and the new content is there.
The culprit was a quiet UIKit behavior on iOS. When the RN Modal (the dana confirmation sheet) closes, iOS helpfully restores the previous first responder — which is the description editor. Our code calls blur() and sets editable=false the moment onConfirm fires, but that happens during the modal’s close animation, so the first-responder restoration overwrites it. The editor is back to focused state when the SSE chunks arrive, and the native side’s “ignore prop updates while editing” guard drops them.
Luckily our BottomSheetModal already had an onClosed callback. The comment on it read “the moment when iOS first responder is cleanly released.” Moving the generation kickoff to fire after the modal fully unmounts made the race disappear.
Same Experience on Desktop
It’d be strange if mobile had progressive output and desktop just spun until the call finished. We wired the same SSE handler on desktop and moved the AI button into the editor’s toolbar — over on the right end, next to bold and italic — so the spinner spins in the same spot the icon used to live. While generating, the other format buttons go gray too. The editor is non-editable anyway, but visually marking the toolbar as inactive matches that.
Looking Back
The most interesting part wasn’t the feature itself — it was the prompt. “Stronger instructions for better output” doesn’t always work. “3–5 sections organized by X·Y·Z” helps for writing a report but was violent against “What a wonderful morning!”. Once we let the model categorize the title and match the tone, it scaled itself down.
The impulse to pad with generalities also had to be blocked explicitly. The model can’t stand a blank space. It took saying “don’t invent anything that isn’t in the input” two or three times before it would actually keep things short.
What started as a simple “let AI fill in the empty description” turned out to be mostly an exercise in what AI should refuse to fill in. Knowing what not to write is the harder skill, apparently.