The problemNo clear way to plan
The Toyota Stellenbosch Woordfees runs for nine days in October with hundreds of shows, talks, concerts and films across dozens of venues. It is wonderful, and it is hard to plan for. We kept hitting the same wall:
- The programme was a 261-page, 31 MB PDF, printed in June. Great to flick through, hopeless for a question like “what’s free tomorrow after 18:00 near the Neelsie?”.
- The website had no search that answered those questions, so you ended up opening show pages one by one.
- Tickets live on a separate platform. Whether a show was still bookable was a third place to look, and for reserved-seating shows it was often not obvious at all.
- Nothing checked a day for you. Two shows back to back at venues a kilometre apart looks fine on paper. It isn’t.
- Things change after printing. One show we checked had already moved venue between the PDF and the website.
Three tabs and a spreadsheet to plan a festival.
So we asked a simpler question: what would it take to just ask, in Afrikaans or English, “what’s on, what’s free, what’s still bookable, and can I get from A to B?”
What we builtAsk it, browse it, plan with it
One page, installable on a phone, in Afrikaans and English. You can chat with the guide, browse the programme by day with live ticket status, and build a plan that warns you about clashes, long walks and sold-out sessions.



Pulling the dataThree sources, none complete
The first version, built from the PDF alone, had 369 productions, 569 sessions and around 790 people. It was a good start and it went stale within days. The programme only became trustworthy once we treated the live sources as the truth and the PDF as history.
- The PDF fast, staleReading two-column pages with a script got us a working index in an afternoon. But it was printed in June, some entries were lost where text columns interleaved, and one section used a broken font encoding.
- The festival website the truthThe site’s public listing showed 472 entries, but the listing data carried only titles and descriptions; dates, venues and prices sat on each event page. We read every page once (about 5 minutes) and now only re-read entries whose last-edited stamp changes (about 20 seconds). That gave 457 productions with exact times, venues, prices, languages and age limits. The 15 entries with no session dates are left out.
- The ticket platform tickets and places366 events link straight to their ticket page. The platform’s official developer API gave us exact venue coordinates for 430 of the 457 entries, which later made real walking times possible.
Keeping it honest
Everything is read politely: one request at a time where the source throttles quick callers, only what is needed, and never for shows that have already happened. We link out to the festival site and ticket page for every event, because they remain the source of record.
A problem we didn’t expectThe category trap
Everything about the programme is organised by category: twelve of them, from Film Festival to Children’s Festival. It is how the PDF is laid out and how the website filters. But nobody plans a festival by category. People plan by place and time: “what’s on at the Drostdy tonight?”, “what can I do after the film at the Neelsie?”. And at this festival the two don’t line up.
How many different venues each category is spread across
- “Spier Theatre” reads like a venue, but it is a category. It holds 44 shows spread across 11 different venues. Visual Arts is 41 shows in 28 places, Lifestyle 57 in 23. Only Film Festival sits in one place: all 42 films are at the Road House Cinema at The Neelsie.
- It also runs the other way. The Drostdy-teater hosts shows from six different categories, and Standard Bank Endler from four. One evening at one venue is scattered across six chapters of the PDF.
- The label hides the travel. Two back-to-back “Spier Theatre” shows can be a kilometre apart, and nothing in a category list tells you so.
- Some shows belong to two categories. Ten are listed under two on the festival site, such as classical music for children. Our guide keeps one category per show and files these under the first, so they appear in one filter, not both. That is a known limitation; the fix is to let a show carry several tags.
What we did about it
We made time and place the structure and demoted category to a label. The programme opens on a day, then times; a venue filter sits beside the category filter; every venue has exact coordinates (430 of 457 entries), so the plan can warn “about 12 minutes on foot (1 km)” between two sessions. Category survives as a colour stripe and a filter, not as the way the page is built.
The hardest questionIs it still bookable?
This is where the data told a comfortable lie. The ticket data we could read said every reserved-seating show was fine. Then someone spotted that one show’s Saturday session was sold out on the ticket site. The ticket data simply had no sold-out signal for reserved seating.
The truth was in the seat map, the same thing a buyer sees: grey seats are taken, coloured seats are free. So we read it the way a visitor does and count the free seats. Across 277 sessions that found around 70 sold out that the ticket data had reported as fine. General-admission shows use the platform’s ticket counts, which are real remaining numbers.
Checking often, but not wastefully
- A staggered schedule. Sessions starting within six hours are re-checked every cycle; tomorrow’s about hourly; later ones every few hours; sold-out ones rarely, because they rarely reopen. A cycle needs about 41 seat maps instead of re-reading everything.
- Never past shows. Anything that has started is skipped everywhere.
- Say what we don’t know. Where we can’t see seats it says “reserved seating, check the seat map”; where we estimate it says “about 8 left”; and the details show when it was last checked.
Pulling the designMake it feel like the festival
We didn’t want a generic app. The 2026 poster, “It’s about time”, already had a strong look, so we started there and took its cues apart.
The palette, sampled from the poster
The paper-cut creatures
The little animal illustrations were lifted out of the programme PDF with their transparency intact, then used as category art and in empty states. The clock-face rings from the cover are rebuilt in plain CSS gradients, with no image at all.
Type and feel
Thick outlines and offset shadows keep it playful. On a phone the lists run edge to edge, touch targets are at least 40 px, and the input stays above the keyboard. Afrikaans is a tap away, and the headline becomes “Dis hoog tyd”, the festival’s own wording.
The AI guideTools, not search
The chat doesn’t “know” the festival. Each question makes it call tools over the structured programme: search by day, time, venue and price; list what’s sold out; look up a person or a show; and check a proposed day for clashes and walking time. If a tool doesn’t return it, the guide doesn’t say it.
We chose tool-calling over embeddings and retrieval on purpose. The whole programme is only about 400 KB, and people ask structured questions (this day, after this time, free, near here), which filters answer exactly and a similarity search does not.
Choosing the model by measuring it
We ran the same four real questions through four configurations and compared speed, cost and correctness.
| Setup | Avg. time | Avg. cost / answer | Verdict |
|---|---|---|---|
| Sonnet 5.5, default effort | 10.0 s | $0.029 | Correct, slowest |
| Sonnet 5.5, low effort | 7.8 s | $0.020 | Correct. Chosen. |
| Sonnet 5.5, between-tools thinking | 8.1 s | $0.023 | Fastest first word, failed to finish a plan |
| Haiku 4.5 (smallest) | 7.1 s | $0.013 | Cheapest, but said “seats available” on a reserved-seating show it could not see |
That last row decided it. The cheapest model confidently claimed the one thing the data couldn’t tell it. For a tool people use to decide where to spend an evening, we’ll pay a little more for one that says “I can’t see which seats are left”.
Guardrails, tested
- Stays on the festival; politely declines unrelated requests, role-play and attempts to extract its instructions.
- Treats a visitor’s claim (“it’s free”, “it’s cancelled”) as a question to check, not a fact to repeat.
- Earlier replies are signed by the server, so a forged “assistant said the festival is cancelled” message in the chat history is thrown away.
- Only a short list of trusted links can be clicked. Typed questions are stored with emails, phone numbers and ID-style numbers removed.
- We ran 15 attack prompts (injections, false premises, link injection, persona swaps). All 15 were handled correctly.
- The page says plainly: the AI and this tool can make mistakes. We hope it helps more than it hurts.
Shipping itFresh, fast and installable
- Hosting. A static front end with a few small server functions on Vercel, on its own address, with an installable offline mode so the programme works with patchy signal.
- Freshness. A refresh cycle every 30 minutes: programme changes, seat maps (staggered as above), a sold-out snapshot, then a redeploy only if something really changed.
- Learning from use. Anonymous analytics (no cookies, no accounts) show what people ask and which questions the guide can’t answer, so the next fixes come from real use.
- Cost control. Prompt caching and a low-effort setting keep an answer at around two US cents, and a rate limit caps abuse.
Why it matters beyond one appMaking events visible
A festival this size has a long tail, and a long tail is where discovery goes to die. Of the 457 entries, 91 have no ticket page at all (free talks, exhibitions, broadcasts, school programmes) and 124 are free. They never show up in a ticket search, and in a 261-page PDF a small free talk on page 180 is competing with the headline acts.
A guide that answers questions treats every event the same. Ask “something free for kids on Saturday” and a small morning show comes up next to the big concert, because the question, not the layout of a brochure, decides what is found. A few other ways this helps:
- Showing what’s open steers people to seats. With around 70 of the 277 reserved-seating sessions sold out at the time, “sold out Saturday, Sunday still open” moves someone to a seat instead of away from the show.
- Questions are a signal. Anonymous questions and which events get opened show what visitors actually look for, and which questions nobody can answer yet. A festival rarely sees that.
- Plans travel by word of mouth. A shared plan link lets a friend add the same sessions in one tap.
- Two languages, one place. Afrikaans and English side by side puts the programme in reach of more of the people who live in and visit the town.
What fought backThe honest list
- Data that looked right and wasn’t. The sold-out story above. It was a person using it, not our tests, who noticed.
- Two clocks. The festival site and the ticket platform did not agree on session times, so matching has to be careful and say “unknown” rather than guess.
- A ticket platform that throttles. Quick callers get rejected, so everything is one polite request at a time.
- Phone keyboards. Safari and Chrome on iPhone each draw their own bars above the keyboard and resize the page differently. Getting the chat input to sit where it should took many rounds with real-device screenshots, and a well-known chat app as the reference.
- Our own bugs. A page lock that hid the input, raw template text showing in buttons, a model that over-claimed. Each was caught by testing against real screens or real answers, then fixed.
What we’d ask of any festivalMake planning easy by default
- Publish the programme as an open feed (JSON and calendar) with stable IDs, instead of only a PDF and web pages.
- Expose session-level availability, including reserved seating, so “is it still bookable?” has an official answer.
- Include venue coordinates and a last-changed stamp on every entry.
- One canonical page per session that a guide can link straight to.
- Organise by place and time as well as category, and let a show carry several categories instead of one.
If the data were open, tools like this one wouldn’t need to be built from the outside. They would simply be built on top.




