Skip to content

Commit 47bab01

Browse files
lucianfialhoclaude
andcommitted
feat: replace Playwright with agent-browser for deterministic pipeline
- Phase 0 now uses agent-browser CLI for screenshots, color extraction, brand/font/icon detection — fully deterministic, zero Claude API cost - Add section catalog and page renderers for structured page generation - Add pixel-diff module for visual verification - Add finalizeProject() to adapter interface to fix theme toggle and hydration issues after template writing - Next.js adapter patches layout.tsx with suppressHydrationWarning and touches globals.css to trigger Turbopack recompilation - Remove playwright-core dependency Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ee057f4 commit 47bab01

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+3844
-409
lines changed

.mcp.json

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
},
77
"reactbits": {
88
"command": "npx",
9-
"args": ["-y", "reactbits-mcp-server"]
9+
"args": [
10+
"-y",
11+
"reactbits-mcp-server"
12+
]
1013
},
1114
"shadcn": {
1215
"type": "http",
@@ -18,11 +21,17 @@
1821
},
1922
"hugeicons": {
2023
"command": "npx",
21-
"args": ["-y", "@hugeicons/mcp-server"]
24+
"args": [
25+
"-y",
26+
"@hugeicons/mcp-server"
27+
]
2228
},
2329
"motion-dev": {
2430
"command": "npx",
25-
"args": ["-y", "motion-mcp"]
31+
"args": [
32+
"-y",
33+
"motion-mcp"
34+
]
2635
}
2736
}
2837
}

packages/cli/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@
3535
"@clack/prompts": "^1.0.1",
3636
"@guataiba/isac-core": "workspace:*",
3737
"@guataiba/isac-nextjs": "workspace:*",
38+
"agent-browser": "^0.16.1",
3839
"chalk": "^5.4.1",
39-
"commander": "^13.1.0",
40-
"playwright-core": "^1.50.0"
40+
"commander": "^13.1.0"
4141
},
4242
"devDependencies": {
4343
"@types/node": "^22.13.0",

packages/cli/tsup.config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,4 @@ export default defineConfig({
1212
js: "#!/usr/bin/env node",
1313
},
1414
noExternal: ["@guataiba/isac-core", "@guataiba/isac-nextjs"],
15-
external: ["playwright-core"],
1615
});

