Last Friday, the app I have spent most of this year on did the one job it exists for.
It is called Rueda de Actos, a Windows desktop app for the Filà Ligeros, one of the groups that take part in the Moros y Cristianos festival in Alcoy, Spain. Once a year it hands out participation in five festival events, eleven spots each, following a rotating turn that has to stay fair from one year to the next. It does it in a single session, in a room, projected on a wall, in front of the people whose names are in the table.
Version 1 had that session last year. It took about three hours, and I came home with a notebook full of things to fix. In September I wrote about what that notebook turned into, about designing for a projected screen and about testing for one shot a year. This is the other half: what happened when version 2 met the same room.
The short version: it worked from start to finish, the session took under an hour, and the notebook has two lines in it.
Same chair, same rule
I did exactly what I did last year. I sat in the room, I kept my hands off the keyboard, and I wrote down every hesitation: every pause before a click, every time the person driving went back on something they had just done, every question from the room that the screen could not answer.
I don't ask for feedback afterwards. Asking gets you opinions about the app in the abstract. Watching gets you the gap between what someone expected to happen and what happened, and that gap is the only thing worth writing down.
Last year the notebook ended up with fifteen items. Most of them became version 2.
Under an hour
Last year's session took about three hours. This one took under an hour.
I am not going to claim all of that for the software. Part of it is a room that had already done this once with an app on the wall, and knew what was coming. But part of it is visible in what actually got used, and the most interesting thing about that list is where it came from.
What got used
Verification at eleven. Every time an event reached eleven people, the app stopped and opened the verification window: the eleven names, a checkbox for squad leaders, and a way to take someone out before closing. That window is the moment the room checks the app, not the other way around.
The search box. Last year, the single most repeated moment of the evening was somebody scrolling a table of nearly a hundred rows looking for a name while everyone waited. This year, when someone needed a name, it got typed instead of hunted.
An event that didn't get its eleven. Not every event fills. In version 1, an event that closed short looked exactly like a full one: five identical green cards. This year the card went amber and said what had happened, and that is what next year's turn will read.
A verified event, corrected. At some point, an event that had already been verified needed different people in it. Last year, the only tool for that would have erased every event played afterwards, so the correction went onto paper, and from that moment the paper was the record, not the app. This year the event was reopened, corrected and verified again, and the app stayed the record of what happened.
Three of those four (the search box, the amber card and the reopen) exist because of last year's notebook. None of them was in the rules I was given when I built version 1. That is still the strongest argument I know for shipping something incomplete and then watching it being used.
What nobody noticed
Most of what I wrote about in September never came up.
Cells blocked by unpaid dues or penalties don't say why, so the room sees that a rule was applied without learning anything private about anyone. Dialogs can be dragged out of the way. Notices that need no answer slide into a corner and leave on their own, and only the ones that change the table under everyone's eyes take the whole screen.
There is not a single line in the notebook about any of it. For that kind of design, that is the result you want. Privacy that works is invisible. A notice that doesn't interrupt is one nobody remembers. If any of those decisions had been wrong, they would have been wrong in public, in front of exactly the people they affect, and they would have been the first thing I wrote down.
The two lines
This is the whole notebook for this year:
- Moving between cells, and marking or unmarking them, from the keyboard instead of double-clicking.
- A scroll that adjusts itself.
Compare that with last year. Version 1's notes were about things the app could not do: correct a mistake without losing work, tell a full event from an incomplete one, get the group's data in without me. Those were missing capabilities. This year's two lines are about something else entirely: the hands of the person driving.
That is what a second round looks like, I think. You only notice how many times someone reaches for the mouse once nobody is waiting for you to fix something bigger. Double-clicking works. It is also the slowest way to walk down a list, aim at a cell on a projected table and do it again, with a room watching each move.
What goes into the next version
Both lines are already built, and they go out with the next version.
The table is now a single Tab stop. Inside it, the arrow keys move between rows, Home and End jump to the first and the last, and the space bar marks or unmarks the person in the event in progress. The part I cared about most is that the keyboard doesn't get its own logic:
// The row the keyboard is on. It goes by position, like the rows' `key`: when an
// event closes and the rota moves people to the back of the queue, focus stays
// where it was on the table instead of chasing the person to the bottom.
const filaActiva = ref(0)
function onTeclaFila(e: KeyboardEvent, idx: number) {
if (e.altKey || e.ctrlKey || e.metaKey || hayModalAbierto()) return
if (TECLAS_NAVEGACION.includes(e.key)) { // arrows, Home, End
e.preventDefault()
enfocarFila(destinoDe(e.key, idx))
return
}
// Space on the row's edit button belongs to the button.
if (e.key !== ' ' || e.target !== e.currentTarget) return
e.preventDefault()
// Holding it down can't keep marking and unmarking the same person.
if (e.repeat || !columnaActual.value) return
// Exactly what a double-click on the current event's cell does, blocks included.
onCeldaDblClick(idx, columnaActual.value)
}
The space bar calls the same function as the double-click. Every rule that blocks a cell (dues, penalties, the gender of the squad, not repeating an event) applies to both, because there is only one path. A second path would have been a second place for those rules to drift apart, and on this app a rule that drifts doesn't throw an error. It produces a plausible, unfair result.
Focus going by position is the other detail that only makes sense in this room. Closing an event rotates the queue: everyone who took part moves to the back. If focus followed the person, it would jump to the bottom of the table every time an event closed, and the operator would lose their place in front of everyone.
The scroll is a single line once the row has focus:
fila.focus({ preventScroll: true })
// Centred in the visible area: once the row passes the middle of the screen, the
// table scrolls to keep it there. At the very top and bottom there's nothing to
// centre against, and the scroll stops at the edge, which is what you want.
fila.scrollIntoView({ block: 'center' })
And one guard that only makes sense in front of a room:
// With a dialog open, the table ignores the keyboard. The dialog doesn't trap
// focus, so Tab can end up on a row behind it, and the space bar would change
// the selection without anyone seeing it.
function hayModalAbierto(): boolean {
return document.body.classList.contains('modal-open')
}
A change nobody can see is the worst kind of change in front of a room. The table simply stops listening while something is covering it.
What stays out
Nothing in the notebook argues for the things I said no to last month. Not a second person editing at the same time, not cloud sync, not a configurable number of events and spots, not a general undo. One person drove, in one room, and the outcome counted because everyone watched it happen. None of those features would have made that hour better, and most of them would have made it different.
The temptation after a good night is to read it as permission to add things. I am reading it the other way: the notebook is the spec, and the spec got shorter.
The only metric
Software that runs once a year has no analytics worth the name. No retention curve, no error tracking, no release every week. The night is the whole feedback loop, and whatever you learn in that room is all the feedback you get until the next one.
So the only measure I really trust is the length of the notebook. Last year: fifteen items, pages of them. This year: two lines. Next year I will sit in the same chair, with the same notebook, and keep my hands off the same keyboard. A blank page is the goal. I don't expect to get it.
The full case study, with screenshots made from test data, is on my site: https://ancer.es/en/project/rueda-ligeros.
Top comments (0)