Logic Prototype
Tiny interactive terminal app,让用户手 drive state model。当问题是 business logic、state transition 或 data shape——纸上 reasonable 但 push real case 才 feel wrong 时用。
何时是该形态
- "I'm not sure if this state machine handles the edge case where X then Y."
- "Does this data model actually let me represent the case where..."
- "I want to feel out what the API should look like before writing it."
- 任何用户想 按按钮看 state 变 的时候。
若问题是 "what should this look like"——wrong branch。用 UI.md。
流程
1. State the question
写代码前,写下 prototype 什么 state model、回答什么问题。一段,在 prototype README 或 file 顶 comment。答错问题的 logic prototype 纯 waste——explicit question 以便 later check,无论用户 now 看还是 AFK 回来。
2. Pick the language
用 host project 用的。若无 obvious runtime(如 docs repo),问。
Match 项目现有 tooling convention——不要为 prototype 加新 package manager 或 runtime。
3. Isolate the logic in a portable module
将 actual logic——回答问题的部分——放在 small pure interface 后,日后可 lift 进 real codebase。周围 TUI 可丢弃;logic module 不应。
Right shape 取决于问题:
- A pure reducer —
(state, action) => state。Good when actions are discrete events and state is a single value. - A state machine — explicit states and transitions. Good when "which actions are even legal right now" is part of the question.
- A small set of pure functions over a plain data type. Good when there's no implicit current state — just transformations.
- A class or module with a clear method surface when the logic genuinely owns ongoing internal state.
Pick whichever shape best fits the question being asked, not whichever is easiest to wire to a TUI. Keep it pure: no I/O, no terminal code, no console.log for control flow. The TUI imports it and calls into it; nothing flows the other direction.
这使 prototype 超越自身 lifetime useful。问题 answered 后,validated reducer / machine / function set 可 lift 进 real module——TUI shell delete。
4. Build the smallest TUI that exposes the state
Build lightweight TUI——每 tick clear screen(console.clear() / print("\033[2J\033[H") / equivalent)re-render 整 frame。用户应始终见 one stable view,非 ever-growing scrollback。
每 frame 两部分,此顺序:
- Current state,pretty-printed、diff-friendly(一行一 field,或 formatted JSON)。field name 或 section header 用 bold,less important context(timestamp、ID、derived value)用 dim。Native ANSI fine——
\x1b[1mbold、\x1b[2mdim、\x1b[0mreset。除非项目已有 styling library,否则不必 pull in。 - Keyboard shortcuts,列在 bottom:
[a] add user [d] delete user [t] tick clock [q] quit。Key bold、description dim,或 vice-versa——whatever reads cleanly。
Behaviour:
- Initialise state — single in-memory object/struct。start 时 render 第一 frame。
- Read one keystroke (or one line) at a time,dispatch handler mutate state。
- Re-render full frame after every action — don't append, replace.
- Loop until quit.
Whole frame 应 fit one screen。
5. Make it runnable in one command
Add script 到项目现有 task runner(package.json scripts、Makefile、justfile、pyproject.toml)。用户应 run pnpm run <prototype-name> 或 equivalent——never remember path。
Host project 无 task runner 时,command 放 prototype README 顶。
6. Hand it over
给 run command。用户 self drive;有趣时刻是 "wait, that shouldn't be possible" 或 "huh, I assumed X would be different"——那是 idea 的 bug,whole point。若要新 action,加。Prototype evolve。
7. Capture the answer
Prototype done 后,唯一 worth keeping 是问题的 answer。若用户在,问教会了什么。若不在,留 NOTES.md 旁以便填 answer(或你若 watched session 则你填)再 delete prototype。
Anti-patterns
- Don't add tests. 需要 test 的不再是 prototype。
- Don't wire it to the real database. 用 in-memory store,除非问题 specifically 关于 persistence。
- Don't generalise. 无 "what if we wanted to support X later." Prototype 答一个问题。
- Don't blur the logic and the TUI together. 若 reducer / state machine 引用
console.log、prompts 或 terminal escape codes,不再 portable。TUI 作 thin shell over pure module。 - Don't ship the TUI shell into production. Shell 优化为 terminal hand drive。背后 logic module 才是 worth keeping 的 bit。