One prompt, six boxes, 25/25: building an OTP screen in 30 minutes
The challenge was simple to state and hard to finish: build an OTP verification screen with one AI prompt, in 30 minutes, and let a CI pipeline and an AI grader decide how good it is. This was hakkahakka26 at droidcon Kenya 2026. The room had 20 teams, a 30-minute clock, and one prompt per team. Te

The challenge was simple to state and hard to finish: build an OTP verification screen with one AI prompt, in 30 minutes, and let a CI pipeline and an AI grader decide how good it is.
This was hakkahakka26 at droidcon Kenya 2026. The room had 20 teams, a 30-minute clock, and one prompt per team. Teams picked a mobile stack, copied a template, committed the prompt they used as PROMPT.md, added a screenshot and a demo, and opened a pull request. The repo's Build workflow compiled the app and ran the tests. Then Claude Sonnet graded the submission out of 25 and posted the score on the PR.
I built the Flutter version. It scored 23/25 on the first pass, then 25/25 after a focused refactor. This is what happened, what the grader cared about, and what I would do differently next time.
The brief asked for a bottom sheet over a dimmed, blurred background. The sheet needed a logo, a title, a subtitle, six 48dp digit boxes, a resend line, and a full-width rounded Submit button.
The interactions were where the real work was:
The copy had to match the challenge list exactly, even where the reference image differed. That detail matters: the reference said "Enter Your code" and "your cell phone", but the copy list said "Enter your code" and "your phone". The list wins.
The cleanest decision was to use one invisible TextField as the source of truth and render six boxes from its value. That makes paste and backspace behave like a normal text field, while the UI still looks like six separate inputs.
final filled = index < digits. length; final focused = index == digits. length; Enter fullscreen mode Exit fullscreen mode The hidden field keeps the input honest:
inputFormatters: [ FilteringTextInputFormatter. digitsOnly, LengthLimitingTextInputFormatter ( 6 ), ], Enter fullscreen mode Exit fullscreen mode The paste case then works without special clipboard code. Your code is 482913 becomes 482913, and the boxes update from the same string. Backspace removes the last digit, which is the same as clearing the previous box and moving the focus back.
The sheet itself is split into small pieces: OtpScreen owns state, OtpSheet owns the layout, OtpBackdrop owns the dimmed brand background, and OtpInput owns the six circles. Colours come from assets/colours.json, so light and dark mode share one source of truth.
The submission format is strict. The PR can only touch submissions/<team-name>/. It needs:
The Build workflow detects the stack from pubspec.yaml, runs flutter build apk --debug, and runs flutter test. A separate Grade workflow runs from main, checks out no PR code, and sends the diff, prompt, screenshot, and build result to Claude Sonnet with a fixed rubric.
The feedback was specific. The screen file was doing too much, the fallback palette duplicated the JSON, and the prompt was 862 characters, over the 600-character limit for the point.
I split the screen into OtpSheet and OtpBackdrop, kept the state in _OtpScreenState, and removed the duplicated fallback palette. The app now loads assets/colours.json once at startup, and the tests load the same asset. I also tightened the submitted prompt to 600 characters.
Then I pushed again. The Build workflow stayed green, all 12 tests passed, and the next grade was 25/25.
The tests cover the parts that are easy to fake:
They are not decoration. The test point is real, and the error path is the easiest place to lose it.
I did not have a local Flutter SDK on the machine, so I used GitHub Actions for the whole loop. The repo's Build workflow compiled the APK and ran the tests. A throwaway capture workflow ran a golden test against the widget tree, rendered the empty, partial, filled, success, error, resend, and dark frames, and assembled them into a GIF with ImageMagick.
That workflow was temporary. The final PR only contains submissions/malhare/. The screenshot shows the partially filled state with 2 1 5, matching the reference layout. The GIF shows the full interaction.
The final submission scored 25/25 with 8/8 checklist items. The PR is here:
https://github.com/buildthamani/hakka26/pull/24
The humbling part: 25/25 is the ceiling, and several teams reached it before me. The tie-break is the earliest graded run, so the top-three demo places were already taken by the time my second pass landed. The score is maxed, but the clock is part of the competition.
The code and the PR are public. If you are building an OTP screen, the six-boxes-one-field pattern is a good place to start, and the tests are a good checklist for the edge cases.