Before wiring a live terminal, prove the whole interactive flow with a scripted session - a list of keys driven through the finder to a final choice. Today you run that end to end, and confirm the non-interactive filter shares the same pipeline.
Drive a Finder through a scripted key sequence to an accepted result, and confirm filter mode produces the matching highlighted output.
Before touching a real terminal - raw mode, escape sequences, the parts that are fiddly and platform-specific - you can prove the entire interactive flow works by scripting it. A session is just a list of keys pushed through HandleKey: type app, watch the results narrow to the one matching candidate, press Enter, and read back the accepted value. Because every step is deterministic, the whole session is a plain, exact test - no timing, no I/O.
This is also the moment the two faces of the tool line up. The scripted interactive session and the non-interactive filter mode run the very same pipeline - parse, rank, highlight - so app accepts src/app.go interactively and prints src/[a][p][p].go from -f. That shared core is why the finder stayed testable the whole way through. All that is left is the capstone: the same machinery, driven over a realistic corpus, standing in for the live tool.
// Feed a slice of keys through HandleKey; the last Enter returns the// accepted value. This is a full interactive session, deterministic// and testable, with no terminal involved.keys := []Key{ch('a'), ch('p'), ch('p'), enter()}var accepted stringfor _, k := range keys {if s, done := f.HandleKey(k); done { accepted = s }}// accepted == "src/app.go"