Writing prose flows
A flow file is the kind of thing you’d Slack a teammate when explaining how to reproduce something. One paragraph, in plain English, describing what someone does in the app and what they expect to see. That’s the whole authoring surface for klera.
If you’ve ever pasted a reproduction into a Linear ticket — “sign in with the demo account, dismiss the welcome modal, tap Reorder, you’ll see the wrong total” — you have already written the body of a flow. The only difference is where the file lives and what its name ends in.
The visual above shows what happens after you save the prose: klera compiles your scenario into an internal step list (the bit that actually talks to the app), commits it next to your prose, and runs it. You read and review the prose. Engineers occasionally peek at the compiled steps. Nobody hand-edits the compiled file.
What a flow file looks like
A flow file is a Markdown file ending in .flow.md. It lives in a
flows/ folder at the root of the project, alongside the app code.
# Sign in and see today's notifications
Sign in with the seeded demo user, dismiss the onboarding modal if it
appears, and assert that the home screen shows today's notifications.
Take a visual snapshot called "home-after-login".Three things to notice:
- The first
# Headingis the scenario name. It shows up in the test report, in CI status checks, and in the file listing. Make it read like a one-line summary of what the scenario proves. - Everything after the heading is the scenario body. Plain prose, in any structure that reads naturally. One paragraph is fine. Two paragraphs are fine. A short numbered list is fine.
- That’s it. No special syntax to learn.
How to describe a scenario
Three habits make the difference between a flow that compiles cleanly and one that needs another pass.
1. Name the screens you expect
When you mention a screen, name it the way the design or the build calls it: “the home screen”, “the cart drawer”, “the address picker”. The compiler uses screen names as anchors when it walks the app’s component tree. “Make sure it goes back” leaves the compiler guessing; “assert the home screen is visible again” does not.
2. Be specific about what you tap
Tell the flow what the button literally says, or what it does. “Tap Sign in” is precise. “Tap the primary button” is fragile — there are three primary buttons in your app. If the button is icon-only, describe the icon (“tap the bell icon in the top right”) or the accessibility label engineering attached to it (“tap the notifications icon”).
3. Mention the data you’re using
If the scenario depends on the user being signed in, say so. If it depends on the cart having three items, say so. The compiler reads these cues to set up the right starting state.
# Reorder a previous purchase
Sign in with the demo account that has at least one past order, navigate
to the orders tab, tap the most recent order, then tap Reorder. Assert
that the cart drawer shows the same items as that order.You can pull data out into a fixtures block if you don’t want to inline it:
---
fixtures:
email: pm@example.com
password: hunter2
---
# Sign in with the demo account
Sign in with the email and password from fixtures, then assert the
home screen shows "Welcome back, PM".The fenced --- block at the top is optional and is the only place
where structured config lives. Anything inside it is data the prose
can reference.
Mistakes to avoid
“Make sure it works” is not a scenario. It tells the compiler nothing. Replace it with the specific check that proves it works.
A few patterns that look like flows but read like wishes:
- “Make sure the login flow doesn’t break.” → “Sign in with the demo account, then assert the home screen shows the user’s name.”
- “Test the checkout.” → “Add the first item to cart, tap Checkout, fill the address from fixtures, tap Pay, and assert the confirmation screen shows order number ‘TEST-1234’.”
- “Verify the empty state.” → “Sign in as a brand-new user with no history, navigate to Orders, and assert the screen shows ‘No orders yet’.”
A scenario that doesn’t say what success looks like cannot be a test. Always end with an assertion — a screen, a piece of text, a snapshot.
Worked examples
Below are five real ticket-style scenarios translated into .flow.md
files. Each one shows the original prose a PM might have pasted into a
ticket, then the fix that turns it into a flow.
Example 1: a sign-in regression
Original ticket: Login is broken on iOS. Demo account doesn’t get through.
# Demo account signs in successfully on iOS
Sign in with the demo account email "pm@example.com" and password
"hunter2". Wait for the welcome screen. Assert that the welcome screen
shows the text "Welcome, pm@example.com".Example 2: a notification badge
Original ticket: The unread badge isn’t clearing when you read a notification.
# Reading a notification clears its unread badge
Sign in with the demo account. Note that the bell icon shows a red
badge with "1" on it. Tap the bell icon, tap the first notification in
the list, then go back to the home screen. Assert that the bell icon no
longer shows a badge.Example 3: an onboarding modal that should only show once
Original ticket: The “What’s New” modal keeps coming back after I dismiss it.
# Whats-New modal stays dismissed across sessions
Sign in with a fresh demo account. The Whats-New modal should appear.
Dismiss it by tapping "Got it". Sign out, sign back in, and assert that
the Whats-New modal does not appear this time.Example 4: a cart with a fixture-backed item set
Original ticket: Cart total is wrong when you have multiples of the same item.
---
fixtures:
cart:
- { sku: "SHIRT-M", qty: 2, price: 25.00 }
- { sku: "SOCKS", qty: 3, price: 5.00 }
---
# Cart total reflects quantity correctly
Sign in. Seed the cart with the items from fixtures. Open the cart
drawer. Assert that the total reads "$65.00".Example 5: a visual regression on the welcome screen
Original ticket: Marketing wants pixel-stable hero on the welcome screen.
# Welcome screen renders pixel-stable
Wait for the welcome screen to settle. Take a visual snapshot called
"welcome-hero". On subsequent runs, fail if the hero image differs by
more than a few pixels from the baseline.The visual snapshot is captured the first time the flow runs and becomes the baseline; future runs diff against it. Engineers can dig into how that diff is computed in visual snapshots.
Where the compiled steps live
When you save the .flow.md, klera writes a sibling .flow.json next
to it. That file is the compiled step list. You don’t edit it; it gets
committed and reviewed alongside your prose so reviewers can see both
your intent change and the resulting behaviour change in one PR. The
mechanics live under reviewing flow changes
and the engineer-track prose flows page.
If a flow is too vague to compile, the compiler will tell you what it couldn’t figure out. The fix is almost always to name a screen, name a button, or add a final assertion.