/**
* init-form.tsx — Ink confirmation screen for `woco init`.
*
* Replaces the 20+ prompt interactive wizard with a minimal Ink form.
* Shows auto-detected defaults in a confirmation screen with 3 editable
* fields (baseBranch, buildCommand, installCommand).
*
* Navigation:
* - Tab / Up / Down to move between fields
* - Type to edit a field
* - Enter to confirm and write files
* - Escape to cancel
*
* The form uses the existing TextInput component from ink-input-system.
*/
import React, { useState, useCallback } from "react";
import { Box, Text, useInput } from "ink";
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface InitFormDefaults {
baseBranch: string;
buildCommand: string;
installCommand: string;
}
export interface InitFormProps {
/** Project name (auto-detected from folder). */
projectName: string;
/** Auto-detected default values. */
defaults: InitFormDefaults;
/** Called when the user confirms. Receives the final values. */
onConfirm: (values: InitFormDefaults) => void;
/** Called when the user cancels (Escape). */
onCancel: () => void;
}
// ---------------------------------------------------------------------------
// Field definitions
// ---------------------------------------------------------------------------
export interface FieldDef {
key: keyof InitFormDefaults;
label: string;
}
export const FIELDS: FieldDef[] = [
{ key: "baseBranch", label: "Base Branch" },
{ key: "buildCommand", label: "Build Command" },
{ key: "installCommand", label: "Install Command" },
];
// ---------------------------------------------------------------------------
// EditableField — a single form row
// ---------------------------------------------------------------------------
interface EditableFieldProps {
label: string;
value: string;
focused: boolean;
}
function EditableField({ label, value, focused }: EditableFieldProps): React.ReactElement {
// When focused, we render a simple inline text input
// For renderToString compatibility, we render the value directly
return (
{focused ? "▸ " : " "}
{label}:
{focused ? (
{value || " "}
) : (
{value}
)}
);
}
// ---------------------------------------------------------------------------
// InitForm — the confirmation screen component
// ---------------------------------------------------------------------------
/**
* InitForm — minimal Ink form for project initialization.
*
* Shows auto-detected project settings with 3 editable fields.
* Uses Tab/arrows to navigate, Enter to confirm, Escape to cancel.
*/
export function InitForm({
projectName,
defaults,
onConfirm,
onCancel,
}: InitFormProps): React.ReactElement {
const [values, setValues] = useState({ ...defaults });
const [focusedField, setFocusedField] = useState(0);
const [editing, setEditing] = useState(false);
const [editBuffer, setEditBuffer] = useState("");
const handleConfirm = useCallback(() => {
// If editing, merge the edit buffer into values before confirming.
// We compute finalValues inline because setValues is async and the
// state update won't have applied when onConfirm runs.
let finalValues = values;
if (editing) {
const field = FIELDS[focusedField];
finalValues = { ...values, [field.key]: editBuffer };
setValues(finalValues);
setEditing(false);
}
onConfirm(finalValues);
}, [values, onConfirm, editing, editBuffer, focusedField]);
const handleCancel = useCallback(() => {
onCancel();
}, [onCancel]);
useInput((input, key) => {
if (editing) {
// In edit mode
if (key.escape) {
// Cancel edit, restore original value
setEditing(false);
return;
}
if (key.return) {
// Apply edit
const field = FIELDS[focusedField];
const newValues = { ...values, [field.key]: editBuffer };
setValues(newValues);
setEditing(false);
return;
}
// Ctrl+S — confirm directly from edit mode
if (key.ctrl && input === "s") {
handleConfirm();
return;
}
if (key.backspace || key.delete) {
setEditBuffer((prev) => prev.slice(0, -1));
return;
}
if (!key.ctrl && !key.meta && input && input.length > 0) {
setEditBuffer((prev) => prev + input);
return;
}
return;
}
// Normal navigation mode
if (key.escape) {
handleCancel();
return;
}
if (key.return) {
// Start editing the focused field
const field = FIELDS[focusedField];
setEditBuffer(values[field.key]);
setEditing(true);
return;
}
// Tab or Down — move to next field
if (key.tab || key.downArrow) {
setFocusedField((prev) => Math.min(prev + 1, FIELDS.length - 1));
return;
}
// Shift+Tab or Up — move to previous field
if (key.upArrow) {
setFocusedField((prev) => Math.max(prev - 1, 0));
return;
}
// Ctrl+S or Enter while at the end — confirm
if (key.ctrl && input === "s") {
handleConfirm();
return;
}
});
return (
{/* Header */}
wombo-combo
— Project Setup
{/* Project info (auto-detected, read-only) */}
Project:
{projectName}
(auto-detected)
{/* Editable fields */}
─── Settings ───────────────────────────────────
{FIELDS.map((field, idx) => (
))}
{/* Key hints */}
{editing
? "Enter to apply • Esc to cancel edit"
: "Enter to edit • ↑↓ to navigate • Ctrl+S to confirm • Esc to cancel"}
);
}