@biela.dev/ai-bridge 0.1.0

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/dist/index.cjs ADDED
@@ -0,0 +1,470 @@
1
+ 'use strict';
2
+
3
+ var devices = require('@biela.dev/devices');
4
+
5
+ // src/serializer.ts
6
+ function serializeDLCForAI(dlc, appDescription) {
7
+ const { device, screen, safeArea, hardwareOverlays, statusBar, homeIndicator, contentZone, cssVariables } = dlc;
8
+ const sa = safeArea.portrait;
9
+ const cz = contentZone.portrait;
10
+ const isIOS = device.platform === "ios";
11
+ const platformName = isIOS ? "iOS" : "Android";
12
+ const designLanguage = isIOS ? "iOS 18 design language (SF Pro, iOS navigation patterns, native tab bars, iOS sheets)" : "Material You / Android 15 design language (Roboto Flex, Material 3, bottom navigation bar, Material sheets)";
13
+ const cssVarLines = Object.entries(cssVariables).map(
14
+ ([key, value]) => ` ${key}: ${value}`
15
+ );
16
+ let overlayDesc = "";
17
+ if (hardwareOverlays.type === "dynamic-island") {
18
+ const ov = hardwareOverlays.portrait;
19
+ overlayDesc = `Dynamic Island (pill shape): ${ov.width}\xD7${ov.height}pt centered at y=${ov.y}. Do NOT place interactive content behind the Dynamic Island. The status bar area (${statusBar.height}pt) contains the clock and signal indicators split around the island.`;
20
+ } else if (hardwareOverlays.type === "punch-hole") {
21
+ const ov = hardwareOverlays.portrait;
22
+ overlayDesc = `Punch-hole camera (circle): ${ov.width}pt diameter centered at top of screen. The status bar area (${statusBar.height}pt) contains the clock, signal, and battery indicators. Avoid placing critical content directly behind the camera cutout.`;
23
+ } else if (hardwareOverlays.type === "notch") {
24
+ overlayDesc = `Notch at top center. The status bar flanks the notch with clock and indicators.`;
25
+ }
26
+ let homeDesc = "";
27
+ if (homeIndicator.type === "swipe-bar") {
28
+ homeDesc = `Home indicator: swipe bar (${homeIndicator.width}\xD7${homeIndicator.height}pt) at the bottom. Reserve ${sa.bottom}pt at the bottom for the home indicator area. Do not place interactive buttons in this zone.`;
29
+ } else if (homeIndicator.type === "gestureline") {
30
+ homeDesc = `Gesture navigation bar: thin line (${homeIndicator.width}\xD7${homeIndicator.height}pt) at the bottom. Reserve ${sa.bottom}pt at the bottom for the navigation bar. Do not place interactive buttons in this zone.`;
31
+ }
32
+ return `You are generating a React mobile UI component. You MUST respect ALL constraints below exactly.
33
+
34
+ DEVICE: ${device.name} (${platformName}, ${device.year})
35
+ SCREEN SIZE: ${screen.width}\xD7${screen.height} logical points (CSS pixels)
36
+
37
+ \u2501\u2501\u2501 RENDERING CONTEXT \u2501\u2501\u2501
38
+ Your component renders inside a container that is ALREADY sized to exactly ${screen.width}\xD7${screen.height}px.
39
+ - Do NOT set width or height on your root element \u2014 the container handles sizing.
40
+ - Do NOT use viewport units (vw, vh, dvh, svh) \u2014 they reference the browser viewport, not the device screen.
41
+ - Use 100% width/height, CSS variables, or fixed pixel values instead.
42
+ - The container has overflow: hidden and clips to the screen shape (${screen.cornerRadius}px corner radius).
43
+ - Your component always renders at 1:1 device resolution. The visual presentation is scaled via CSS transform \u2014 you never need to think about scaling.
44
+
45
+ \u2501\u2501\u2501 CSS VARIABLES (already injected \u2014 use var() directly) \u2501\u2501\u2501
46
+ ${cssVarLines.join("\n")}
47
+
48
+ \u2501\u2501\u2501 LAYOUT CONSTRAINTS (NON-NEGOTIABLE) \u2501\u2501\u2501
49
+ Usable content zone: x=${cz.x}, y=${cz.y}, width=${cz.width}pt, height=${cz.height}pt
50
+ Safe area insets (portrait): top ${sa.top}pt, bottom ${sa.bottom}pt, left ${sa.left}pt, right ${sa.right}pt
51
+ Corner radius: ${screen.cornerRadius}pt (already applied to container clip \u2014 but round inner elements near edges to match)
52
+ ${overlayDesc ? `
53
+ ${overlayDesc}` : ""}
54
+ ${homeDesc ? `
55
+ ${homeDesc}` : ""}
56
+
57
+ \u2501\u2501\u2501 APP TO BUILD \u2501\u2501\u2501
58
+ ${appDescription}
59
+
60
+ \u2501\u2501\u2501 OUTPUT REQUIREMENTS \u2501\u2501\u2501
61
+ 1. Return a SINGLE self-contained React functional component as the default export.
62
+ 2. Use Tailwind CSS utility classes for all styling.
63
+ 3. Include a realistic status bar row at the top (time, signal, battery icons) that respects the ${statusBar.height}pt status bar height. Use var(--safe-top) for the top padding.
64
+ 4. The UI must feel native to ${designLanguage}.
65
+ 5. Include realistic dummy data \u2014 no Lorem Ipsum, no placeholder text. Use genuine-looking names, dates, numbers.
66
+ 6. Make it interactive where appropriate (useState for toggles, tabs, modals, forms, navigation).
67
+ 7. Do NOT import any external libraries \u2014 React only (hooks: useState, useEffect, useRef, useMemo, useCallback).
68
+ 8. Component name: GeneratedApp (export default function GeneratedApp()).
69
+ 9. The component must handle its own scroll internally if content exceeds the screen height \u2014 use overflow-y-auto on a scrollable container.
70
+ 10. Use var(--safe-bottom) for bottom padding to avoid the home indicator zone.
71
+ 11. All text must be legible \u2014 minimum 12px font size, proper contrast ratios.
72
+ 12. Return ONLY the component code \u2014 no markdown fences, no explanations, just the JSX.`;
73
+ }
74
+
75
+ // src/compact-serializer.ts
76
+ function serializeCompactDLC(dlc, validation) {
77
+ const { device, screen, safeArea, contentZone } = dlc;
78
+ const sa = safeArea.portrait;
79
+ const cz = contentZone.portrait;
80
+ const platform = device.platform === "ios" ? "iOS" : "Android";
81
+ let prompt = `[BielaFrame] Target: ${device.name} (${platform}, ${screen.width}\xD7${screen.height}pt)
82
+ Safe areas: top ${sa.top}pt, bottom ${sa.bottom}pt | Content zone: ${cz.width}\xD7${cz.height}pt
83
+ Corner radius: ${screen.cornerRadius}pt | All original constraints still apply.`;
84
+ if (validation && (validation.errors.length > 0 || validation.warnings.length > 0)) {
85
+ prompt += "\n\n[BielaFrame Validation Feedback]";
86
+ if (validation.errors.length > 0) {
87
+ prompt += "\nERRORS (must fix):";
88
+ for (const err of validation.errors) {
89
+ prompt += `
90
+ - ${err}`;
91
+ }
92
+ }
93
+ if (validation.warnings.length > 0) {
94
+ prompt += "\nWARNINGS (should fix):";
95
+ for (const warn of validation.warnings) {
96
+ prompt += `
97
+ - ${warn}`;
98
+ }
99
+ }
100
+ }
101
+ return prompt;
102
+ }
103
+
104
+ // src/validator.ts
105
+ function validateGeneratedUI(code, dlc) {
106
+ const errors = [];
107
+ const warnings = [];
108
+ const viewportUnitPattern = /\b\d+\.?\d*(vw|vh|dvh|svh|lvh)\b/g;
109
+ const viewportMatches = code.match(viewportUnitPattern);
110
+ if (viewportMatches) {
111
+ errors.push(
112
+ `Viewport units detected: ${[...new Set(viewportMatches)].join(", ")}. Use CSS variables (var(--device-width)) or percentage values instead.`
113
+ );
114
+ }
115
+ const hardcodedDimPattern = new RegExp(
116
+ `(width|height)\\s*[:=]\\s*["']?${dlc.screen.width}(px)?["']?`
117
+ );
118
+ if (hardcodedDimPattern.test(code)) {
119
+ warnings.push(
120
+ `Root element may be setting explicit device dimensions (${dlc.screen.width}px). The container is already sized \u2014 avoid setting width/height on the root element.`
121
+ );
122
+ }
123
+ const hasSafeTopUsage = code.includes("--safe-top") || code.includes("safe-top");
124
+ const hasStatusBar = /status.?bar|StatusBar/i.test(code);
125
+ if (!hasSafeTopUsage && !hasStatusBar) {
126
+ warnings.push(
127
+ `No safe area top usage detected. Use var(--safe-top) or include a status bar to prevent content from going behind the ${dlc.hardwareOverlays.type === "dynamic-island" ? "Dynamic Island" : "camera cutout"}.`
128
+ );
129
+ }
130
+ const hasSafeBottomUsage = code.includes("--safe-bottom") || code.includes("safe-bottom");
131
+ const hasBottomNav = /tab.?bar|bottom.?nav|navigation.?bar/i.test(code);
132
+ if (!hasSafeBottomUsage && !hasBottomNav) {
133
+ warnings.push(
134
+ `No safe area bottom usage detected. Use var(--safe-bottom) for bottom padding to avoid the ${dlc.homeIndicator.type === "swipe-bar" ? "home indicator" : "gesture navigation"} zone.`
135
+ );
136
+ }
137
+ const pxValuePattern = /(\d{3,})px/g;
138
+ let match;
139
+ while ((match = pxValuePattern.exec(code)) !== null) {
140
+ const value = parseInt(match[1], 10);
141
+ if (value > dlc.screen.width && value > dlc.screen.height) {
142
+ warnings.push(
143
+ `Large pixel value detected: ${value}px exceeds device dimensions (${dlc.screen.width}\xD7${dlc.screen.height}). This may cause overflow.`
144
+ );
145
+ }
146
+ }
147
+ if (!code.includes("GeneratedApp")) {
148
+ warnings.push(
149
+ `Component name "GeneratedApp" not found. Expected: export default function GeneratedApp()`
150
+ );
151
+ }
152
+ const importPattern = /import\s+.*\s+from\s+["']([^"']+)["']/g;
153
+ let importMatch;
154
+ while ((importMatch = importPattern.exec(code)) !== null) {
155
+ const pkg = importMatch[1];
156
+ if (pkg !== "react" && !pkg.startsWith("react/")) {
157
+ errors.push(
158
+ `External library import detected: "${pkg}". Only React imports are allowed.`
159
+ );
160
+ }
161
+ }
162
+ return {
163
+ valid: errors.length === 0,
164
+ warnings,
165
+ errors
166
+ };
167
+ }
168
+
169
+ // src/claude-client.ts
170
+ async function callClaude(params) {
171
+ const apiKey = params.apiKey || (typeof process !== "undefined" ? process.env?.CLAUDE_API_KEY : void 0);
172
+ if (!apiKey) {
173
+ throw new Error(
174
+ "Claude API key not provided. Pass it via claudeApiKey parameter or set the CLAUDE_API_KEY environment variable."
175
+ );
176
+ }
177
+ const response = await fetch("https://api.anthropic.com/v1/messages", {
178
+ method: "POST",
179
+ headers: {
180
+ "Content-Type": "application/json",
181
+ "x-api-key": apiKey,
182
+ "anthropic-version": "2023-06-01"
183
+ },
184
+ body: JSON.stringify({
185
+ model: params.model ?? "claude-sonnet-4-20250514",
186
+ max_tokens: params.maxTokens ?? 8192,
187
+ system: params.systemPrompt,
188
+ messages: params.messages
189
+ })
190
+ });
191
+ if (!response.ok) {
192
+ const errorBody = await response.text();
193
+ throw new Error(
194
+ `Claude API error (${response.status}): ${errorBody}`
195
+ );
196
+ }
197
+ const data = await response.json();
198
+ const text = data.content[0]?.text;
199
+ if (!text) {
200
+ throw new Error("Claude returned empty response");
201
+ }
202
+ return text;
203
+ }
204
+ function extractCode(response) {
205
+ const fencePattern = /```(?:jsx|tsx|javascript|typescript|react)?\s*\n([\s\S]*?)```/;
206
+ const match = fencePattern.exec(response);
207
+ if (match?.[1]) {
208
+ return match[1].trim();
209
+ }
210
+ return response.trim();
211
+ }
212
+
213
+ // src/enricher.ts
214
+ async function enrichAppDescription(rawPrompt, dlc, apiKey) {
215
+ const platform = dlc.device.platform === "ios" ? "iOS" : "Android";
216
+ const systemPrompt = `You are a mobile app specification writer. Given a brief app idea, expand it into a detailed 150-200 word specification suitable for a UI component generator.
217
+
218
+ Include:
219
+ - 3-5 key screens or sections to show (pick the most visually interesting ones)
220
+ - Navigation pattern appropriate for ${platform} (tab bar for iOS, bottom nav for Android)
221
+ - Specific data types and realistic content examples (names, numbers, dates)
222
+ - 2-3 key interactions (tap, swipe, toggle, scroll)
223
+ - Color palette suggestion that fits the app theme
224
+
225
+ The specification is for a SINGLE React component that simulates an app screen at ${dlc.screen.width}\xD7${dlc.screen.height}pt.
226
+
227
+ Return ONLY the specification text \u2014 no preamble, no markdown headers, just the description.`;
228
+ const response = await callClaude({
229
+ systemPrompt,
230
+ messages: [{ role: "user", content: rawPrompt }],
231
+ apiKey,
232
+ maxTokens: 500
233
+ });
234
+ return response.trim();
235
+ }
236
+ async function generateMobileUI(params) {
237
+ const {
238
+ deviceId,
239
+ appDescription,
240
+ orientation = "portrait",
241
+ claudeApiKey
242
+ } = params;
243
+ const contract = devices.getDeviceContract(deviceId, orientation);
244
+ let enrichedDescription;
245
+ try {
246
+ enrichedDescription = await enrichAppDescription(
247
+ appDescription,
248
+ contract,
249
+ claudeApiKey
250
+ );
251
+ } catch {
252
+ enrichedDescription = appDescription;
253
+ }
254
+ const prompt = serializeDLCForAI(contract, enrichedDescription);
255
+ const response = await callClaude({
256
+ systemPrompt: prompt,
257
+ messages: [
258
+ {
259
+ role: "user",
260
+ content: `Generate the mobile UI component now. Return ONLY the React component code.`
261
+ }
262
+ ],
263
+ apiKey: claudeApiKey,
264
+ maxTokens: 8192
265
+ });
266
+ const code = extractCode(response);
267
+ const validation = validateGeneratedUI(code, contract);
268
+ if (validation.errors.length > 0) {
269
+ console.warn("[BielaFrame AI Bridge] Validation errors:", validation.errors);
270
+ }
271
+ if (validation.warnings.length > 0) {
272
+ console.warn("[BielaFrame AI Bridge] Validation warnings:", validation.warnings);
273
+ }
274
+ return {
275
+ code,
276
+ contract,
277
+ metadata: {
278
+ device: deviceId,
279
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
280
+ promptUsed: prompt,
281
+ enrichedDescription
282
+ }
283
+ };
284
+ }
285
+ async function refineUI(params) {
286
+ const { previousCode, contract, refinementInstruction, claudeApiKey } = params;
287
+ const systemPrompt = `You are refining a React mobile UI component. The component runs inside a ${contract.screen.width}\xD7${contract.screen.height}pt container for ${contract.device.name} (${contract.device.platform}).
288
+
289
+ ALL the original constraints still apply:
290
+ - Use Tailwind CSS utility classes
291
+ - Respect safe areas: top ${contract.safeArea.portrait.top}pt, bottom ${contract.safeArea.portrait.bottom}pt
292
+ - No external libraries \u2014 React only
293
+ - No viewport units (vw, vh)
294
+ - Component name must remain: GeneratedApp
295
+
296
+ Return TWO things separated by "---CHANGES---":
297
+ 1. The complete updated component code (not a diff \u2014 the full code)
298
+ 2. A brief summary of what changed (1-3 sentences)`;
299
+ const response = await callClaude({
300
+ systemPrompt,
301
+ messages: [
302
+ {
303
+ role: "user",
304
+ content: `Here is the current component:
305
+
306
+ ${previousCode}
307
+
308
+ Refinement instruction: ${refinementInstruction}`
309
+ }
310
+ ],
311
+ apiKey: claudeApiKey,
312
+ maxTokens: 8192
313
+ });
314
+ const separatorIndex = response.indexOf("---CHANGES---");
315
+ let code;
316
+ let changesSummary;
317
+ if (separatorIndex !== -1) {
318
+ code = extractCode(response.slice(0, separatorIndex));
319
+ changesSummary = response.slice(separatorIndex + "---CHANGES---".length).trim();
320
+ } else {
321
+ code = extractCode(response);
322
+ changesSummary = "Component updated per refinement instruction.";
323
+ }
324
+ return { code, changesSummary };
325
+ }
326
+
327
+ // src/prompt-session.ts
328
+ function createPromptSession(initialDlc, appDescription) {
329
+ let dlc = initialDlc;
330
+ let description = appDescription;
331
+ let turnCount = 0;
332
+ let forceFullNext = false;
333
+ const history = [];
334
+ return {
335
+ getInjection(opts) {
336
+ const isFirst = turnCount === 0 || forceFullNext;
337
+ let injection;
338
+ let type;
339
+ if (isFirst) {
340
+ injection = serializeDLCForAI(dlc, description);
341
+ type = "full";
342
+ forceFullNext = false;
343
+ } else {
344
+ injection = serializeCompactDLC(dlc, opts?.validation);
345
+ type = "compact";
346
+ }
347
+ turnCount++;
348
+ history.push({
349
+ turn: turnCount,
350
+ type,
351
+ deviceId: dlc.device.id,
352
+ injection,
353
+ timestamp: Date.now()
354
+ });
355
+ return injection;
356
+ },
357
+ getTurnCount() {
358
+ return turnCount;
359
+ },
360
+ isFirstTurn() {
361
+ return turnCount === 0 || forceFullNext;
362
+ },
363
+ switchDevice(newDlc) {
364
+ dlc = newDlc;
365
+ turnCount = 0;
366
+ forceFullNext = false;
367
+ },
368
+ getContract() {
369
+ return dlc;
370
+ },
371
+ resetToFull() {
372
+ forceFullNext = true;
373
+ },
374
+ getHistory() {
375
+ return [...history];
376
+ }
377
+ };
378
+ }
379
+
380
+ // src/injection-settings.ts
381
+ function createDefaultSettings() {
382
+ return {
383
+ modes: {
384
+ sdk: true,
385
+ api: false,
386
+ builtinGeneration: false
387
+ },
388
+ prompt: {
389
+ extraConstraints: [],
390
+ suppressedConstraints: [],
391
+ descriptionPrefix: "",
392
+ descriptionSuffix: "",
393
+ designLanguageOverride: null,
394
+ outputRequirementsOverride: null
395
+ },
396
+ validation: {
397
+ strictMode: false,
398
+ suppressedWarnings: [],
399
+ autoFeedback: true
400
+ },
401
+ apiServer: {
402
+ port: 3100,
403
+ host: "localhost"
404
+ }
405
+ };
406
+ }
407
+ function applySettingsToPrompt(prompt, settings) {
408
+ let result = prompt;
409
+ for (const suppression of settings.prompt.suppressedConstraints) {
410
+ if (!suppression.trim()) continue;
411
+ const lines = result.split("\n");
412
+ result = lines.filter((line) => !line.includes(suppression)).join("\n");
413
+ }
414
+ if (settings.prompt.extraConstraints.length > 0) {
415
+ const marker = "\u2501\u2501\u2501 APP TO BUILD \u2501\u2501\u2501";
416
+ const idx = result.indexOf(marker);
417
+ if (idx !== -1) {
418
+ const extras = settings.prompt.extraConstraints.filter((c) => c.trim()).map((c) => `- ${c}`).join("\n");
419
+ result = result.slice(0, idx) + `\u2501\u2501\u2501 CUSTOM CONSTRAINTS \u2501\u2501\u2501
420
+ ${extras}
421
+
422
+ ` + result.slice(idx);
423
+ }
424
+ }
425
+ if (settings.prompt.descriptionPrefix || settings.prompt.descriptionSuffix) {
426
+ const appMarker = "\u2501\u2501\u2501 APP TO BUILD \u2501\u2501\u2501\n";
427
+ const appIdx = result.indexOf(appMarker);
428
+ if (appIdx !== -1) {
429
+ const afterMarker = appIdx + appMarker.length;
430
+ const nextSection = result.indexOf("\n\n\u2501\u2501\u2501", afterMarker);
431
+ const appDesc = result.slice(afterMarker, nextSection !== -1 ? nextSection : void 0);
432
+ const prefix = settings.prompt.descriptionPrefix ? settings.prompt.descriptionPrefix + "\n" : "";
433
+ const suffix = settings.prompt.descriptionSuffix ? "\n" + settings.prompt.descriptionSuffix : "";
434
+ const newDesc = prefix + appDesc + suffix;
435
+ result = result.slice(0, afterMarker) + newDesc + (nextSection !== -1 ? result.slice(nextSection) : "");
436
+ }
437
+ }
438
+ return result;
439
+ }
440
+ function serializeSettings(settings) {
441
+ return JSON.stringify(settings, null, 2);
442
+ }
443
+ function deserializeSettings(json) {
444
+ const defaults = createDefaultSettings();
445
+ try {
446
+ const parsed = JSON.parse(json);
447
+ return {
448
+ modes: { ...defaults.modes, ...parsed.modes },
449
+ prompt: { ...defaults.prompt, ...parsed.prompt },
450
+ validation: { ...defaults.validation, ...parsed.validation },
451
+ apiServer: { ...defaults.apiServer, ...parsed.apiServer }
452
+ };
453
+ } catch {
454
+ return defaults;
455
+ }
456
+ }
457
+
458
+ exports.applySettingsToPrompt = applySettingsToPrompt;
459
+ exports.createDefaultSettings = createDefaultSettings;
460
+ exports.createPromptSession = createPromptSession;
461
+ exports.deserializeSettings = deserializeSettings;
462
+ exports.enrichAppDescription = enrichAppDescription;
463
+ exports.generateMobileUI = generateMobileUI;
464
+ exports.refineUI = refineUI;
465
+ exports.serializeCompactDLC = serializeCompactDLC;
466
+ exports.serializeDLCForAI = serializeDLCForAI;
467
+ exports.serializeSettings = serializeSettings;
468
+ exports.validateGeneratedUI = validateGeneratedUI;
469
+ //# sourceMappingURL=index.cjs.map
470
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/serializer.ts","../src/compact-serializer.ts","../src/validator.ts","../src/claude-client.ts","../src/enricher.ts","../src/generate.ts","../src/prompt-session.ts","../src/injection-settings.ts"],"names":["getDeviceContract"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { DeviceLayoutContract } from \"@biela.dev/devices\";\n\n/**\n * Serialize a Device Layout Contract into a complete AI instruction prompt.\n *\n * This is the core of the AI Bridge — it converts structured device data\n * into precise natural language instructions that Claude can follow to\n * generate pixel-perfect mobile UI components.\n */\nexport function serializeDLCForAI(\n dlc: DeviceLayoutContract,\n appDescription: string,\n): string {\n const { device, screen, safeArea, hardwareOverlays, statusBar, homeIndicator, contentZone, cssVariables } = dlc;\n const sa = safeArea.portrait;\n const cz = contentZone.portrait;\n\n const isIOS = device.platform === \"ios\";\n const platformName = isIOS ? \"iOS\" : \"Android\";\n const designLanguage = isIOS ? \"iOS 18 design language (SF Pro, iOS navigation patterns, native tab bars, iOS sheets)\" : \"Material You / Android 15 design language (Roboto Flex, Material 3, bottom navigation bar, Material sheets)\";\n\n // Build CSS variables section\n const cssVarLines = Object.entries(cssVariables).map(\n ([key, value]) => ` ${key}: ${value}`,\n );\n\n // Build hardware overlay description\n let overlayDesc = \"\";\n if (hardwareOverlays.type === \"dynamic-island\") {\n const ov = hardwareOverlays.portrait;\n overlayDesc = `Dynamic Island (pill shape): ${ov.width}×${ov.height}pt centered at y=${ov.y}. Do NOT place interactive content behind the Dynamic Island. The status bar area (${statusBar.height}pt) contains the clock and signal indicators split around the island.`;\n } else if (hardwareOverlays.type === \"punch-hole\") {\n const ov = hardwareOverlays.portrait;\n overlayDesc = `Punch-hole camera (circle): ${ov.width}pt diameter centered at top of screen. The status bar area (${statusBar.height}pt) contains the clock, signal, and battery indicators. Avoid placing critical content directly behind the camera cutout.`;\n } else if (hardwareOverlays.type === \"notch\") {\n overlayDesc = `Notch at top center. The status bar flanks the notch with clock and indicators.`;\n }\n\n // Build home indicator description\n let homeDesc = \"\";\n if (homeIndicator.type === \"swipe-bar\") {\n homeDesc = `Home indicator: swipe bar (${homeIndicator.width}×${homeIndicator.height}pt) at the bottom. Reserve ${sa.bottom}pt at the bottom for the home indicator area. Do not place interactive buttons in this zone.`;\n } else if (homeIndicator.type === \"gestureline\") {\n homeDesc = `Gesture navigation bar: thin line (${homeIndicator.width}×${homeIndicator.height}pt) at the bottom. Reserve ${sa.bottom}pt at the bottom for the navigation bar. Do not place interactive buttons in this zone.`;\n }\n\n return `You are generating a React mobile UI component. You MUST respect ALL constraints below exactly.\n\nDEVICE: ${device.name} (${platformName}, ${device.year})\nSCREEN SIZE: ${screen.width}×${screen.height} logical points (CSS pixels)\n\n━━━ RENDERING CONTEXT ━━━\nYour component renders inside a container that is ALREADY sized to exactly ${screen.width}×${screen.height}px.\n- Do NOT set width or height on your root element — the container handles sizing.\n- Do NOT use viewport units (vw, vh, dvh, svh) — they reference the browser viewport, not the device screen.\n- Use 100% width/height, CSS variables, or fixed pixel values instead.\n- The container has overflow: hidden and clips to the screen shape (${screen.cornerRadius}px corner radius).\n- Your component always renders at 1:1 device resolution. The visual presentation is scaled via CSS transform — you never need to think about scaling.\n\n━━━ CSS VARIABLES (already injected — use var() directly) ━━━\n${cssVarLines.join(\"\\n\")}\n\n━━━ LAYOUT CONSTRAINTS (NON-NEGOTIABLE) ━━━\nUsable content zone: x=${cz.x}, y=${cz.y}, width=${cz.width}pt, height=${cz.height}pt\nSafe area insets (portrait): top ${sa.top}pt, bottom ${sa.bottom}pt, left ${sa.left}pt, right ${sa.right}pt\nCorner radius: ${screen.cornerRadius}pt (already applied to container clip — but round inner elements near edges to match)\n${overlayDesc ? `\\n${overlayDesc}` : \"\"}\n${homeDesc ? `\\n${homeDesc}` : \"\"}\n\n━━━ APP TO BUILD ━━━\n${appDescription}\n\n━━━ OUTPUT REQUIREMENTS ━━━\n1. Return a SINGLE self-contained React functional component as the default export.\n2. Use Tailwind CSS utility classes for all styling.\n3. Include a realistic status bar row at the top (time, signal, battery icons) that respects the ${statusBar.height}pt status bar height. Use var(--safe-top) for the top padding.\n4. The UI must feel native to ${designLanguage}.\n5. Include realistic dummy data — no Lorem Ipsum, no placeholder text. Use genuine-looking names, dates, numbers.\n6. Make it interactive where appropriate (useState for toggles, tabs, modals, forms, navigation).\n7. Do NOT import any external libraries — React only (hooks: useState, useEffect, useRef, useMemo, useCallback).\n8. Component name: GeneratedApp (export default function GeneratedApp()).\n9. The component must handle its own scroll internally if content exceeds the screen height — use overflow-y-auto on a scrollable container.\n10. Use var(--safe-bottom) for bottom padding to avoid the home indicator zone.\n11. All text must be legible — minimum 12px font size, proper contrast ratios.\n12. Return ONLY the component code — no markdown fences, no explanations, just the JSX.`;\n}\n","import type { DeviceLayoutContract } from \"@biela.dev/devices\";\nimport type { ValidationResult } from \"./types\";\n\n/**\n * Compact DLC summary for refinement prompts (prompt #2+).\n *\n * Instead of re-sending the full ~2000-token DLC prompt on every turn,\n * this produces a short reminder (~200 tokens) with just the device ID,\n * dimensions, and any validation errors/warnings from the previous render.\n */\nexport function serializeCompactDLC(\n dlc: DeviceLayoutContract,\n validation?: ValidationResult,\n): string {\n const { device, screen, safeArea, contentZone } = dlc;\n const sa = safeArea.portrait;\n const cz = contentZone.portrait;\n const platform = device.platform === \"ios\" ? \"iOS\" : \"Android\";\n\n let prompt = `[BielaFrame] Target: ${device.name} (${platform}, ${screen.width}×${screen.height}pt)\nSafe areas: top ${sa.top}pt, bottom ${sa.bottom}pt | Content zone: ${cz.width}×${cz.height}pt\nCorner radius: ${screen.cornerRadius}pt | All original constraints still apply.`;\n\n if (validation && (validation.errors.length > 0 || validation.warnings.length > 0)) {\n prompt += \"\\n\\n[BielaFrame Validation Feedback]\";\n\n if (validation.errors.length > 0) {\n prompt += \"\\nERRORS (must fix):\";\n for (const err of validation.errors) {\n prompt += `\\n - ${err}`;\n }\n }\n\n if (validation.warnings.length > 0) {\n prompt += \"\\nWARNINGS (should fix):\";\n for (const warn of validation.warnings) {\n prompt += `\\n - ${warn}`;\n }\n }\n }\n\n return prompt;\n}\n","import type { DeviceLayoutContract } from \"@biela.dev/devices\";\nimport type { ValidationResult } from \"./types\";\n\n/**\n * Validate generated UI code against a Device Layout Contract.\n *\n * Performs static analysis on the code string to catch common constraint\n * violations before rendering. This is a best-effort check — it catches\n * obvious issues but can't guarantee pixel-perfect compliance.\n */\nexport function validateGeneratedUI(\n code: string,\n dlc: DeviceLayoutContract,\n): ValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // Check for viewport unit usage (vw, vh, dvh, svh, lvh)\n const viewportUnitPattern = /\\b\\d+\\.?\\d*(vw|vh|dvh|svh|lvh)\\b/g;\n const viewportMatches = code.match(viewportUnitPattern);\n if (viewportMatches) {\n errors.push(\n `Viewport units detected: ${[...new Set(viewportMatches)].join(\", \")}. ` +\n `Use CSS variables (var(--device-width)) or percentage values instead.`,\n );\n }\n\n // Check for root element setting explicit width/height (common mistake)\n // Look for patterns like: style={{ width: \"430px\", height: \"956px\" }}\n const hardcodedDimPattern = new RegExp(\n `(width|height)\\\\s*[:=]\\\\s*[\"']?${dlc.screen.width}(px)?[\"']?`,\n );\n if (hardcodedDimPattern.test(code)) {\n warnings.push(\n `Root element may be setting explicit device dimensions (${dlc.screen.width}px). ` +\n `The container is already sized — avoid setting width/height on the root element.`,\n );\n }\n\n // Check for safe-top usage when content touches top\n const hasSafeTopUsage = code.includes(\"--safe-top\") || code.includes(\"safe-top\");\n const hasStatusBar = /status.?bar|StatusBar/i.test(code);\n if (!hasSafeTopUsage && !hasStatusBar) {\n warnings.push(\n `No safe area top usage detected. Use var(--safe-top) or include a status bar ` +\n `to prevent content from going behind the ${dlc.hardwareOverlays.type === \"dynamic-island\" ? \"Dynamic Island\" : \"camera cutout\"}.`,\n );\n }\n\n // Check for safe-bottom usage\n const hasSafeBottomUsage = code.includes(\"--safe-bottom\") || code.includes(\"safe-bottom\");\n const hasBottomNav = /tab.?bar|bottom.?nav|navigation.?bar/i.test(code);\n if (!hasSafeBottomUsage && !hasBottomNav) {\n warnings.push(\n `No safe area bottom usage detected. Use var(--safe-bottom) for bottom padding ` +\n `to avoid the ${dlc.homeIndicator.type === \"swipe-bar\" ? \"home indicator\" : \"gesture navigation\"} zone.`,\n );\n }\n\n // Check for hardcoded pixel values exceeding device dimensions\n const pxValuePattern = /(\\d{3,})px/g;\n let match;\n while ((match = pxValuePattern.exec(code)) !== null) {\n const value = parseInt(match[1]!, 10);\n if (value > dlc.screen.width && value > dlc.screen.height) {\n warnings.push(\n `Large pixel value detected: ${value}px exceeds device dimensions ` +\n `(${dlc.screen.width}×${dlc.screen.height}). This may cause overflow.`,\n );\n }\n }\n\n // Check that component name is GeneratedApp\n if (!code.includes(\"GeneratedApp\")) {\n warnings.push(\n `Component name \"GeneratedApp\" not found. Expected: export default function GeneratedApp()`,\n );\n }\n\n // Check for external library imports\n const importPattern = /import\\s+.*\\s+from\\s+[\"']([^\"']+)[\"']/g;\n let importMatch;\n while ((importMatch = importPattern.exec(code)) !== null) {\n const pkg = importMatch[1]!;\n if (pkg !== \"react\" && !pkg.startsWith(\"react/\")) {\n errors.push(\n `External library import detected: \"${pkg}\". Only React imports are allowed.`,\n );\n }\n }\n\n return {\n valid: errors.length === 0,\n warnings,\n errors,\n };\n}\n","/**\n * Minimal Claude API client for AI Bridge.\n *\n * Uses the Messages API directly via fetch — no SDK dependency.\n * The API key comes from either the function parameter or CLAUDE_API_KEY env var.\n */\n\ninterface ClaudeMessage {\n role: \"user\" | \"assistant\";\n content: string;\n}\n\ninterface ClaudeResponse {\n content: Array<{ type: \"text\"; text: string }>;\n usage: { input_tokens: number; output_tokens: number };\n}\n\nexport async function callClaude(params: {\n systemPrompt: string;\n messages: ClaudeMessage[];\n apiKey?: string;\n maxTokens?: number;\n model?: string;\n}): Promise<string> {\n const apiKey = params.apiKey || (typeof process !== \"undefined\" ? process.env?.CLAUDE_API_KEY : undefined);\n\n if (!apiKey) {\n throw new Error(\n \"Claude API key not provided. Pass it via claudeApiKey parameter \" +\n \"or set the CLAUDE_API_KEY environment variable.\",\n );\n }\n\n const response = await fetch(\"https://api.anthropic.com/v1/messages\", {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": apiKey,\n \"anthropic-version\": \"2023-06-01\",\n },\n body: JSON.stringify({\n model: params.model ?? \"claude-sonnet-4-20250514\",\n max_tokens: params.maxTokens ?? 8192,\n system: params.systemPrompt,\n messages: params.messages,\n }),\n });\n\n if (!response.ok) {\n const errorBody = await response.text();\n throw new Error(\n `Claude API error (${response.status}): ${errorBody}`,\n );\n }\n\n const data = (await response.json()) as ClaudeResponse;\n const text = data.content[0]?.text;\n\n if (!text) {\n throw new Error(\"Claude returned empty response\");\n }\n\n return text;\n}\n\n/**\n * Extract code from a Claude response that may contain markdown fences.\n */\nexport function extractCode(response: string): string {\n // Try to extract from ```jsx or ```tsx or ``` blocks\n const fencePattern = /```(?:jsx|tsx|javascript|typescript|react)?\\s*\\n([\\s\\S]*?)```/;\n const match = fencePattern.exec(response);\n if (match?.[1]) {\n return match[1].trim();\n }\n\n // If no fences found, assume the whole response is code\n return response.trim();\n}\n","import type { DeviceLayoutContract } from \"@biela.dev/devices\";\nimport { callClaude } from \"./claude-client\";\n\n/**\n * Enrich a short app description into a detailed feature specification.\n *\n * Takes a brief prompt like \"a banking app\" and returns a 150-200 word\n * structured specification including screens, navigation patterns,\n * data types, and interactions appropriate for the target platform.\n */\nexport async function enrichAppDescription(\n rawPrompt: string,\n dlc: DeviceLayoutContract,\n apiKey?: string,\n): Promise<string> {\n const platform = dlc.device.platform === \"ios\" ? \"iOS\" : \"Android\";\n\n const systemPrompt = `You are a mobile app specification writer. Given a brief app idea, expand it into a detailed 150-200 word specification suitable for a UI component generator.\n\nInclude:\n- 3-5 key screens or sections to show (pick the most visually interesting ones)\n- Navigation pattern appropriate for ${platform} (tab bar for iOS, bottom nav for Android)\n- Specific data types and realistic content examples (names, numbers, dates)\n- 2-3 key interactions (tap, swipe, toggle, scroll)\n- Color palette suggestion that fits the app theme\n\nThe specification is for a SINGLE React component that simulates an app screen at ${dlc.screen.width}×${dlc.screen.height}pt.\n\nReturn ONLY the specification text — no preamble, no markdown headers, just the description.`;\n\n const response = await callClaude({\n systemPrompt,\n messages: [{ role: \"user\", content: rawPrompt }],\n apiKey,\n maxTokens: 500,\n });\n\n return response.trim();\n}\n","import { getDeviceContract } from \"@biela.dev/devices\";\nimport { serializeDLCForAI } from \"./serializer\";\nimport { enrichAppDescription } from \"./enricher\";\nimport { validateGeneratedUI } from \"./validator\";\nimport { callClaude, extractCode } from \"./claude-client\";\nimport type { GenerateMobileUIParams, GenerationResult, RefineUIParams, RefinementResult } from \"./types\";\n\n/**\n * Generate a mobile UI component for a specific device.\n *\n * Full pipeline: device lookup → DLC retrieval → description enrichment →\n * prompt serialization → Claude API call → code extraction → validation.\n */\nexport async function generateMobileUI(\n params: GenerateMobileUIParams,\n): Promise<GenerationResult> {\n const {\n deviceId,\n appDescription,\n orientation = \"portrait\",\n claudeApiKey,\n } = params;\n\n // 1. Get the Device Layout Contract\n const contract = getDeviceContract(deviceId, orientation);\n\n // 2. Enrich the app description\n let enrichedDescription: string;\n try {\n enrichedDescription = await enrichAppDescription(\n appDescription,\n contract,\n claudeApiKey,\n );\n } catch {\n // If enrichment fails (e.g., no API key), use the raw description\n enrichedDescription = appDescription;\n }\n\n // 3. Serialize the DLC into a prompt\n const prompt = serializeDLCForAI(contract, enrichedDescription);\n\n // 4. Call Claude to generate the UI\n const response = await callClaude({\n systemPrompt: prompt,\n messages: [\n {\n role: \"user\",\n content: `Generate the mobile UI component now. Return ONLY the React component code.`,\n },\n ],\n apiKey: claudeApiKey,\n maxTokens: 8192,\n });\n\n // 5. Extract code from response\n const code = extractCode(response);\n\n // 6. Validate (log warnings but don't fail)\n const validation = validateGeneratedUI(code, contract);\n if (validation.errors.length > 0) {\n console.warn(\"[BielaFrame AI Bridge] Validation errors:\", validation.errors);\n }\n if (validation.warnings.length > 0) {\n console.warn(\"[BielaFrame AI Bridge] Validation warnings:\", validation.warnings);\n }\n\n return {\n code,\n contract,\n metadata: {\n device: deviceId,\n generatedAt: new Date().toISOString(),\n promptUsed: prompt,\n enrichedDescription,\n },\n };\n}\n\n/**\n * Refine a previously generated UI component.\n *\n * Enables iterative improvements without re-running the full generation\n * pipeline. Sends the previous code + refinement instruction to Claude.\n */\nexport async function refineUI(\n params: RefineUIParams,\n): Promise<RefinementResult> {\n const { previousCode, contract, refinementInstruction, claudeApiKey } = params;\n\n const systemPrompt = `You are refining a React mobile UI component. The component runs inside a ${contract.screen.width}×${contract.screen.height}pt container for ${contract.device.name} (${contract.device.platform}).\n\nALL the original constraints still apply:\n- Use Tailwind CSS utility classes\n- Respect safe areas: top ${contract.safeArea.portrait.top}pt, bottom ${contract.safeArea.portrait.bottom}pt\n- No external libraries — React only\n- No viewport units (vw, vh)\n- Component name must remain: GeneratedApp\n\nReturn TWO things separated by \"---CHANGES---\":\n1. The complete updated component code (not a diff — the full code)\n2. A brief summary of what changed (1-3 sentences)`;\n\n const response = await callClaude({\n systemPrompt,\n messages: [\n {\n role: \"user\",\n content: `Here is the current component:\\n\\n${previousCode}\\n\\nRefinement instruction: ${refinementInstruction}`,\n },\n ],\n apiKey: claudeApiKey,\n maxTokens: 8192,\n });\n\n // Split response on separator\n const separatorIndex = response.indexOf(\"---CHANGES---\");\n let code: string;\n let changesSummary: string;\n\n if (separatorIndex !== -1) {\n code = extractCode(response.slice(0, separatorIndex));\n changesSummary = response.slice(separatorIndex + \"---CHANGES---\".length).trim();\n } else {\n // If no separator, treat entire response as code\n code = extractCode(response);\n changesSummary = \"Component updated per refinement instruction.\";\n }\n\n return { code, changesSummary };\n}\n","import type { DeviceLayoutContract } from \"@biela.dev/devices\";\nimport type { ValidationResult } from \"./types\";\nimport { serializeDLCForAI } from \"./serializer\";\nimport { serializeCompactDLC } from \"./compact-serializer\";\n\n/**\n * Tracks prompt session state to determine whether to send the full DLC\n * injection (first prompt) or a compact summary (subsequent prompts).\n *\n * Usage:\n * const session = createPromptSession(dlc, \"a banking app\");\n * session.getInjection(); // → full DLC prompt (first time)\n * session.getInjection(); // → compact summary (second time onward)\n * session.getInjection({ validation }); // → compact + validation feedback\n * session.switchDevice(newDlc); // → resets to full prompt for new device\n */\nexport interface PromptSession {\n /** Get the appropriate injection for the current turn. */\n getInjection(opts?: { validation?: ValidationResult }): string;\n /** How many prompts have been sent in this session. */\n getTurnCount(): number;\n /** Whether this is the first prompt (full DLC will be sent). */\n isFirstTurn(): boolean;\n /** Switch to a different device — resets the session so the next injection is full. */\n switchDevice(dlc: DeviceLayoutContract): void;\n /** Get the current device contract. */\n getContract(): DeviceLayoutContract;\n /** Force the next injection to be a full DLC prompt (useful after settings changes). */\n resetToFull(): void;\n /** Get history of all injections sent. */\n getHistory(): PromptHistoryEntry[];\n}\n\nexport interface PromptHistoryEntry {\n turn: number;\n type: \"full\" | \"compact\";\n deviceId: string;\n injection: string;\n timestamp: number;\n}\n\nexport function createPromptSession(\n initialDlc: DeviceLayoutContract,\n appDescription: string,\n): PromptSession {\n let dlc = initialDlc;\n let description = appDescription;\n let turnCount = 0;\n let forceFullNext = false;\n const history: PromptHistoryEntry[] = [];\n\n return {\n getInjection(opts) {\n const isFirst = turnCount === 0 || forceFullNext;\n let injection: string;\n let type: \"full\" | \"compact\";\n\n if (isFirst) {\n injection = serializeDLCForAI(dlc, description);\n type = \"full\";\n forceFullNext = false;\n } else {\n injection = serializeCompactDLC(dlc, opts?.validation);\n type = \"compact\";\n }\n\n turnCount++;\n history.push({\n turn: turnCount,\n type,\n deviceId: dlc.device.id,\n injection,\n timestamp: Date.now(),\n });\n\n return injection;\n },\n\n getTurnCount() {\n return turnCount;\n },\n\n isFirstTurn() {\n return turnCount === 0 || forceFullNext;\n },\n\n switchDevice(newDlc) {\n dlc = newDlc;\n turnCount = 0;\n forceFullNext = false;\n },\n\n getContract() {\n return dlc;\n },\n\n resetToFull() {\n forceFullNext = true;\n },\n\n getHistory() {\n return [...history];\n },\n };\n}\n","/**\n * Injection settings — configurable overrides for the DLC prompt injection.\n *\n * Lets users customize what gets injected, toggle integration modes,\n * and add custom constraints or remove default ones.\n */\nexport interface InjectionSettings {\n /** Integration modes — toggle on/off */\n modes: {\n /** NPM SDK mode: serializeDLCForAI() + validateGeneratedUI() used as imports */\n sdk: boolean;\n /** API mode: Studio exposes DLC over local HTTP/WebSocket */\n api: boolean;\n /** Built-in generation: generateMobileUI() calls Claude directly */\n builtinGeneration: boolean;\n };\n\n /** Prompt customization */\n prompt: {\n /** Additional constraints to append to the DLC prompt */\n extraConstraints: string[];\n /** Constraints to suppress from the default DLC prompt (matched by substring) */\n suppressedConstraints: string[];\n /** Custom app description prefix (prepended to user's description) */\n descriptionPrefix: string;\n /** Custom app description suffix (appended to user's description) */\n descriptionSuffix: string;\n /** Override the design language instruction */\n designLanguageOverride: string | null;\n /** Override the output requirements section entirely */\n outputRequirementsOverride: string[] | null;\n };\n\n /** Validation settings */\n validation: {\n /** Treat warnings as errors */\n strictMode: boolean;\n /** Disable specific warning checks by ID */\n suppressedWarnings: string[];\n /** Auto-inject validation feedback into refinement prompts */\n autoFeedback: boolean;\n };\n\n /** API server settings (when api mode is enabled) */\n apiServer: {\n port: number;\n host: string;\n };\n}\n\nexport function createDefaultSettings(): InjectionSettings {\n return {\n modes: {\n sdk: true,\n api: false,\n builtinGeneration: false,\n },\n prompt: {\n extraConstraints: [],\n suppressedConstraints: [],\n descriptionPrefix: \"\",\n descriptionSuffix: \"\",\n designLanguageOverride: null,\n outputRequirementsOverride: null,\n },\n validation: {\n strictMode: false,\n suppressedWarnings: [],\n autoFeedback: true,\n },\n apiServer: {\n port: 3100,\n host: \"localhost\",\n },\n };\n}\n\n/**\n * Apply injection settings to modify a generated prompt.\n * Processes suppressions and additions.\n */\nexport function applySettingsToPrompt(\n prompt: string,\n settings: InjectionSettings,\n): string {\n let result = prompt;\n\n // Apply suppressions — remove lines containing suppressed substrings\n for (const suppression of settings.prompt.suppressedConstraints) {\n if (!suppression.trim()) continue;\n const lines = result.split(\"\\n\");\n result = lines.filter((line) => !line.includes(suppression)).join(\"\\n\");\n }\n\n // Apply extra constraints — insert before \"━━━ APP TO BUILD ━━━\"\n if (settings.prompt.extraConstraints.length > 0) {\n const marker = \"━━━ APP TO BUILD ━━━\";\n const idx = result.indexOf(marker);\n if (idx !== -1) {\n const extras = settings.prompt.extraConstraints\n .filter((c) => c.trim())\n .map((c) => `- ${c}`)\n .join(\"\\n\");\n result =\n result.slice(0, idx) +\n `━━━ CUSTOM CONSTRAINTS ━━━\\n${extras}\\n\\n` +\n result.slice(idx);\n }\n }\n\n // Apply description prefix/suffix\n if (settings.prompt.descriptionPrefix || settings.prompt.descriptionSuffix) {\n const appMarker = \"━━━ APP TO BUILD ━━━\\n\";\n const appIdx = result.indexOf(appMarker);\n if (appIdx !== -1) {\n const afterMarker = appIdx + appMarker.length;\n const nextSection = result.indexOf(\"\\n\\n━━━\", afterMarker);\n const appDesc = result.slice(afterMarker, nextSection !== -1 ? nextSection : undefined);\n\n const prefix = settings.prompt.descriptionPrefix ? settings.prompt.descriptionPrefix + \"\\n\" : \"\";\n const suffix = settings.prompt.descriptionSuffix ? \"\\n\" + settings.prompt.descriptionSuffix : \"\";\n const newDesc = prefix + appDesc + suffix;\n\n result = result.slice(0, afterMarker) + newDesc + (nextSection !== -1 ? result.slice(nextSection) : \"\");\n }\n }\n\n return result;\n}\n\n/**\n * Serialize settings to JSON for persistence (localStorage, file, etc.)\n */\nexport function serializeSettings(settings: InjectionSettings): string {\n return JSON.stringify(settings, null, 2);\n}\n\n/**\n * Deserialize settings from JSON, merging with defaults for missing fields.\n */\nexport function deserializeSettings(json: string): InjectionSettings {\n const defaults = createDefaultSettings();\n try {\n const parsed = JSON.parse(json) as Partial<InjectionSettings>;\n return {\n modes: { ...defaults.modes, ...parsed.modes },\n prompt: { ...defaults.prompt, ...parsed.prompt },\n validation: { ...defaults.validation, ...parsed.validation },\n apiServer: { ...defaults.apiServer, ...parsed.apiServer },\n };\n } catch {\n return defaults;\n }\n}\n"]}