Case study, Roam, Side Project, 2026
Roam: Trip planning by AI, grounded in reality
Cost per plan
Time to a saved plan
Users who finished
Commits since launch
Cost per plan measured from the server log of a real 3-day trip, Google APIs at list price. Testing: unmoderated remote study, 6 participants, directional.
My role
Solo designer and builder
Timeline
48-hour build, then 8 weeks of refining
Scope
Research to deployed product
Status
The product
One designer. No engineers. Shipped.
Live at roam.akberahmed.com
Enter a destination, set your dates, travellers and budget, and Roam generates a real day by day itinerary with live places, real travel times between stops, and an estimated nightly price range for where you stay. Everything, from the first wireframe to the deployed app, was built by me. Roam is mobile-first by design, built for the phone people actually plan trips on.
This is not a prototype made to look real. It is real, and you can use it right here. →
A 0-to-1 AI trip planner I took from problem to production on my own, with no engineers. Claude read the live Figma spec through MCP and built the React front end from it; the tools, the phases and the cost model are all below. Six users then tested it unaided, and every one of them reached a saved plan.
Discovery
Planning a trip is scattered by default
Before designing anything, I planned the Albania trip and paid attention to where it fell apart.
This was not a two-week research programme. It was a real trip planned end to end, two national surveys I checked my own experience against, and a read through what independent testing had found about AI itineraries in 2026. Small and scrappy, but enough to turn a hunch into problems worth building against.
The same pattern in two markets
Before I trusted my own frustration, I checked it against national surveys in the US and Germany. People named more than one source, so the columns add up past 100, and the two studies differ in method and year, so read the order rather than the gap.
| Where US travellers get ideas | % of travellers |
|---|---|
| Social media | 34% |
| Family and friends | 33% |
| Online travel agencies | 25% |
| Social ads | 24% |
| Influencers | 21% |
| Television | 21% |
| AI tools | 17% |
| Travel agency, in person | 16% |
| Celebrities | 13% |
| Newspapers | 12% |
US, 2025: AI sits near the bottom but grew fastest, up 30% in a year. Amadeus, 2,000 travellers.
| Where German travellers get ideas | % of travellers |
|---|---|
| Online travel portals | 64% |
| Provider websites directly | 47% |
| Friends and acquaintances | 40% |
| Travel agency, in person | 34% |
| Television | 22% |
| Travel catalogues | 17% |
| Phoning the provider | 17% |
| Travel blogs | 16% |
| Social media | 13% |
| AI tools | 3% |
Germany, 2024: portals lead, a third still visit a travel agency, AI is at 3%. Bitkom Research, 954 holidaymakers.
These surveys only measure where a trip starts, though. What happens next, when a scattered set of ideas has to become a day you can actually walk, is where my own planning came apart.
AI is on both lists, and it does not survive a real day
In the same Amadeus survey, 27% said AI had returned inaccurate information, and Amadeus's own President of Hospitality said gen AI tools are not yet ready for trip planning prime time. When InsureMyTrip tested ChatGPT, Gemini and Google's AI on a real week-long trip, the plans recommended a restaurant that does not exist and laid out days with travel times nobody could actually make.
Independent testing
"The itineraries often looked polished and logical on the surface... closer inspection revealed that both big and small details were often inaccurate."
Sara Boisvert, InsureMyTrip, reported in Forbes, 2026
That became the thesis for the whole product. Anything can generate a plan. The design problem is guaranteeing the plan is real: places that exist, prices that are close, and a day whose stops you can actually get between before dinner. Trust, not text, is the job. Four failures showed up again and again, and each one became something Roam had to answer.
Problem
The four failures Roam had to answer
All four came out of planning my own trips, worst first. Each one drove a decision in the build.
01
Trip planning has no single home
A dozen tabs: a group chat, a doc nobody updates, blogs, a budget sheet. Roam turns one short brief into a full day by day plan with places, costs and a map.
02
AI bolted onto the wrong moment
Booking tools assume you already know where you are going, then bolt AI on after. Roam is AI-first from the first screen, built for "help us decide where to go."
03
Plans that look right but fall apart
Ask any chatbot and it reads well, then breaks: lunch across town, stops with no gap between them. Roam checks each plan against real travel and fixed meal times.
04
Change one stop, the plan breaks
Swap a museum for a market in most tools and the timings fall out of sync. In Roam, change any stop and the schedule recomputes itself around it automatically.
Process
48 hours, phase by phase
Seven phases, each leaning on a different tool. When a phase ran long, scope was cut, not quality. The phase that mattered most was the sixth, where Claude read the live Figma spec through MCP and built each React component from it, so spacing came from the component and colour came from the tokens, not from eyeballing a screenshot.
| Hours | Tool | Phase | What happened |
|---|---|---|---|
| 0–2 | Claude | Ideation | Stress-tested the concept in a Claude session. The output was a set of decisions: three surfaces, two integrations, one hard time limit. |
| 2–4 | Figma Make | Wireframes | Nine screens from a plain-language brief. Several survived almost unchanged, which moved the real work to flow rather than layout. |
| 4–20 | Figma Agent | Hi-fi design | Agent populated the frames with real content, so the design went from placeholder boxes to something that felt like a product. |
| 20–24 | Token Studio | Design tokens to CSS | The whole visual system exported as CSS custom properties. Change a colour in Figma, it changes in the app. |
| 24–28 | Anthropic API Google Places |
Planning logic and prompt | Wired up the planning layer and wrote the system prompt, where the two-phase "ask, then generate" model was defined. Google Places sat behind it so every suggestion resolved to a real location. |
| 28–40 | Figma MCP | Design to code | Components built in Cursor straight from the live spec. This is the phase that made handoff feel solved rather than tedious. |
| 40–48 | Vercel | Build and deploy | Tight iteration on edge cases and loading states, then a production deploy in under two minutes. |
Wireframes
The structure, decided in wireframes
Hours two to four went into wireframes, not visuals. Figma Make turned a plain-language brief, a travel app with a trip form, an AI planning flow and an itinerary view, into nine rough screens. Grey boxes, real structure, enough to react to.
The speed was the point. With working screens in hours rather than days, the real decisions moved to flow and interaction instead of layout, and several of these frames carried through, almost unchanged, into the shipped app. The originals are below, still clickable.
Click through the wireframes Figma Make generated from a plain-language brief. Several survived, almost untouched, into the final build.
Design foundation
A system before the screens
Before any screen went to high fidelity, I built the foundation everything else would inherit from: a set of tokens and a component library. Working solo under a 48-hour clock, a single source of truth was the only way to stay consistent, and AI let me build that foundation far faster than I could alone.
Design Tokens
Colour primitives were aliased to semantic tokens, each graded for AA or AAA contrast, so no component ever touched a raw hex. I worked that layer out with Claude, pressure-testing the naming and contrast before I committed it. Plugged into Token Studio in Figma, the tokens export to CSS custom properties, so one colour change flows through the whole file and into the app.
Design System
On top of the tokens sat the component library: buttons, tags, cards, inputs, confidence badges and meal pills, each designed once and reused everywhere. Figma Agent generated the variant matrix, so I could spend my time on the decisions that needed judgement. Every screen drew from that same set, so nothing drifted and a fix in one place appeared everywhere it was used.
Tokens and components, straight to code
The same pipeline, traced through two components. The meal tag follows a colour, the trending card follows shape and elevation. Each time, the value set in the design system is the exact token the shipped CSS renders from.
The Dinner meal tag, selected. Its colours live in the design system, not on the component.
Each colour is one named token, the only place the raw hex is written.
The component asks for the token by name. It never touches a raw colour.
Fill, border and text each come from a named token, one of dozens wired this way.
The trending card, selected. Its shape, surface and shadow all come from the system, not hard-coded values.
Shape and elevation are tokens too. The shadow is one compound token carrying a whole two-layer elevation.
The card sets no pixels of its own. It reads its radius, surface and shadow from named tokens.
Radius, surface and a two-layer shadow each come from a named token, so the system carries shape and depth into code, not just colour.
Design
Three acts, and the prompt behind them
Roam's interface follows a three-act structure: tell it about your trip, let it plan, then explore and refine. The brief opens with a structured form rather than a blank chat box, because most people do not want to type an essay to start a trip, and the form doubles as the model's context, so it can generate straight away. The interest chips are generated for the destination you type, so Marrakech offers souks and Bergen offers fjords, and the first set a place gets is the set everyone sees from then on. The accommodation step offers three options filtered by budget, so the choice stays small enough to make quickly.
The shipped app, from a short brief to a plan you can save. Two pacing plans to compare, real places with real drive times, a live map and one-tap swaps, each screen answering one of the four problems rather than just looking the part.
The end-to-end flow. A short brief becomes a plan, then you compare the two pacing options, open the map, or peel off to swap a stop, and any swap recomputes the itinerary around it.
Designing the prompt as much as the interface
One of the most important design artefacts was the system prompt, not a Figma frame. Getting itineraries that felt specific and appropriately scoped took as much iteration as any screen. Four decisions changed the output most.
01
Structured output
The model returns a consistent shape: time, place, duration, reasoning note. The UI renders it predictably regardless of destination.
02
Brief as context
Every field the user fills becomes a constraint the model reasons within, rather than a question it has to ask.
03
Budget as a constraint
Economy, Standard and Luxury are injected as constraints, calibrating stays, dining and activities to the same tier.
04
Reasoning first
A one-line rationale for every stop, so the output feels advisory, not generated. It matched the core insight from my Muse project: unexplained recommendations do not build trust.
The Figma bridge
The usual handoff, static design files passed to a developer to implement by hand, was replaced by a live connection to the Figma file itself.
After the sprint
What only real use reveals
Roam wasn't built in a day. Two days got a real, working product live; the eight weeks after are where it got good, and they are the part I am proudest of. Real use showed me the calls I had got wrong, and fixing them is work the sprint could never have produced.
Browse the 250+ commits on GitHub
Cutting the cloud bill
About a week in, Google Cloud credits were draining far faster than the traffic justified: €134 of €263 in seven days. Almost all of it was the place search, where every lookup asked for Enterprise-tier fields. So I cut them. Then I put two back.
Review count is the only thing separating a famous restaurant from a chain branch, and opening hours the only way to know a place is open when you arrive. Without them, dinner came from the same yakiniku chain twice and the app scheduled a government building at 22:15. Right on the numbers, wrong on the product. The saving that survived is caching, so a place is never paid for twice, and static photos, so the app only costs money when someone plans a trip.
The biggest leak after that was the repair passes. Every attempt to swap a stop or fill a thin day was a fresh search, and a search that found nothing was tried again the next round; my first count of the calls never included them. Those searches are now cached for 30 days, a failed repair is not retried, and each plan gets a budget of 60 searches, after which it ships as it is.
Verifying every stop is what costs money, and both pacing plans are verified, not just the one you pick.
A place already looked up costs nothing. Different dates or interests still turn up new ones.
One full 3-day generation at Google's per-call prices above the free tier. Same tier on both sides; the saving is a shared cache. Both figures are read from the server log of a real Valencia trip.
A subtler leak only showed once the app was public: the home screen was fetching a live Google photo for every trending destination on every visit, so bots and idle browser tabs were quietly billing me for people who never planned a thing. Moving those curated photos to static files meant the app only costs money when someone actually generates a trip, not when someone merely looks at it.
When it runs out, and when it breaks
Cutting the per-trip cost did not cap the total. Cheap multiplied by unlimited is still unlimited, and a link in a public post is how you find that out. Roam now plans a set number of trips a day and then stops. Hitting that ceiling is not an error state: the app says it is at today's limit and offers a complete example trip, because a red failure screen would tell a user the product is broken when it is only busy.
Knowing when it genuinely is broken is a separate problem. One command checks the live endpoints after every deploy, so a dead key or a bad build reaches me rather than whoever clicks the link next.
The itinerary rulebook
The biggest body of work was a set of rules the model has to obey before a plan reaches the screen, enforced in the prompt and again on the server.
| Rule | Why it exists |
|---|---|
| No idle gaps | The only space between two stops is the travel between them, checked to the minute by a script that replays a real saved plan. |
| Fixed meal times | Breakfast at 09:00, lunch at 13:30, dinner at 20:00, the same on every day. The stops flex around them; the meals do not. |
| One meal per slot | Exactly one breakfast, lunch and dinner a day. A second dinner gets the whole generation rejected and retried on the server. |
| Meal length follows pace | Slow plans give each meal a longer, unhurried sitting; Packed keeps them brief, so the pace is felt at the table too. |
| Day start and end | No stop starts before 09:00 and the last one ends by 22:30, which stretches to 02:00 when nightlife is one of the interests. |
| Opening hours | Every stop is checked against its real opening hours for the whole visit. A place that would be shut is replaced; one that would close early is shortened. |
| No place twice | Dedupe on the resolved place and on the brand, not the name. This caught a Lahore plan listing the fort and a palace inside it twice. |
| Distance-first routing | Keep every stop within 15 km of your stay and reorder any day that doubles back, with Slow carrying fewer stops than Packed. |
| Interest coverage | Every interest you pick has to appear somewhere in the trip, decided by Google's place types rather than by a place's name. |
| Balanced interests | One stop per interest a day, and a cap per trip that scales with its length, so a plan never has three casinos or two shrines. |
| Resolve or drop | A stop that fails verification is dropped rather than shown with a time and a travel leg, looking like one that checked out. |
| Labels from data | Category tags are built from Google's own place types, after the model returned a street number as a descriptor. |
| No numbers in prose | No travel times, distances or visit lengths in the text. Every number on a card is measured, so the prose cannot contradict it. |
When a correct rule still ships a wrong plan
A real place wearing another place's description. When a stop failed verification, the substitution replaced its name, address, photo, location, hours and category, and left the original description untouched. A members' club in Roppongi shipped described as a 24-hour ramen chain. Real place, real photo, fluent prose about a different business. Not hallucinated, mismatched, and it read perfectly.
A rule that could not fire. The check for stops scheduled at an hour they cannot keep was correct, and ran before two later passes that moved stops. Anything those passes touched shipped unexamined. A shrine went out at 21:10 and a design centre at 21:00, both hours after closing, under a rule written to prevent exactly that.
The same mistake, one level up. The pass that straightens a day that doubles back was correct, and ran before the loop that drops stops, fetches replacements and re-routes. So the day it inspected was never the day that shipped. The first time I found a rule running before the passes that invalidate it, I fixed that rule. The second time I understood it was a shape, not an incident.
Each of those cost ~€2.29 to reproduce, because finding out meant generating a real plan against real APIs. So I built an offline harness that replays the actual scheduling code against a saved generation and reports meal times, route doubling-back, opening-hours violations and continuity to the minute. A change that used to cost a euro and a coffee now costs a second.
Reworked and fixed
Four calls I got wrong, each one caught and fixed on my own rather than reported by a user. Unglamorous work, but it is the difference between a demo and something people can actually rely on.
| Area | What changed |
|---|---|
| Budget | Moved the budget selector to the start of the flow. Set late, it meant regenerating the whole plan; upfront, it becomes a constraint the model plans within from the first call. |
| The map | Shipped completely broken. Fixing it took a solo pass through Google Cloud: enabling Static Maps separately, attaching billing, and realigning a key that no longer matched the console. |
| Travel times | A mislocated stop produced a 15-hour drive that spilled into the next day. Every stop must now sit within 15 km of your accommodation, so a wrong-city place never reaches the plan. |
| Types | The itinerary now carries real types from the API to the screen, so a malformed plan fails at build time, not mid-trip. It has already refused a stop the model typed "nightlife". |
User testing
What six real travellers taught me
The sprint proved I could ship it. Testing was where I found out whether anyone would actually trust it. Six users planned the same Lisbon weekend on their own phones, unguided, while the screen recorded. Most of the plan held. The one place it broke turned out to be the most important thing I learned.
Method
Unmoderated remote usability test on real phones, via UXtweak with screen recording. Success and time on task judged from the recordings, not a completion flag.
Task
One end to end task: plan a two-night Lisbon trip, choose where to stay, reach a finished plan and save it. Questions before and after.
Participants
6 users who plan their own leisure trips, a convenience sample. Five iPhone, one Android; five in Berlin, one in the UK. Mixed on how much they trust AI.
Measures
Completion, Single Ease, a trust rating, UMUX-Lite, would-follow and would-use intent, plus open text coded into themes. Small n, read as directional.
Every one of the six planned a full trip on Roam and saved it, and not one of them needed help from me to get there.
reached a finished, saved plan unaided, recordings confirmed
mean ease of planning the trip (range 4–7)
mean trust that places are correctly located (range 4–7)
median time from a blank screen to a saved plan
Reading the numbers
A small convenience sample of six users who plan their own trips, one unmoderated remote task. Directional, not representative. The fixed Lisbon destination made "would you follow it" awkward for anyone not actually travelling, which is where both "no"s came from; next round I'd let people plan their own trip and add think-aloud.
01
The core promise landed, even for sceptics
Everyone reached a plan in minutes and called it easy. "It's convenient to see a skeleton of a trip in just a few questions." Even the participant who dislikes itinerary apps praised "the variety of locations and activities."
02
The two pacing plans were the favourite idea
Five of six noticed them, usefulness 6.6 out of 7, and one wanted a third. From the video recordings, only three actually toggled between them, so people valued the reassurance of a choice without exercising it.
03
People stopped to read the reasoning
On every recording, participants paused on the one-line rationale for each place. The reasoning-first decision earned its place: the "why this" note is read, not skipped.
04
Seeing the map made it credible
Asked why the plan felt right, one participant just said: "A map with the itinerary is shown." The map itself did the convincing, and that is the thread the next finding pulls on.
What held up
Those are the wins, and they held across all six sessions. The same sessions also gave me a ranked list of what to fix.
Findings at a glance
Rated on Nielsen's severity scale, a blend of how bad, how frequent and how persistent. Each finding maps to one change.
| What I found | Severity | The change it points to |
|---|---|---|
| Stops resolve to the wrong location. Two plans routed a day to another continent.2 of 6 plans · only 1 caught | Critical | Reject any stop outside the destination or more than 15 km from your accommodation, and surface the Google Places check so correctness is shown, not assumed. |
| People want a visible sense of control, not less automation.Raised by 3 of 6 | Major | Surface the levers Roam already has: budget, timing and choosing between options. |
| Accommodation cards select but do not open to detail.1 tried it, 3 asked for it | Minor | Make cards expand to a detail view with photos and a price range. |
| The two pacing plans are valued but under-used.3 of 6 never compared them | Minor | Make the Packed versus Slow difference visible at a glance so the choice gets used. |
| Swapping was not recognised as the way to change the plan.1 of 6 | Minor | Move the swap onto each stop so “change this” reads as an action. |
Reflection
What building and testing this taught me
Shipping is where the real design starts
The sprint produced something that looked finished. Real trips showed what wasn't: plans that read well but fell apart, and calls I had to reverse. The most senior work happened after launch, not before it.
Trust is a feature you have to show, not just build
Roam verifies every place, but users never see that, so they trust the tone instead, and one trusted a plan that would have crossed an ocean. The verification exists. The first piece of it is now on every card, the Google rating and review count the check produced. The rest of it is the next job.
The system prompt is a design artefact
The decisions that changed output quality most were about how the model was instructed to behave, not layout or colour. Writing the prompt with the rigour of UX copy mattered more than any single screen.
Automation and control are not opposites
Testing pushed me off my own assumption. People want the machine to do the work and to feel in charge of it. The interesting problem is agency inside automation, not one or the other.
One designer can own the whole product now
One person can own an entire product now. Research, the design system, the front-end, deployment, testing, the whole line sat with me, and the tooling made that fast rather than just possible. That shift is the real headline of this project.
Next steps
What comes next
Roam is not finished, and it was never meant to be. The path from here is a real product, not a tidier prototype: a proper backend and accounts so trips persist, real booking so a saved plan becomes a trip you can actually take, and deeper coordination for the group travelling together. The foundation is live and tested. What it needs next is depth.
Six of six reached a plan they could save, and most would follow it. The tools got a real product live in 48 hours. Knowing exactly how to make it better, from the first research note to the one bug a user caught, is what I walked away with.






































