The instinct with complex features is "let me just try something." But architectural decisions hide in the 3rd model, the 5th edge case. Plan mode's real value is moving the architecture-level conversation to text before code. Real case: Pickful's community jury system — 3,032 lines, 119 specs green, one commit, zero architectural rework after.
The instinct with complex features is "let me just try something."
The problem: complex systems have architectural decisions hidden in the 3rd model, the 5th edge case, the 8th points rule. Diving into code and hitting those decisions later — changing them then costs 10× what it would have cost to talk them out in text first.
I used Claude Code's plan mode to build TopicReview, the community jury system in Pickful, and got to see the extreme version of this inequality: one commit, 3,032 lines, 119 specs green, shipped in a single go. None of the dozen-plus commits that followed was an architectural redo — they're all parameter tuning, UI polish, edge-case patching. The system has been running steadily ever since and is now central to how the community self-moderates.
This post is about why complex features need plan mode first, what the plan phase is actually doing, and when the conversation is done enough to start writing code.
TopicReview is a community vote to decide whether a low-quality post gets removed. One sentence to state — but the spec unfolds in layers:
The complexity is not in any single rule — it's in how the rules interact. Every rule you add can trigger a rollback in another.
When you write straight through, the hardest problems aren't the visible facts — they're the questions you never thought to ask. Read TopicReview's code backward, and at least four walls would inevitably hit you if you skipped the plan:
Juror eligibility rules. Looks like just User.jurors_and_judges.sample(12). But the actual rules are: exclude the post author, exclude anyone who reported the post, exclude anyone who already voted in the initial round (so they can't also vote on appeal). Three layered exclusions. Writing the model all in one go, you'll miss one or two.
Deferred points payout. A remove verdict would normally trigger juror payouts. But an appeal can overturn a remove — and when it does, the jurors who voted with the old verdict are now on the wrong side, so payouts need to be recomputed against the new verdict. Therefore the remove verdict must wait for the 24h appeal window to close before paying jurors. Skip writing that rule down, pay first, and you end up trying to claw back points — about 10× messier than paying late.
Scheduled-job interface design. CloseTopicReviewJob looks like "finish a review." In practice it handles three situations:
# Initial voting window expiration
CloseTopicReviewJob.set(wait_until: voting_ends_at).perform_later(review.id)
# Deferred juror payout after a remove verdict
CloseTopicReviewJob.set(wait: 24.hours).perform_later(review.id, award_juror_points: true)
# Appeal window expiration
CloseTopicReviewJob.set(wait: 24.hours).perform_later(review.id, appeal_id: appeal.id)
No upfront planning and you'll write the first signature, then realize at case 2 that the interface has to change.
The most expensive wall: can you appeal during provisional removal? Once remove votes cross the threshold, the post is hidden (provisional), but the whole review is still in the voting state — not decided. Can the user appeal yet?
decided and appealeddecided transition before appeal opensThat single decision reaches back and changes the parameter checks inside open_appeal! and the state-machine logic. Deciding halfway through = rewriting half the system.
Claude Code's plan mode is a mode where writing code is disallowed — Claude can read the repo, think through approaches, discuss with you, but every file modification is hard-blocked until you approve a plan.
That mechanical constraint is the point: it forces the architecture-level conversation to happen in text.
A few things the plan phase is doing in practice:
1. Drawing the state machine and the roles. 5 states, 4 roles (juror / judge / post author / reporter), what each role can and can't do at each state — all in a few lines of markdown. A few lines vs. tens of files. The cost of change is two orders of magnitude apart.
2. Walking through each role's flow:
Wherever the walk stalls, a hidden question pops out: "can jurors see each other's votes?" "what can the post author do during provisional removal?"
3. Interrogating edge cases. The plan phase is not designing the "normal path" — it's deliberately asking the questions that don't usually surface:
finalize at the same time — does the race double-pay? (Add a juror_points_awarded field for idempotency.)95% of these questions never surface naturally while writing code. The plan phase forces you to answer them one by one.
4. The points ledger. The points system is complex enough that talking isn't enough — you have to actually draw the table: each point flow with its trigger, amount, and rollback path. Once the ledger balances, every edge case (provisional refund, appeal refund, bonuses) reconciles.
A hard test:
closed (keep / remove / warn × appealed / not × provisional / not) — you can talk through it end to endOnce that bar is met, writing code becomes translating the plan into Ruby.
The first ship of this system:
d162f1e Add community moderation system with jury/judge review and appeals
63 files changed, 3032 insertions(+)
119 specs, 0 failures
63 files, 3,032 lines, 119 specs green. Shipped in one go.
The dozen-plus commits after prove the architecture held:
Apply legacy penalty (1pt) for posts created before 2026-03-26
Fix topic review appeal bugs: window mismatch and verdict not updated
Add topic_removed status for posts removed by community review
Default to keep verdict when review expires with zero votes
Reduce provisional removal penalty to 1pt during trial period
Allow topic creator to withdraw active reviews
Add handled tab to jury dashboard, fix tabs styling
Every one is tune / fix / polish / a small feature. Not one is "go back and redesign the state machine" or "the points model needs to change." The skeleton held.
This is the real payoff of plan: execution stays uninterrupted. No "wait, how does this path work" — the plan already covered it. No "I didn't think of this edge case" — the plan phase asked. No "this interface has to be redesigned" — the plan already settled it.
Hours of writing code, doing only one thing: translating a clear design onto the keyboard.
Not every task is worth planning:
Plan mode's payoff comes from lowering rewrite cost. If there's no rewrite risk in the task, plan is just overhead.
"Think before you act" sounds like a personality tip. Plan mode isn't that.
Plan mode is about making the architecture-level conversation happen where 10 words can change it, not where 30 files have to change.
Under complexity, that old industry saw "words are cheap, code is expensive" needs to be flipped — it doesn't mean "just write code first and see" (which only holds when the cost gap is small). It means using the cost gap: put the expensive conversation up front, in the cheap medium.
3,000 lines in one commit feels great. Not because I wrote fast — because the plan turned "writing" into a purely mechanical act.