유대선
프로젝트로
·사용성 회고·4

Subtracting your way to a 'real app': Mimi's 6→4 tab restructure

A code-grounded audit said Mimi read like a feature toolkit, not a daily app. The fix was mostly deletion — 6 tabs to 4, a feature-grid home to one action, a mixing-desk video screen to a script — plus finally giving the output drill real AI feedback.

The complaint that started it

The app worked. You could import a YouTube video, clip a line, shadow it, drill it, and review it on a spaced-repetition schedule. The engine was real. But it didn't feel like a real app — it felt like a box of tools. Six bottom tabs. A home screen that was a grid of feature cards. A video screen with a row of speed buttons, an A‑B looper, an auto-advance toggle, a reps counter, and a "Clip this line" button stamped onto every single transcript line. The question "what do I do right now?" had no answer in under a second.

Audit with code, not vibes

Before cutting anything I ran a read-only sweep of mobile/src with three agents in parallel — one inventoried every screen and its loading/empty/error states, one traced the core learning loop end to end, one checked the table-stakes polish layer. The point was to make the cut list evidence-based instead of "this feels cluttered."

What it surfaced, with file-level receipts:

The fix was mostly subtraction

The commit (02c427f) touched 24 files and was a net deletion+566 / −1112. The headline changes were things removed:

A deliberate restraint: the core shadowing actions — replay, sentence-level looping, record — were not demoted to "advanced." Looping a line is the product; burying it would have been deleting the wrong thing.

Finally, the output drill got a coach

The one place I added code was the Produce drill. A new lenient grader — POST /api/practice/scenario/checkCompositionService.scenarioCheck with a ScenarioPrompt tuned for "does this English answer work in the situation" (a different-but-valid answer passes; it does not demand you copy the sample). It reuses the existing analysis AI provider, so it's independent of the OpenAI speech-to-text key. The frontend calls it and, if grading is ever unavailable, degrades to simply revealing the sample — the drill is never a dead end.

What's verified vs not

Verified this turn: tsc --noEmit clean after each step; backend gradlew compileJava BUILD SUCCESSFUL; the linux/amd64 backend image built clean for the NCP box. Not verified yet: the scenario endpoint end-to-end (the backend image is built but not yet deployed), and Android — zero Android builds so far, and the keyboard-driven video-collapse uses an iOS-only event, so that one behaviour won't fire there.

The lesson

On an app that does too much, the biggest lever on perceived quality is subtraction plus one obvious next action — not another screen. The diff that made Mimi feel more like a real app was, on net, 546 lines lighter. The only way to cut confidently was to let agents read the actual code first and tell me what was load-bearing and what was noise.