Public: anyone can read this; the crew can change it.

The terminal's New forge screen picks a template, Blank or Starter app, as MCP spawn's `template` does #8035

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/7934-the-terminal-s-new-forge-screen-picks-a → main 5 commits fork 4 threads · 4 unresolved

  • Merged. scout/7934-the-terminal-s-new-forge-screen-picks-a was merged into main as 060eccc.

Review

Sign in to review this change.
2 files changed +38 −2
src/tui/screens/newforge.ts +10 −2
@@ -35,6 +35,8 @@ const SIZE_CHOICES = Object.keys(SIZES) as Size[];
3535
3636 const TEMPLATE_CHOICES = TEMPLATES.map((t) => t.id);
3737
38 const TEMPLATE_HINT = "blank (default) or starter: left and right choose, or type it.";
39
3840 const SPECS: readonly Spec[] = [
3941 { key: "name", label: "Name", hint: "Lowercase letters, digits and dashes." },
4042 { key: "for", label: "For whom", hint: "A person or client, for your list of forges sent. Optional." },
@@ -46,9 +48,15 @@ const SPECS: readonly Spec[] = [
4648 { key: "budgetUsd", label: "Budget in dollars", hint: `${MIN_BUDGET_USD}–${MAX_BUDGET_USD}, for the key's whole life. Needed with a key.` },
4749 { key: "followMode", label: "Follows this forge's code", hint: FOLLOW_LINE },
4850 { key: "size", label: "Size", hint: "small (default), medium or large: left and right choose, or type it.", choices: SIZE_CHOICES },
49 { key: "template", label: "Template", hint: "blank (default) or starter: left and right choose, or type it.", choices: TEMPLATE_CHOICES },
51 { key: "template", label: "Template", hint: TEMPLATE_HINT, choices: TEMPLATE_CHOICES },
5052 ];
5153
54 /** The chosen template's own sentence; a typed id that is no template keeps the general hint. */
outdated

This repeats the string from SPECS line 49. Take the fallback from the spec so there is one source.

55 function templateHint(id: string): string {
56 const t = TEMPLATES.find((x) => x.id === id);
57 return t === undefined ? TEMPLATE_HINT : t.says;
58 }
59
5260 /** The door's "field: reason" refusal, as the field it names and the reason. */
5361 function refusal(body: Record<string, unknown>): { key?: string; text: string } {
5462 const text = String(body["message"] ?? "refused");
@@ -199,7 +207,7 @@ export function newForgeScreen(f: Session, ctx: Ctx): Screen {
199207 ...(focus === i ? { cursor: [...shown].length } : {}),
200208 ...(error !== undefined && (errorKey === spec.key || (errorKey === undefined && focus === i))
201209 ? { error }
202 : { hint: spec.hint }),
210 : { hint: spec.key === "template" ? templateHint(values["template"] || DEFAULT_TEMPLATE) : spec.hint }),
203211 });
204212 if (focus === i) cursor = at;
205213 });
test/tui-newforge.test.ts +28
@@ -207,6 +207,34 @@ describe("the new forge screen", () => {
207207 expect(docs.find((d) => d?.name === "tuisize")?.size).toBe("medium");
208208 }));
209209
210 test("Blank is the default: the plan names no template", () =>
outdated

The name says the default is sent as nothing, but the test never inspects what was sent. Assert on the body.

211 withScreen(admin, async ({ tui, terminal }) => {
212 terminal.feed("tuiblank" + "\t".repeat(10));
213 await settleApp(tui);
214 expect(tui.screen.toText()).toContain("Only the apps you fork");
215 terminal.feed("\x10");
216 await settleApp(tui);
217 expect(tui.screen.toText()).toContain("The plan");
218 expect(tui.screen.toText()).not.toContain("Template:");
219 }));
220
221 test("choosing starter shows its sentence and the plan names it", () =>
222 withScreen(admin, async ({ tui, terminal }) => {
223 terminal.feed("tuistart" + "\t".repeat(10) + "\x1b[C"); // right arrow
224 await settleApp(tui);
225 expect(tui.screen.toText()).toContain("A server, a page, tests and a forge.json");
226 terminal.feed("\x10");
227 await settleApp(tui);
228 expect(tui.screen.toText()).toContain("Template: Starter app.");
229 }));
230
231 test("an unknown template is refused beside the field", () =>
232 withScreen(admin, async ({ tui, terminal }) => {
233 terminal.feed("tuitpl" + "\t".repeat(10) + "nope\x10");
234 await settleApp(tui);
235 expect(tui.screen.toText()).toContain("template must be blank or starter");
236 }));
237
210238 test("an unknown size is refused beside the field", () =>
211239 withScreen(admin, async ({ tui, terminal }) => {
212240 terminal.feed("tuibig" + "\t".repeat(9) + "huge\x10");

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs