Skip to content

Commit d7a82fb

Browse files
authored
fix(#315): scan outputComponents instead of hardcoding array index (#346)
Project.generate() hardcoded outputComponents[1] to find the screen. The Stitch API omits the designSystem prefix block on 2nd+ calls, shifting the screen to index 0 and causing a StitchError. Screen.edit() had the same fragility with a hardcoded index 0. Both methods now scan outputComponents for the first entry with design.screens[0], matching the robust pattern already used by Screen.variants() and DesignSystem.apply(). Also updates domain-map.json to use 'find: design.screens' so the next codegen run produces scanning code instead of reverting. Fixes #315.
1 parent bbacfbb commit d7a82fb

4 files changed

Lines changed: 44 additions & 4 deletions

File tree

packages/sdk/generated/domain-map.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@
115115
"projection": [
116116
{
117117
"prop": "outputComponents",
118-
"index": 1
118+
"find": "design.screens"
119119
},
120120
{
121121
"prop": "design"
@@ -156,7 +156,7 @@
156156
"projection": [
157157
{
158158
"prop": "outputComponents",
159-
"index": 0
159+
"find": "design.screens"
160160
},
161161
{
162162
"prop": "design"

packages/sdk/generated/src/project.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class Project {
3838
async generate(prompt: string, deviceType?: "DEVICE_TYPE_UNSPECIFIED" | "MOBILE" | "DESKTOP" | "TABLET" | "AGNOSTIC", modelId?: "MODEL_ID_UNSPECIFIED" | "GEMINI_3_PRO" | "GEMINI_3_FLASH" | "GEMINI_3_1_PRO"): Promise<Screen> {
3939
try {
4040
const raw = await this.client.callTool<any>("generate_screen_from_text", { projectId: this.projectId, prompt, deviceType, modelId });
41-
const _projected = raw?.outputComponents?.[1]?.design?.screens?.[0];
41+
const _projected = (raw?.outputComponents ?? []).map((c: any) => c?.design?.screens?.[0]).find((s: any) => s != null);
4242
if (!_projected) throw new StitchError({ code: "UNKNOWN_ERROR", message: "Incomplete API response from generate_screen_from_text: expected object at projection path", recoverable: false });
4343
return new Screen(this.client, { ..._projected, projectId: this.projectId })
4444
} catch (error) {

packages/sdk/generated/src/screen.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class Screen {
3838
async edit(prompt: string, deviceType?: "DEVICE_TYPE_UNSPECIFIED" | "MOBILE" | "DESKTOP" | "TABLET" | "AGNOSTIC", modelId?: "MODEL_ID_UNSPECIFIED" | "GEMINI_3_PRO" | "GEMINI_3_FLASH" | "GEMINI_3_1_PRO"): Promise<Screen> {
3939
try {
4040
const raw = await this.client.callTool<any>("edit_screens", { projectId: this.projectId, selectedScreenIds: [this.screenId], prompt, deviceType, modelId });
41-
const _projected = raw?.outputComponents?.[0]?.design?.screens?.[0];
41+
const _projected = (raw?.outputComponents ?? []).map((c: any) => c?.design?.screens?.[0]).find((s: any) => s != null);
4242
if (!_projected) throw new StitchError({ code: "UNKNOWN_ERROR", message: "Incomplete API response from edit_screens: expected object at projection path", recoverable: false });
4343
return new Screen(this.client, { ..._projected, projectId: this.projectId })
4444
} catch (error) {

packages/sdk/test/unit/sdk.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,23 @@ describe("SDK Unit Tests", () => {
136136
expect(edited.id).toBe("edited-screen");
137137
});
138138

139+
it("edit should find screen when a prefix block is present", async () => {
140+
const screen = new Screen(mockClient, screenData);
141+
142+
(mockClient.callTool as Mock).mockResolvedValue({
143+
outputComponents: [
144+
{ designSystem: { name: "ds-update" } },
145+
{ design: { screens: [{ id: "edited-2", htmlCode: "<div>Dark</div>", projectId }] } },
146+
],
147+
projectId,
148+
sessionId: "session-2",
149+
});
150+
151+
const edited = await screen.edit("Make it dark");
152+
expect(edited).toBeInstanceOf(Screen);
153+
expect(edited.id).toBe("edited-2");
154+
});
155+
139156
it("edit should throw StitchError (not TypeError) when response has no screens", async () => {
140157
const screen = new Screen(mockClient, screenData);
141158

@@ -255,6 +272,29 @@ describe("SDK Unit Tests", () => {
255272
expect(result.projectId).toBe(projectId);
256273
});
257274

275+
it("generate should find screen when designSystem block is absent (issue #315)", async () => {
276+
const project = new Project(mockClient, projectId);
277+
278+
(mockClient.callTool as Mock).mockResolvedValue({
279+
outputComponents: [
280+
{
281+
design: {
282+
screens: [{ id: "screen-2", name: "Second", htmlCode: "<div>2</div>", projectId }],
283+
},
284+
},
285+
{ text: "summary" },
286+
{ suggestion: "try this" },
287+
],
288+
projectId: projectId,
289+
sessionId: "session-2",
290+
});
291+
292+
const result = await project.generate("Second page");
293+
294+
expect(result).toBeInstanceOf(Screen);
295+
expect(result.id).toBe("screen-2");
296+
});
297+
258298

259299
it("generate should throw StitchError (not TypeError) when response has no screens", async () => {
260300
const project = new Project(mockClient, projectId);

0 commit comments

Comments
 (0)