procedure-cli 0.1.13 → 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -13
- package/dist/app.js +27 -15
- package/dist/app.js.map +1 -1
- package/dist/lib/template.js +1 -1
- package/dist/lib/template.js.map +1 -1
- package/dist/lib/types.d.ts +1 -1
- package/dist/steps/build-test.js +1 -1
- package/dist/steps/build-test.js.map +1 -1
- package/dist/steps/product-context.js +1 -1
- package/dist/steps/product-context.js.map +1 -1
- package/dist/steps/project-info.js +1 -1
- package/dist/steps/project-info.js.map +1 -1
- package/dist/steps/stack-style.js +44 -2
- package/dist/steps/stack-style.js.map +1 -1
- package/package.json +5 -1
- package/templates/gitignore +4 -0
- package/.claude/settings.local.json +0 -27
- package/.env.example +0 -2
- package/AGENTS.md +0 -134
- package/CLAUDE.md +0 -138
- package/CODE-FIXED.md +0 -252
- package/CODE-REVIEW.md +0 -558
- package/config/defaults.json +0 -8
- package/config/powerline-config.json +0 -52
- package/config/stacks/typescript-node.json +0 -15
- package/docs/GIAI-THICH-CLAUDE-MD.md +0 -206
- package/docs/PRD.md +0 -141
- package/docs/USER-STORIES.md +0 -324
- package/src/app.tsx +0 -213
- package/src/cli.tsx +0 -19
- package/src/components/banner.tsx +0 -23
- package/src/components/gutter-line.tsx +0 -16
- package/src/components/guttered-select.tsx +0 -231
- package/src/components/step-indicator.tsx +0 -32
- package/src/components/timeline.tsx +0 -57
- package/src/lib/fs.ts +0 -23
- package/src/lib/git.ts +0 -41
- package/src/lib/powerline.ts +0 -48
- package/src/lib/template.ts +0 -161
- package/src/lib/types.ts +0 -70
- package/src/providers/openai.ts +0 -5
- package/src/providers/zai.ts +0 -7
- package/src/steps/architecture.tsx +0 -72
- package/src/steps/build-test.tsx +0 -114
- package/src/steps/generation.tsx +0 -176
- package/src/steps/powerline.tsx +0 -254
- package/src/steps/product-context.tsx +0 -269
- package/src/steps/project-info.tsx +0 -183
- package/src/steps/stack-style.tsx +0 -304
- package/src/theme.ts +0 -15
- package/tsconfig.json +0 -17
package/src/steps/powerline.tsx
DELETED
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
import React, { useState } from "react";
|
|
2
|
-
import { Text, useInput } from "ink";
|
|
3
|
-
import { ConfirmInput, Spinner } from "@inkjs/ui";
|
|
4
|
-
import { GutterLine } from "../components/gutter-line.js";
|
|
5
|
-
import { setupPowerline } from "../lib/powerline.js";
|
|
6
|
-
import { initGit } from "../lib/git.js";
|
|
7
|
-
import { scaffoldRelease, ensurePackageJsonReleaseScripts } from "../lib/template.js";
|
|
8
|
-
import type { WizardAnswers } from "../lib/types.js";
|
|
9
|
-
import { C } from "../theme.js";
|
|
10
|
-
|
|
11
|
-
interface Props {
|
|
12
|
-
answers: WizardAnswers;
|
|
13
|
-
onComplete: (answers: Partial<WizardAnswers>) => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
type Phase =
|
|
17
|
-
| "ask-powerline"
|
|
18
|
-
| "ask-git"
|
|
19
|
-
| "ask-release"
|
|
20
|
-
| "running"
|
|
21
|
-
| "done"
|
|
22
|
-
| "error";
|
|
23
|
-
|
|
24
|
-
interface SetupResult {
|
|
25
|
-
gitCommitted?: boolean;
|
|
26
|
-
gitWarning?: string;
|
|
27
|
-
releaseCreated?: boolean;
|
|
28
|
-
releaseSkipped?: boolean;
|
|
29
|
-
pkgModified?: boolean;
|
|
30
|
-
pkgCreated?: boolean;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export default function Powerline({ answers, onComplete }: Props) {
|
|
34
|
-
const [phase, setPhase] = useState<Phase>("ask-powerline");
|
|
35
|
-
const [wantPowerline, setWantPowerline] = useState(false);
|
|
36
|
-
const [wantGit, setWantGit] = useState(false);
|
|
37
|
-
const [wantRelease, setWantRelease] = useState(false);
|
|
38
|
-
const [errorMsg, setErrorMsg] = useState("");
|
|
39
|
-
const [setupResult, setSetupResult] = useState<SetupResult>({});
|
|
40
|
-
|
|
41
|
-
useInput(
|
|
42
|
-
(input, key) => {
|
|
43
|
-
if (key.shift && key.tab) {
|
|
44
|
-
if (phase === "ask-git") setPhase("ask-powerline");
|
|
45
|
-
else if (phase === "ask-release") setPhase("ask-git");
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
{ isActive: phase === "ask-git" || phase === "ask-release" }
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
function runSetup(powerline: boolean, git: boolean, release: boolean) {
|
|
52
|
-
setPhase("running");
|
|
53
|
-
try {
|
|
54
|
-
const targetDir = process.cwd();
|
|
55
|
-
const projectName = answers.projectName || "untitled";
|
|
56
|
-
const result: SetupResult = {};
|
|
57
|
-
|
|
58
|
-
if (powerline) setupPowerline(targetDir);
|
|
59
|
-
|
|
60
|
-
if (git) {
|
|
61
|
-
const gitResult = initGit(targetDir);
|
|
62
|
-
result.gitCommitted = gitResult.committed;
|
|
63
|
-
result.gitWarning = gitResult.committed ? undefined : gitResult.reason;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (release) {
|
|
67
|
-
const releaseRes = scaffoldRelease(projectName, answers);
|
|
68
|
-
const pkgRes = ensurePackageJsonReleaseScripts(targetDir, projectName);
|
|
69
|
-
result.releaseCreated = !releaseRes.skipped;
|
|
70
|
-
result.releaseSkipped = releaseRes.skipped;
|
|
71
|
-
result.pkgModified = pkgRes.modified;
|
|
72
|
-
result.pkgCreated = pkgRes.created;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
setSetupResult(result);
|
|
76
|
-
setPhase("done");
|
|
77
|
-
setTimeout(() => onComplete({ setupPowerline: powerline, setupGit: git, setupRelease: release }), 1000);
|
|
78
|
-
} catch (err) {
|
|
79
|
-
setPhase("error");
|
|
80
|
-
setErrorMsg(err instanceof Error ? err.message : String(err));
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ── Ask: Powerline ──────────────────────────────────────────────────────────
|
|
85
|
-
if (phase === "ask-powerline") {
|
|
86
|
-
return (
|
|
87
|
-
<>
|
|
88
|
-
<GutterLine>
|
|
89
|
-
<Text bold>Claude Powerline</Text>
|
|
90
|
-
</GutterLine>
|
|
91
|
-
<GutterLine>
|
|
92
|
-
<Text color={C.overlay1}>Adds a live status bar to Claude Code showing: git branch,</Text>
|
|
93
|
-
</GutterLine>
|
|
94
|
-
<GutterLine>
|
|
95
|
-
<Text color={C.overlay1}>context usage %, session cost, and active tools.</Text>
|
|
96
|
-
</GutterLine>
|
|
97
|
-
<GutterLine>
|
|
98
|
-
<Text color={C.overlay1}>Runs via npx on each session — no global install needed.</Text>
|
|
99
|
-
</GutterLine>
|
|
100
|
-
<GutterLine>
|
|
101
|
-
<Text> </Text>
|
|
102
|
-
</GutterLine>
|
|
103
|
-
<GutterLine>
|
|
104
|
-
<Text bold>Set up Powerline? </Text>
|
|
105
|
-
<ConfirmInput
|
|
106
|
-
onConfirm={() => { setWantPowerline(true); setPhase("ask-git"); }}
|
|
107
|
-
onCancel={() => { setWantPowerline(false); setPhase("ask-git"); }}
|
|
108
|
-
/>
|
|
109
|
-
</GutterLine>
|
|
110
|
-
</>
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// ── Ask: Git ────────────────────────────────────────────────────────────────
|
|
115
|
-
if (phase === "ask-git") {
|
|
116
|
-
return (
|
|
117
|
-
<>
|
|
118
|
-
{wantPowerline && (
|
|
119
|
-
<GutterLine>
|
|
120
|
-
<Text color={C.overlay1}>Powerline: yes</Text>
|
|
121
|
-
</GutterLine>
|
|
122
|
-
)}
|
|
123
|
-
<GutterLine>
|
|
124
|
-
<Text bold>Git repository</Text>
|
|
125
|
-
</GutterLine>
|
|
126
|
-
<GutterLine>
|
|
127
|
-
<Text color={C.overlay1}>Runs git init and makes an initial commit with the generated</Text>
|
|
128
|
-
</GutterLine>
|
|
129
|
-
<GutterLine>
|
|
130
|
-
<Text color={C.overlay1}>files — clean starting point for your project history.</Text>
|
|
131
|
-
</GutterLine>
|
|
132
|
-
<GutterLine>
|
|
133
|
-
<Text> </Text>
|
|
134
|
-
</GutterLine>
|
|
135
|
-
<GutterLine>
|
|
136
|
-
<Text bold>Initialize git repo? </Text>
|
|
137
|
-
<ConfirmInput
|
|
138
|
-
onConfirm={() => { setWantGit(true); setPhase("ask-release"); }}
|
|
139
|
-
onCancel={() => { setWantGit(false); setPhase("ask-release"); }}
|
|
140
|
-
/>
|
|
141
|
-
</GutterLine>
|
|
142
|
-
<GutterLine>
|
|
143
|
-
<Text color={C.overlay1}>{"Shift+Tab prev y/n answer"}</Text>
|
|
144
|
-
</GutterLine>
|
|
145
|
-
</>
|
|
146
|
-
);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// ── Ask: Release scripts ────────────────────────────────────────────────────
|
|
150
|
-
if (phase === "ask-release") {
|
|
151
|
-
const projectName = answers.projectName || "untitled";
|
|
152
|
-
return (
|
|
153
|
-
<>
|
|
154
|
-
{wantPowerline && (
|
|
155
|
-
<GutterLine>
|
|
156
|
-
<Text color={C.overlay1}>Powerline: yes</Text>
|
|
157
|
-
</GutterLine>
|
|
158
|
-
)}
|
|
159
|
-
{wantGit && (
|
|
160
|
-
<GutterLine>
|
|
161
|
-
<Text color={C.overlay1}>Git: yes</Text>
|
|
162
|
-
</GutterLine>
|
|
163
|
-
)}
|
|
164
|
-
<GutterLine>
|
|
165
|
-
<Text bold>npm release scripts</Text>
|
|
166
|
-
</GutterLine>
|
|
167
|
-
<GutterLine>
|
|
168
|
-
<Text color={C.overlay1}>Creates ~/bin/{projectName}-release and adds release:patch /</Text>
|
|
169
|
-
</GutterLine>
|
|
170
|
-
<GutterLine>
|
|
171
|
-
<Text color={C.overlay1}>minor / major scripts to package.json. One command to build,</Text>
|
|
172
|
-
</GutterLine>
|
|
173
|
-
<GutterLine>
|
|
174
|
-
<Text color={C.overlay1}>version-bump, push tags, and publish to npm.</Text>
|
|
175
|
-
</GutterLine>
|
|
176
|
-
<GutterLine>
|
|
177
|
-
<Text color={C.overlay1}>Requires: npm login. Best for packages published to npm.</Text>
|
|
178
|
-
</GutterLine>
|
|
179
|
-
<GutterLine>
|
|
180
|
-
<Text> </Text>
|
|
181
|
-
</GutterLine>
|
|
182
|
-
<GutterLine>
|
|
183
|
-
<Text bold>Set up release scripts? </Text>
|
|
184
|
-
<ConfirmInput
|
|
185
|
-
onConfirm={() => { setWantRelease(true); runSetup(wantPowerline, wantGit, true); }}
|
|
186
|
-
onCancel={() => { setWantRelease(false); runSetup(wantPowerline, wantGit, false); }}
|
|
187
|
-
/>
|
|
188
|
-
</GutterLine>
|
|
189
|
-
<GutterLine>
|
|
190
|
-
<Text color={C.overlay1}>{"Shift+Tab prev y/n answer"}</Text>
|
|
191
|
-
</GutterLine>
|
|
192
|
-
</>
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// ── Running ─────────────────────────────────────────────────────────────────
|
|
197
|
-
if (phase === "running") {
|
|
198
|
-
return (
|
|
199
|
-
<GutterLine>
|
|
200
|
-
<Spinner label="Running setup tasks..." />
|
|
201
|
-
</GutterLine>
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// ── Error ───────────────────────────────────────────────────────────────────
|
|
206
|
-
if (phase === "error") {
|
|
207
|
-
return (
|
|
208
|
-
<GutterLine>
|
|
209
|
-
<Text color={C.red}>Setup error: {errorMsg}</Text>
|
|
210
|
-
</GutterLine>
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// ── Done ────────────────────────────────────────────────────────────────────
|
|
215
|
-
return (
|
|
216
|
-
<>
|
|
217
|
-
{wantPowerline && (
|
|
218
|
-
<GutterLine>
|
|
219
|
-
<Text color={C.green}>Powerline configured.</Text>
|
|
220
|
-
</GutterLine>
|
|
221
|
-
)}
|
|
222
|
-
{wantGit && setupResult.gitCommitted && (
|
|
223
|
-
<GutterLine>
|
|
224
|
-
<Text color={C.green}>Git repository initialized.</Text>
|
|
225
|
-
</GutterLine>
|
|
226
|
-
)}
|
|
227
|
-
{wantGit && !setupResult.gitCommitted && setupResult.gitWarning && (
|
|
228
|
-
<GutterLine>
|
|
229
|
-
<Text color={C.peach}>{"⚠ Git: "}{setupResult.gitWarning}</Text>
|
|
230
|
-
</GutterLine>
|
|
231
|
-
)}
|
|
232
|
-
{wantRelease && setupResult.releaseCreated && (
|
|
233
|
-
<GutterLine>
|
|
234
|
-
<Text color={C.green}>
|
|
235
|
-
Release script created.{setupResult.pkgModified ? " package.json updated." : setupResult.pkgCreated ? " package.json created." : ""}
|
|
236
|
-
</Text>
|
|
237
|
-
</GutterLine>
|
|
238
|
-
)}
|
|
239
|
-
{wantRelease && setupResult.releaseSkipped && (
|
|
240
|
-
<GutterLine>
|
|
241
|
-
<Text color={C.overlay1}>Release script already exists — skipped.</Text>
|
|
242
|
-
</GutterLine>
|
|
243
|
-
)}
|
|
244
|
-
{!wantPowerline && !wantGit && !wantRelease && (
|
|
245
|
-
<GutterLine>
|
|
246
|
-
<Text color={C.overlay1}>No extras configured.</Text>
|
|
247
|
-
</GutterLine>
|
|
248
|
-
)}
|
|
249
|
-
<GutterLine>
|
|
250
|
-
<Text color={C.green} bold>Setup complete!</Text>
|
|
251
|
-
</GutterLine>
|
|
252
|
-
</>
|
|
253
|
-
);
|
|
254
|
-
}
|
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
import React, { useState, useEffect } from "react";
|
|
2
|
-
import { Text, useInput } from "ink";
|
|
3
|
-
import { TextInput } from "@inkjs/ui";
|
|
4
|
-
import { GutterLine } from "../components/gutter-line.js";
|
|
5
|
-
import { GutteredMultiSelect } from "../components/guttered-select.js";
|
|
6
|
-
import type { WizardAnswers } from "../lib/types.js";
|
|
7
|
-
import { C } from "../theme.js";
|
|
8
|
-
|
|
9
|
-
interface Props {
|
|
10
|
-
initialValues?: Partial<WizardAnswers>;
|
|
11
|
-
onComplete: (answers: Partial<WizardAnswers>) => void;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
type Phase = "problem" | "users" | "techStack" | "coreFeatures" | "nonGoals";
|
|
15
|
-
|
|
16
|
-
const CORE_FEATURE_OPTIONS = [
|
|
17
|
-
{ label: "Authentication", value: "Authentication", description: "Login, signup, sessions" },
|
|
18
|
-
{ label: "Authorization", value: "Authorization", description: "Roles, permissions, RBAC" },
|
|
19
|
-
{ label: "API / REST", value: "API / REST", description: "RESTful endpoints" },
|
|
20
|
-
{ label: "GraphQL API", value: "GraphQL API", description: "GraphQL schema & resolvers" },
|
|
21
|
-
{ label: "Dashboard", value: "Dashboard", description: "Admin or user dashboard" },
|
|
22
|
-
{ label: "CRUD Operations", value: "CRUD Operations", description: "Create, read, update, delete" },
|
|
23
|
-
{ label: "File Upload", value: "File Upload", description: "Upload and manage files" },
|
|
24
|
-
{ label: "Search", value: "Search", description: "Full-text or filtered search" },
|
|
25
|
-
{ label: "Notifications", value: "Notifications", description: "Email, push, in-app" },
|
|
26
|
-
{ label: "Payments", value: "Payments", description: "Billing, subscriptions, checkout" },
|
|
27
|
-
{ label: "Real-time", value: "Real-time", description: "WebSockets, live updates" },
|
|
28
|
-
{ label: "Analytics", value: "Analytics", description: "Usage tracking, metrics" },
|
|
29
|
-
{ label: "CLI", value: "CLI", description: "Command-line interface" },
|
|
30
|
-
{ label: "CI/CD", value: "CI/CD", description: "Continuous integration & deployment" },
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
const TECH_STACK_OPTIONS = [
|
|
34
|
-
{ label: "React", value: "React", description: "UI component library" },
|
|
35
|
-
{ label: "Next.js", value: "Next.js", description: "Full-stack React framework" },
|
|
36
|
-
{ label: "Vue", value: "Vue", description: "Progressive UI framework" },
|
|
37
|
-
{ label: "Svelte", value: "Svelte", description: "Compile-time UI framework" },
|
|
38
|
-
{ label: "Node.js", value: "Node.js", description: "Server-side JavaScript runtime" },
|
|
39
|
-
{ label: "Express", value: "Express", description: "Minimal Node.js web framework" },
|
|
40
|
-
{ label: "Fastify", value: "Fastify", description: "High-performance Node.js framework" },
|
|
41
|
-
{ label: "PostgreSQL", value: "PostgreSQL", description: "Relational database" },
|
|
42
|
-
{ label: "MongoDB", value: "MongoDB", description: "Document database" },
|
|
43
|
-
{ label: "Redis", value: "Redis", description: "In-memory data store / cache" },
|
|
44
|
-
{ label: "Supabase", value: "Supabase", description: "Open-source Firebase alternative" },
|
|
45
|
-
{ label: "Prisma", value: "Prisma", description: "TypeScript ORM" },
|
|
46
|
-
{ label: "Docker", value: "Docker", description: "Container platform" },
|
|
47
|
-
{ label: "Tailwind CSS", value: "Tailwind CSS", description: "Utility-first CSS framework" },
|
|
48
|
-
{ label: "TypeScript", value: "TypeScript", description: "Typed JavaScript superset" },
|
|
49
|
-
{ label: "Python", value: "Python", description: "General-purpose language" },
|
|
50
|
-
{ label: "Go", value: "Go", description: "Systems programming language" },
|
|
51
|
-
{ label: "Rust", value: "Rust", description: "Memory-safe systems language" },
|
|
52
|
-
];
|
|
53
|
-
|
|
54
|
-
const PHASE_ORDER: Phase[] = ["problem", "users", "techStack", "coreFeatures", "nonGoals"];
|
|
55
|
-
|
|
56
|
-
export default function ProductContext({ initialValues, onComplete }: Props) {
|
|
57
|
-
const [phaseIndex, setPhaseIndex] = useState(0);
|
|
58
|
-
const [answers, setAnswers] = useState<Record<string, string>>({});
|
|
59
|
-
const [error, setError] = useState("");
|
|
60
|
-
const [liveInput, setLiveInput] = useState("");
|
|
61
|
-
|
|
62
|
-
// Prefill tech stack from Stack & Style selections (language + framework)
|
|
63
|
-
const prefillTechStack: string[] = [];
|
|
64
|
-
if (initialValues?.language) {
|
|
65
|
-
const match = TECH_STACK_OPTIONS.find(
|
|
66
|
-
(o) => o.value.toLowerCase() === initialValues.language!.toLowerCase()
|
|
67
|
-
);
|
|
68
|
-
if (match) prefillTechStack.push(match.value);
|
|
69
|
-
}
|
|
70
|
-
if (initialValues?.framework) {
|
|
71
|
-
const match = TECH_STACK_OPTIONS.find(
|
|
72
|
-
(o) => o.value.toLowerCase() === initialValues.framework!.toLowerCase()
|
|
73
|
-
);
|
|
74
|
-
if (match) prefillTechStack.push(match.value);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const currentPhase = PHASE_ORDER[phaseIndex]!;
|
|
78
|
-
const isFirst = phaseIndex === 0;
|
|
79
|
-
const isLast = phaseIndex === PHASE_ORDER.length - 1;
|
|
80
|
-
|
|
81
|
-
// Reset live input when navigating to a different field
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
setLiveInput("");
|
|
84
|
-
}, [phaseIndex]);
|
|
85
|
-
|
|
86
|
-
const isMultiSelectPhase = currentPhase === "techStack" || currentPhase === "coreFeatures";
|
|
87
|
-
|
|
88
|
-
useInput((input, key) => {
|
|
89
|
-
if (key.shift && key.tab && phaseIndex > 0) {
|
|
90
|
-
setPhaseIndex((i) => i - 1);
|
|
91
|
-
setError("");
|
|
92
|
-
} else if (key.tab && !key.shift && !isMultiSelectPhase) {
|
|
93
|
-
// Tab only advances text phases — multi-select phases use Enter to confirm
|
|
94
|
-
const required = currentPhase === "problem" || currentPhase === "users";
|
|
95
|
-
const val = liveInput.trim() || answers[currentPhase] || "";
|
|
96
|
-
if (required && !val) {
|
|
97
|
-
setError("This field is required");
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
setError("");
|
|
101
|
-
const next = { ...answers, [currentPhase]: val };
|
|
102
|
-
setAnswers(next);
|
|
103
|
-
if (phaseIndex < PHASE_ORDER.length - 1) {
|
|
104
|
-
setPhaseIndex((i) => i + 1);
|
|
105
|
-
} else {
|
|
106
|
-
finalize(next);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
function finalize(data: Record<string, string>) {
|
|
112
|
-
onComplete({
|
|
113
|
-
problem: data.problem,
|
|
114
|
-
users: data.users,
|
|
115
|
-
techStack: data.techStack,
|
|
116
|
-
coreFeatures: (data.coreFeatures || "")
|
|
117
|
-
.split(",")
|
|
118
|
-
.map((s) => s.trim())
|
|
119
|
-
.filter(Boolean),
|
|
120
|
-
nonGoals: (data.nonGoals || "")
|
|
121
|
-
.split(",")
|
|
122
|
-
.map((s) => s.trim())
|
|
123
|
-
.filter(Boolean),
|
|
124
|
-
userStories: [],
|
|
125
|
-
envVars: [],
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function advanceToNext(key: string, value: string) {
|
|
130
|
-
const next = { ...answers, [key]: value };
|
|
131
|
-
setAnswers(next);
|
|
132
|
-
setError("");
|
|
133
|
-
|
|
134
|
-
if (phaseIndex < PHASE_ORDER.length - 1) {
|
|
135
|
-
setPhaseIndex(phaseIndex + 1);
|
|
136
|
-
} else {
|
|
137
|
-
finalize(next);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function handleTextSubmit(key: string, required: boolean) {
|
|
142
|
-
return (value: string) => {
|
|
143
|
-
const val = value.trim();
|
|
144
|
-
if (required && !val) {
|
|
145
|
-
setError("This field is required");
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
advanceToNext(key, val);
|
|
149
|
-
};
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function handleTechStackSubmit(values: string[]) {
|
|
153
|
-
advanceToNext("techStack", values.join(", "));
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function handleCoreFeaturesSubmit(values: string[]) {
|
|
157
|
-
advanceToNext("coreFeatures", values.join(", "));
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Build completed fields display
|
|
161
|
-
const completedFields = PHASE_ORDER.slice(0, phaseIndex).map((key) => (
|
|
162
|
-
<GutterLine key={key}>
|
|
163
|
-
<Text color={C.overlay1}>
|
|
164
|
-
{key === "problem" && `Problem: ${answers[key]}`}
|
|
165
|
-
{key === "users" && `Users: ${answers[key]}`}
|
|
166
|
-
{key === "techStack" && `Tech stack: ${answers[key]}`}
|
|
167
|
-
{key === "coreFeatures" && `Core features: ${answers[key]}`}
|
|
168
|
-
{key === "nonGoals" && `Non-goals: ${answers[key]}`}
|
|
169
|
-
</Text>
|
|
170
|
-
</GutterLine>
|
|
171
|
-
));
|
|
172
|
-
|
|
173
|
-
const usersPlaceholder = answers["problem"]
|
|
174
|
-
? `People affected by: ${answers["problem"]}`
|
|
175
|
-
: "e.g. Developers, PMs, designers...";
|
|
176
|
-
|
|
177
|
-
return (
|
|
178
|
-
<>
|
|
179
|
-
{completedFields}
|
|
180
|
-
|
|
181
|
-
{currentPhase === "problem" && (
|
|
182
|
-
<>
|
|
183
|
-
<GutterLine>
|
|
184
|
-
<Text bold>What problem does this solve? </Text>
|
|
185
|
-
<TextInput
|
|
186
|
-
placeholder="Describe the core problem..."
|
|
187
|
-
onChange={setLiveInput}
|
|
188
|
-
onSubmit={handleTextSubmit("problem", true)}
|
|
189
|
-
/>
|
|
190
|
-
</GutterLine>
|
|
191
|
-
<GutterLine>
|
|
192
|
-
<Text color={C.overlay1}>{"Tab next Enter confirm"}</Text>
|
|
193
|
-
</GutterLine>
|
|
194
|
-
</>
|
|
195
|
-
)}
|
|
196
|
-
|
|
197
|
-
{currentPhase === "users" && (
|
|
198
|
-
<>
|
|
199
|
-
<GutterLine>
|
|
200
|
-
<Text bold>Who are the users? </Text>
|
|
201
|
-
<TextInput
|
|
202
|
-
placeholder={usersPlaceholder}
|
|
203
|
-
onChange={setLiveInput}
|
|
204
|
-
onSubmit={handleTextSubmit("users", true)}
|
|
205
|
-
/>
|
|
206
|
-
</GutterLine>
|
|
207
|
-
<GutterLine>
|
|
208
|
-
<Text color={C.overlay1}>{"Shift+Tab prev Tab next Enter confirm"}</Text>
|
|
209
|
-
</GutterLine>
|
|
210
|
-
</>
|
|
211
|
-
)}
|
|
212
|
-
|
|
213
|
-
{currentPhase === "techStack" && (
|
|
214
|
-
<>
|
|
215
|
-
<GutterLine>
|
|
216
|
-
<Text bold>Tech stack:</Text>
|
|
217
|
-
</GutterLine>
|
|
218
|
-
<GutteredMultiSelect
|
|
219
|
-
options={TECH_STACK_OPTIONS}
|
|
220
|
-
initialSelected={prefillTechStack}
|
|
221
|
-
onSubmit={handleTechStackSubmit}
|
|
222
|
-
/>
|
|
223
|
-
<GutterLine>
|
|
224
|
-
<Text color={C.overlay1}>{"Shift+Tab prev ↑↓ move Space toggle Enter confirm"}</Text>
|
|
225
|
-
</GutterLine>
|
|
226
|
-
</>
|
|
227
|
-
)}
|
|
228
|
-
|
|
229
|
-
{currentPhase === "coreFeatures" && (
|
|
230
|
-
<>
|
|
231
|
-
<GutterLine>
|
|
232
|
-
<Text bold>Core features:</Text>
|
|
233
|
-
</GutterLine>
|
|
234
|
-
<GutteredMultiSelect
|
|
235
|
-
options={CORE_FEATURE_OPTIONS}
|
|
236
|
-
onSubmit={handleCoreFeaturesSubmit}
|
|
237
|
-
allowCustom
|
|
238
|
-
customPlaceholder="e.g. Export PDF, Onboarding flow..."
|
|
239
|
-
/>
|
|
240
|
-
<GutterLine>
|
|
241
|
-
<Text color={C.overlay1}>{"Shift+Tab prev ↑↓ move Space toggle Enter confirm"}</Text>
|
|
242
|
-
</GutterLine>
|
|
243
|
-
</>
|
|
244
|
-
)}
|
|
245
|
-
|
|
246
|
-
{currentPhase === "nonGoals" && (
|
|
247
|
-
<>
|
|
248
|
-
<GutterLine>
|
|
249
|
-
<Text bold>Non-goals (comma-separated): </Text>
|
|
250
|
-
<TextInput
|
|
251
|
-
placeholder="e.g. Mobile app, Real-time sync..."
|
|
252
|
-
onChange={setLiveInput}
|
|
253
|
-
onSubmit={handleTextSubmit("nonGoals", false)}
|
|
254
|
-
/>
|
|
255
|
-
</GutterLine>
|
|
256
|
-
<GutterLine>
|
|
257
|
-
<Text color={C.overlay1}>{"Shift+Tab prev Tab / Enter confirm"}</Text>
|
|
258
|
-
</GutterLine>
|
|
259
|
-
</>
|
|
260
|
-
)}
|
|
261
|
-
|
|
262
|
-
{error && (
|
|
263
|
-
<GutterLine>
|
|
264
|
-
<Text color={C.red}>{error}</Text>
|
|
265
|
-
</GutterLine>
|
|
266
|
-
)}
|
|
267
|
-
</>
|
|
268
|
-
);
|
|
269
|
-
}
|