packages/core/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,15 @@
3636
"url": "https://github.com/guataiba/isac-cli/issues"
3737
},
3838
"dependencies": {
39+
"agent-browser": "^0.16.1",
3940
"chalk": "^5.4.1",
40-
"playwright-core": "^1.50.0"
41+
"pixelmatch": "^6.0.0",
42+
"pngjs": "^7.0.0",
43+
"zod": "^3.24.0"
4144
},
4245
"devDependencies": {
4346
"@types/node": "^22.13.0",
47+
"@types/pngjs": "^6.0.5",
4448
"tsup": "^8.4.0",
4549
"typescript": "^5.7.0",
4650
"vitest": "^4.0.18"

packages/core/src/__tests__/prompts.test.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,39 @@ import { getScreenshotPrompt } from "../prompts/screenshot-capturer.js";
44
describe("Core prompt generation", () => {
55
const testUrl = "https://example.com";
66

7-
it("design-system mode prompt does NOT contain take_screenshot", () => {
7+
it("design-system mode prompt uses agent-browser (no screenshots)", () => {
88
const prompt = getScreenshotPrompt(testUrl, "design-system");
99
expect(prompt).toContain(testUrl);
10-
expect(prompt).toContain("navigate_page");
11-
expect(prompt).toContain("1440px");
10+
expect(prompt).toContain("agent-browser open");
11+
expect(prompt).toContain("1440");
1212
// Font extraction steps
1313
expect(prompt).toContain("font-data.json");
14-
expect(prompt).toContain("evaluate_script");
14+
expect(prompt).toContain("agent-browser eval");
1515
expect(prompt).toContain("public/fonts");
16-
// Should NOT contain screenshot instructions
17-
expect(prompt).not.toContain("take_screenshot");
16+
// Should NOT contain actual screenshot capture instructions (full-page.png)
1817
expect(prompt).not.toContain("full-page.png");
18+
// Should NOT reference chrome-devtools MCP
19+
expect(prompt).not.toContain("mcp__chrome-devtools");
1920
});
2021

2122
it("default mode is design-system (no screenshots)", () => {
2223
const prompt = getScreenshotPrompt(testUrl);
23-
expect(prompt).not.toContain("take_screenshot");
24+
expect(prompt).not.toContain("full-page.png");
2425
});
2526

26-
it("replicate mode prompt CONTAINS take_screenshot", () => {
27+
it("replicate mode prompt CONTAINS screenshot commands", () => {
2728
const prompt = getScreenshotPrompt(testUrl, "replicate");
2829
expect(prompt).toContain(testUrl);
29-
expect(prompt).toContain("navigate_page");
30-
expect(prompt).toContain("take_screenshot");
30+
expect(prompt).toContain("agent-browser open");
31+
expect(prompt).toContain("agent-browser screenshot --full");
3132
expect(prompt).toContain("full-page.png");
32-
expect(prompt).toContain("1440px");
33+
expect(prompt).toContain("1440");
3334
// Font extraction steps
3435
expect(prompt).toContain("font-data.json");
35-
expect(prompt).toContain("evaluate_script");
36+
expect(prompt).toContain("agent-browser eval");
3637
expect(prompt).toContain("public/fonts");
38+
// Should NOT reference chrome-devtools MCP
39+
expect(prompt).not.toContain("mcp__chrome-devtools");
3740
});
3841

3942
it("color extraction is NOT in the prompt (handled by Playwright)", () => {
Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { describe, it, expect } from "vitest";
22
import {
3-
CHROME_DEVTOOLS_TOOLS,
43
PHASE_0_TOOLS,
54
PHASE_1B_TOOLS,
65
PHASE_2_TOOLS,
@@ -9,26 +8,14 @@ import {
98
} from "../pipeline/tools.js";
109

1110
describe("Tool sets", () => {
12-
it("CHROME_DEVTOOLS_TOOLS has 18 tools", () => {
13-
expect(CHROME_DEVTOOLS_TOOLS).toHaveLength(18);
14-
});
15-
16-
it("all chrome-devtools tools have correct prefix", () => {
17-
for (const tool of CHROME_DEVTOOLS_TOOLS) {
18-
expect(tool).toMatch(/^mcp__chrome-devtools__/);
11+
it("Phase 0 uses Bash for agent-browser (no MCP tools)", () => {
12+
expect(PHASE_0_TOOLS).toEqual(["Read", "Write", "Bash"]);
13+
for (const tool of PHASE_0_TOOLS) {
14+
expect(tool).not.toMatch(/^mcp__/);
1915
}
2016
});
2117

22-
it("Phase 0 includes chrome-devtools tools, Read, Write, and Bash (for font extraction)", () => {
23-
expect(PHASE_0_TOOLS).toContain("mcp__chrome-devtools__navigate_page");
24-
expect(PHASE_0_TOOLS).toContain("mcp__chrome-devtools__take_screenshot");
25-
expect(PHASE_0_TOOLS).toContain("mcp__chrome-devtools__evaluate_script");
26-
expect(PHASE_0_TOOLS).toContain("Read");
27-
expect(PHASE_0_TOOLS).toContain("Write");
28-
expect(PHASE_0_TOOLS).toContain("Bash");
29-
});
30-
31-
it("Phase 1b has only file tools (no chrome-devtools)", () => {
18+
it("Phase 1b has only file tools", () => {
3219
expect(PHASE_1B_TOOLS).toEqual(["Read", "Write", "Edit", "Glob", "Bash"]);
3320
});
3421

@@ -39,17 +26,17 @@ describe("Tool sets", () => {
3926
expect(PHASE_2_TOOLS).not.toContain("Bash");
4027
});
4128

42-
it("Phase 3 has full file tools but no chrome-devtools", () => {
29+
it("Phase 3 has full file tools but no MCP", () => {
4330
expect(PHASE_3_TOOLS).toEqual(["Read", "Write", "Edit", "Glob", "Grep", "Bash"]);
4431
for (const tool of PHASE_3_TOOLS) {
4532
expect(tool).not.toMatch(/^mcp__/);
4633
}
4734
});
4835

49-
it("Phase 4 includes chrome-devtools tools", () => {
50-
expect(PHASE_4_TOOLS).toContain("mcp__chrome-devtools__take_screenshot");
51-
expect(PHASE_4_TOOLS).toContain("mcp__chrome-devtools__navigate_page");
52-
expect(PHASE_4_TOOLS).toContain("Read");
53-
expect(PHASE_4_TOOLS).toContain("Bash");
36+
it("Phase 4 uses Bash for agent-browser (no MCP tools)", () => {
37+
expect(PHASE_4_TOOLS).toEqual(["Read", "Bash"]);
38+
for (const tool of PHASE_4_TOOLS) {
39+
expect(tool).not.toMatch(/^mcp__/);
40+
}
5441
});
5542
});

packages/core/src/__tests__/types.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const mockAdapter: FrameworkAdapter = {
3131
validateTokenExtraction: () => ({ valid: true }),
3232
postProcessTokenExtraction: () => {},
3333
validateDesignSystem: () => ({ valid: true }),
34-
generateDesignSystemFallback: () => {},
34+
generateDesignSystemData: () => {},
3535
validateImplementation: () => ({ valid: true }),
3636
collectCreatedFiles: () => [],
3737
};

packages/core/src/adapter.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { PipelineMode } from "./pipeline/types.js";
2+
import type { PagePlan } from "./catalog/index.js";
23

34
/**
45
* Describes a template file that the adapter wants written to disk.
@@ -145,15 +146,39 @@ export interface FrameworkAdapter {
145146
validateDesignSystem(cwd: string): PhaseValidation;
146147

147148
/**
148-
* Phase 1B fallback: Generate design system data from globals.css if Claude failed.
149+
* Phase 1B: Generate design system data deterministically from globals.css.
150+
* This is now the primary path (no Claude needed).
149151
*/
150-
generateDesignSystemFallback(cwd: string, url: string): void;
152+
generateDesignSystemData(cwd: string, url: string): void;
153+
154+
/**
155+
* @deprecated Use generateDesignSystemData instead. Kept for backward compatibility.
156+
*/
157+
generateDesignSystemFallback?(cwd: string, url: string): void;
158+
159+
/**
160+
* Phase 2 (v2): Returns the prompt for structured JSON page planning.
161+
* If not implemented, falls back to getPagePlannerPrompt() with Markdown output.
162+
*/
163+
getPagePlannerPromptV2?(screenshotDir: string, catalogTypes: string[]): string;
164+
165+
/**
166+
* Phase 3 (deterministic): Render page.tsx from a validated PagePlan.
167+
* If not implemented, falls back to Claude-based Phase 3.
168+
*/
169+
renderPageFromPlan?(cwd: string, plan: PagePlan): string;
151170

152171
/**
153172
* Phase 3 validation: Check that implementation files exist.
154173
*/
155174
validateImplementation(cwd: string): PhaseValidation;
156175

176+
/**
177+
* Final cleanup after all files are written: clear build cache, patch layout, etc.
178+
* Called as the very last step before the pipeline exits.
179+
*/
180+
finalizeProject?(cwd: string): void;
181+
157182
/**
158183
* Collect created files for the pipeline result summary.
159184
* Returns paths relative to project root.

packages/core/src/catalog/index.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
export {
2+
SectionSchema,
3+
SECTION_TYPES,
4+
HeaderSection,
5+
HeroSection,
6+
FeatureGridSection,
7+
StatsBarSection,
8+
DataTableSection,
9+
TestimonialsSection,
10+
PricingTableSection,
11+
CTASection,
12+
FAQSection,
13+
ContentBlockSection,
14+
LogoCloudSection,
15+
FooterSection,
16+
CustomHTMLSection,
17+
} from "./sections.js";
18+
export type {
19+
Section,
20+
SectionType,
21+
HeaderSectionData,
22+
HeroSectionData,
23+
FeatureGridSectionData,
24+
StatsBarSectionData,
25+
DataTableSectionData,
26+
TestimonialsSectionData,
27+
PricingTableSectionData,
28+
CTASectionData,
29+
FAQSectionData,
30+
ContentBlockSectionData,
31+
LogoCloudSectionData,
32+
FooterSectionData,
33+
CustomHTMLSectionData,
34+
} from "./sections.js";
35+
36+
export { PagePlanSchema } from "./page-plan.js";
37+
export type { PagePlan } from "./page-plan.js";
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { z } from "zod";
2+
import { SectionSchema } from "./sections.js";
3+
4+
// ── Page Plan Schema ────────────────────────────────────────────
5+
6+
export const PagePlanSchema = z.object({
7+
metadata: z.object({
8+
title: z.string(),
9+
description: z.string().optional(),
10+
}),
11+
12+
sections: z.array(SectionSchema).min(1)
13+
.describe("Ordered list of page sections, top to bottom"),
14+
15+
reusableComponents: z.array(z.object({
16+
name: z.string(),
17+
note: z.string().optional().describe("e.g. 'already exists — copy from design-system/components/'"),
18+
})).optional(),
19+
20+
externalLinks: z.array(z.object({
21+
text: z.string(),
22+
url: z.string(),
23+
})).optional(),
24+
25+
dependencies: z.array(z.string()).optional()
26+
.describe("npm packages needed beyond the base project"),
27+
});
28+
29+
export type PagePlan = z.infer<typeof PagePlanSchema>;

0 commit comments

Comments
 (0)