DEV Community

Cover image for One prompt, six boxes, 25/25: building an OTP screen in 30 minutes
James Ngandu
James Ngandu

Posted on

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. 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 screen

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 digit boxes had three states:

  • filled: dark green circle, white digit
  • focused: white circle, dark outline
  • empty: light grey circle, dash placeholder

The interactions were where the real work was:

  • the numeric keyboard opens automatically
  • typing a digit advances the focus
  • backspace on an empty box clears the previous digit
  • pasting Your code is 482913 fills 482913
  • Submit stays disabled until all six digits are present
  • 123456 shows You're in.
  • any other code shows the error, clears the boxes, and focuses the first box
  • Resend is disabled for a 30-second countdown, then tappable, and tapping it restarts the countdown
  • each box is announced as "Digit N of 6"
  • the screen supports dark mode

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.

Six boxes, one text field

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.

The visible state is derived:

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 pipeline

The submission format is strict. The PR can only touch submissions/<team-name>/. It needs:

  • PROMPT.md
  • TEAM.md
  • screenshot.png
  • demo.gif or demo.mp4
  • app/

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 first score was 23/25:

  • acceptance criteria: 10/10
  • visual match: 6/6
  • code quality: 3/4
  • build: 2/2
  • tests: 2/2
  • prompt length: 0/1

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.

The second pass

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:

  • the copy and six 48dp boxes
  • numeric keyboard autofocus and digit rejection
  • auto-advance and backspace
  • SMS paste
  • Submit gating
  • the exact success and error messages
  • the 30-second resend countdown and restart
  • the Digit N of 6 semantics
  • dark mode

They are not decoration. The test point is real, and the error path is the easiest place to lose it.

The screenshot and demo

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 result

The final submission scored 25/25 with 8/8 checklist items. The PR is here:

https://github.com/buildthamani/hakka26/pull/24

The repo is here:

https://github.com/buildthamani/hakka26

The leaderboard is here:

https://buildthamani.github.io/hakka26/

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.

What I learned

  1. One prompt can get you a working screen. The last points come from structure, tests, and following the format.
  2. The prompt length is a scoring constraint. A detailed prompt can be too long for the point.
  3. The screenshot and demo are part of the submission, not an afterthought.
  4. A fast CI loop is worth more than a local setup you cannot run.
  5. Read the rubric before the first push. The grader tells you exactly what it wants.

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.

Top comments (0)