Skip to content

Instantly share code, notes, and snippets.

@SamBoyd
Created February 24, 2026 13:43
Show Gist options
  • Select an option

  • Save SamBoyd/5192b78fec7faa475cdbf24a55aa213e to your computer and use it in GitHub Desktop.

Select an option

Save SamBoyd/5192b78fec7faa475cdbf24a55aa213e to your computer and use it in GitHub Desktop.
Claude Code plan using EARS

Refactor: Ink New Wizard App.tsx

Context

The bep new wizard was recently converted from @clack/prompts to Ink. The resulting App.tsx (~405 lines) is a monolith handling prompt definition, state management, keyboard input, navigation, and rendering. Two provider step components (ManualProviderStep, MixpanelProviderStep) are byte-identical. This refactor decomposes into focused units following container/presentation and single-responsibility principles.

Requirements

  • R1: The InkNewWizard shall expose the same public API: a single InkNewWizardApp component accepting onComplete: (result: NewWizardResult) => void.
  • R2: The InkNewWizard shall preserve all existing keyboard behavior (arrow keys, vim j/k, Enter, Ctrl+C, Backspace, printable text input, Back option navigation) except that Escape shall navigate back (instead of cancelling).
  • R3: The InkNewWizard shall separate prompt configuration (titles, validation rules, options) into a pure module with no React dependency.
  • R4: The InkNewWizard shall separate wizard state management (draft, step index, UI state, navigation actions) into a dedicated React hook.
  • R5: The InkNewWizard shall separate keyboard input routing into a dedicated React hook.
  • R6: The InkNewWizard shall use a single two-branch dispatch (select vs text) for step rendering, eliminating duplicate provider wrapper components.
  • R7: The App container component shall contain only hook composition and presentation wiring — no business logic, no inline state management, no input handling.

Implementation

Chunk 1 — Extract prompt factory

  • S1: Create src/ui/ink/newWizard/promptFactory.ts containing buildPrompt(), buildUiState(), BACK_OPTION, MIXPANEL_URL_HINT, isProviderType(), and the WizardPrompt/WizardUiState type exports. All pure functions, no React. (R3)
  • S2: Update App.tsx to import from promptFactory.ts and remove the extracted code. (R3)
  • S3: npm run build to confirm no compile errors. (R1)

Chunk 2 — Delete identical provider step wrappers

  • S4: Delete components/ManualProviderStep.tsx and components/MixpanelProviderStep.tsx. Delete the isProviderStep() helper from App.tsx. Replace the four-branch body rendering with a two-branch select-or-text dispatch. (R6)
  • S5: npm run build. (R1)

Chunk 3 — Extract useWizardState hook

  • S6: Create src/ui/ink/newWizard/useWizardState.ts containing all useState/useMemo/useEffect calls plus goBack, submitSelect, submitText, completeIfDone, and a cancel wrapper. Hook signature: useWizardState(onComplete) => { prompt, uiState, actions } where actions = { goBack, submitSelect, submitText, cancel, setUiState }. (R4)
  • S7: Update App.tsx to use the hook, removing all inline state logic. (R4, R7)
  • S8: npm run build. (R1)

Chunk 4 — Extract useWizardInput hook, finalize container

  • S9: Create src/ui/ink/newWizard/useWizardInput.ts containing the useInput callback that routes keys to actions.* methods. Signature: useWizardInput(prompt, uiState, actions) => void. (R5)
  • S10: Reduce App.tsx to ~25 lines: hook composition + WizardFrame with SelectStep/TextStep. (R7)
  • S11: USER checkpoint: review final structure before committing.

Verification

  • V1 (R1, R2, R3, R4, R5, R6, R7): npm run build after each chunk.
  • V2 (R1, R2): npm test — existing newWizardFlow.test.ts passes.
  • V3 (R2): Manual trace of keyboard handling paths through useWizardInput to confirm equivalence with original useInput callback.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment