feat: add $D serve command for HTTP-based comparison board feedback

The comparison board feedback loop was fundamentally broken: browse blocks
file:// URLs (url-validation.ts:71), so $B goto file://board.html always
fails. The fallback open + $B eval polls a different browser instance.

$D serve fixes this by serving the board over HTTP on localhost. The server
is stateful: stays alive across regeneration rounds, exposes /api/progress
for the board to poll, and accepts /api/reload from the agent to swap in
new board HTML. Stdout carries feedback JSON only; stderr carries telemetry.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-03-27 08:13:59 -06:00
parent 73395fb54b
commit 41cf56617a
3 changed files with 251 additions and 6 deletions
+17 -4
View File
@@ -23,6 +23,7 @@ import { extractDesignLanguage, updateDesignMd } from "./memory";
import { diffMockups, verifyAgainstMockup } from "./diff";
import { evolve } from "./evolve";
import { generateDesignToCodePrompt } from "./design-to-code";
import { serve } from "./serve";
function parseArgs(argv: string[]): { command: string; flags: Record<string, string | boolean> } {
const args = argv.slice(2); // skip bun/node and script path
@@ -134,10 +135,15 @@ async function main(): Promise<void> {
// Parse --images as glob or multiple files
const imagesArg = flags.images as string;
const images = await resolveImagePaths(imagesArg);
compare({
images,
output: (flags.output as string) || "/tmp/gstack-design-board.html",
});
const outputPath = (flags.output as string) || "/tmp/gstack-design-board.html";
compare({ images, output: outputPath });
// If --serve flag is set, start HTTP server for the board
if (flags.serve) {
await serve({
html: outputPath,
timeout: flags.timeout ? parseInt(flags.timeout as string) : 600,
});
}
break;
}
@@ -230,6 +236,13 @@ async function main(): Promise<void> {
output: (flags.output as string) || "/tmp/gstack-evolved.png",
});
break;
case "serve":
await serve({
html: flags.html as string,
timeout: flags.timeout ? parseInt(flags.timeout as string) : 600,
});
break;
}
}