@bitovi/vybit 0.11.7 → 0.12.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/README.md +3 -2
- package/overlay/dist/overlay.js +5480 -2569
- package/package.json +4 -1
- package/panel/dist/assets/{DesignMode-Yfq3lye8.js → DesignMode-MoLVGcFD.js} +15 -15
- package/panel/dist/assets/index-By_WWRF6.css +1 -0
- package/panel/dist/assets/index-C4LjGBfX.js +63 -0
- package/panel/dist/index.html +2 -2
- package/server/mcp-tools.ts +114 -3
- package/server/queue.ts +38 -0
- package/server/storybook.ts +4 -1
- package/server/websocket.ts +11 -1
- package/shared/types.ts +251 -3
- package/panel/dist/assets/index-0eiAXBf9.js +0 -62
- package/panel/dist/assets/index-BWp0wXqx.css +0 -1
package/panel/dist/index.html
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
font-size: 12px;
|
|
17
17
|
}
|
|
18
18
|
</style>
|
|
19
|
-
<script type="module" crossorigin src="/panel/assets/index-
|
|
20
|
-
<link rel="stylesheet" crossorigin href="/panel/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/panel/assets/index-C4LjGBfX.js"></script>
|
|
20
|
+
<link rel="stylesheet" crossorigin href="/panel/assets/index-By_WWRF6.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="root"></div>
|
package/server/mcp-tools.ts
CHANGED
|
@@ -55,9 +55,11 @@ function buildJsx(componentName: string, args?: Record<string, unknown>): string
|
|
|
55
55
|
|
|
56
56
|
function buildCommitInstructions(commit: Commit, remainingCount: number): string {
|
|
57
57
|
const classChanges = commit.patches.filter(p => p.kind === 'class-change');
|
|
58
|
+
const textChanges = commit.patches.filter(p => p.kind === 'text-change');
|
|
58
59
|
const messages = commit.patches.filter(p => p.kind === 'message');
|
|
59
60
|
const designs = commit.patches.filter(p => p.kind === 'design');
|
|
60
61
|
const componentDrops = commit.patches.filter(p => p.kind === 'component-drop');
|
|
62
|
+
const bugReports = commit.patches.filter(p => p.kind === 'bug-report');
|
|
61
63
|
const moreText = remainingCount > 0
|
|
62
64
|
? `${remainingCount} more commit${remainingCount === 1 ? '' : 's'} waiting in the queue after this one.`
|
|
63
65
|
: 'This is the last commit in the queue. After implementing it, call `implement_next_change` again to wait for future changes.';
|
|
@@ -149,6 +151,91 @@ ${parentComp ? `\n- **Parent component:** \`${parentComp}\` — edit this compon
|
|
|
149
151
|
${context ? `- **Context HTML:**\n\`\`\`html\n${context}\n\`\`\`\n` : ''}
|
|
150
152
|
⚠️ Do NOT paste rendered HTML. Import and render the React component with the props shown above.
|
|
151
153
|
|
|
154
|
+
`;
|
|
155
|
+
} else if (patch.kind === 'text-change') {
|
|
156
|
+
const comp = patch.component?.name ?? 'unknown component';
|
|
157
|
+
const tag = patch.target?.tag ?? 'element';
|
|
158
|
+
const context = patch.context ?? '';
|
|
159
|
+
patchList += `### ${stepNum}. Text change \`${patch.id}\`
|
|
160
|
+
- **Component:** \`${comp}\`
|
|
161
|
+
- **Element:** \`<${tag}>\`
|
|
162
|
+
- **Original HTML:**
|
|
163
|
+
\`\`\`html
|
|
164
|
+
${patch.originalHtml ?? ''}
|
|
165
|
+
\`\`\`
|
|
166
|
+
- **New HTML:**
|
|
167
|
+
\`\`\`html
|
|
168
|
+
${patch.newHtml ?? ''}
|
|
169
|
+
\`\`\`
|
|
170
|
+
${context ? `- **Context HTML:**\n\`\`\`html\n${context}\n\`\`\`\n` : ''}
|
|
171
|
+
`;
|
|
172
|
+
} else if (patch.kind === 'bug-report') {
|
|
173
|
+
patchList += `### ${stepNum}. Bug report \`${patch.id}\`
|
|
174
|
+
- **Description:** ${patch.bugDescription ?? '(no description)'}
|
|
175
|
+
- **Time range:** ${patch.bugTimeRange ? `${patch.bugTimeRange.start} – ${patch.bugTimeRange.end}` : 'unknown'}
|
|
176
|
+
${patch.bugElement ? `
|
|
177
|
+
- **Related element:** \`${patch.bugElement.selectorPath}\`${patch.bugElement.componentName ? ` (in \`${patch.bugElement.componentName}\`)` : ''}
|
|
178
|
+
- **Element HTML:**
|
|
179
|
+
\`\`\`html
|
|
180
|
+
${patch.bugElement.outerHTML.slice(0, 10000)}
|
|
181
|
+
\`\`\`
|
|
182
|
+
` : ''}
|
|
183
|
+
${patch.bugTimeline && patch.bugTimeline.length > 0 ? (() => {
|
|
184
|
+
const triggerLabel = (t: import('../shared/types').BugTimelineEntry) => {
|
|
185
|
+
switch (t.trigger) {
|
|
186
|
+
case 'click': return `Click${t.elementInfo ? ` on \`<${t.elementInfo.tag}${t.elementInfo.classes ? ` class="${t.elementInfo.classes}"` : ''}>\`` : ''}`;
|
|
187
|
+
case 'mutation': return 'DOM mutation';
|
|
188
|
+
case 'error': return 'Error';
|
|
189
|
+
case 'navigation': return `Navigation${t.navigationInfo ? ` (${t.navigationInfo.method}: ${t.navigationInfo.from} → ${t.navigationInfo.to ?? 'unknown'})` : ''}`;
|
|
190
|
+
case 'page-load': return 'Page load';
|
|
191
|
+
default: return t.trigger;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
let screenshotNum = 0;
|
|
195
|
+
let timeline = `**Timeline** (${patch.bugTimeline!.length} events):\n\n`;
|
|
196
|
+
for (let i = 0; i < patch.bugTimeline!.length; i++) {
|
|
197
|
+
const entry = patch.bugTimeline![i];
|
|
198
|
+
const time = entry.timestamp.replace(/.*T/, '').replace(/Z$/, '');
|
|
199
|
+
timeline += `#### ${i + 1}. [${time}] ${triggerLabel(entry)}\n`;
|
|
200
|
+
timeline += `**URL:** ${entry.url}\n`;
|
|
201
|
+
if (entry.hasScreenshot) {
|
|
202
|
+
screenshotNum++;
|
|
203
|
+
timeline += `📸 **Screenshot ${screenshotNum}** (see attached image ${screenshotNum} below)\n`;
|
|
204
|
+
}
|
|
205
|
+
if (entry.consoleLogs && entry.consoleLogs.length > 0) {
|
|
206
|
+
timeline += `\n**Console (${entry.consoleLogs.length}):**\n\`\`\`\n${entry.consoleLogs.map(l => `[${l.level.toUpperCase()}] ${l.args.join(' ')}${l.stack ? `\n${l.stack}` : ''}`).join('\n').slice(0, 3000)}\n\`\`\`\n`;
|
|
207
|
+
}
|
|
208
|
+
if (entry.networkErrors && entry.networkErrors.length > 0) {
|
|
209
|
+
timeline += `\n**Network errors (${entry.networkErrors.length}):**\n${entry.networkErrors.map(e => `- \`${e.status ?? 'ERR'} ${e.method} ${e.url}\`${e.errorMessage ? ` — ${e.errorMessage}` : ''}`).join('\n')}\n`;
|
|
210
|
+
}
|
|
211
|
+
if (entry.domChanges && entry.domChanges.length > 0) {
|
|
212
|
+
timeline += `\n**DOM changes (${entry.domChanges.length}):**\n`;
|
|
213
|
+
for (const c of entry.domChanges) {
|
|
214
|
+
const loc = `\`${c.selector}\`${c.componentName ? ` (in \`${c.componentName}\`)` : ''}`;
|
|
215
|
+
if (c.type === 'attribute') {
|
|
216
|
+
timeline += `- ${loc}: attribute \`${c.attributeName}\` changed: \`${c.oldValue ?? ''}\` → \`${c.newValue ?? ''}\`\n`;
|
|
217
|
+
} else if (c.type === 'text') {
|
|
218
|
+
timeline += `- ${loc}: text changed: "${c.oldText ?? ''}" → "${c.newText ?? ''}"\n`;
|
|
219
|
+
} else if (c.type === 'childList') {
|
|
220
|
+
const parts: string[] = [];
|
|
221
|
+
if (c.addedCount) parts.push(`${c.addedCount} added`);
|
|
222
|
+
if (c.removedCount) parts.push(`${c.removedCount} removed`);
|
|
223
|
+
timeline += `- ${loc}: children ${parts.join(', ')}`;
|
|
224
|
+
if (c.addedHTML) timeline += `\n Added: \`${c.addedHTML.slice(0, 300)}\``;
|
|
225
|
+
if (c.removedHTML) timeline += `\n Removed: \`${c.removedHTML.slice(0, 300)}\``;
|
|
226
|
+
timeline += `\n`;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
} else if (entry.domDiff) {
|
|
230
|
+
timeline += `\n**DOM diff:**\n\`\`\`diff\n${entry.domDiff.slice(0, 10000)}\n\`\`\`\n`;
|
|
231
|
+
}
|
|
232
|
+
if (entry.domSnapshot && i === 0) {
|
|
233
|
+
timeline += `\n**Initial DOM state:**\n\`\`\`html\n${entry.domSnapshot.slice(0, 50000)}\n\`\`\`\n`;
|
|
234
|
+
}
|
|
235
|
+
timeline += `\n---\n\n`;
|
|
236
|
+
}
|
|
237
|
+
return timeline;
|
|
238
|
+
})() : ''}
|
|
152
239
|
`;
|
|
153
240
|
}
|
|
154
241
|
stepNum++;
|
|
@@ -157,22 +244,29 @@ ${context ? `- **Context HTML:**\n\`\`\`html\n${context}\n\`\`\`\n` : ''}
|
|
|
157
244
|
// Build summary parts
|
|
158
245
|
const summaryParts: string[] = [];
|
|
159
246
|
if (classChanges.length) summaryParts.push(`${classChanges.length} class change${classChanges.length === 1 ? '' : 's'}`);
|
|
247
|
+
if (textChanges.length) summaryParts.push(`${textChanges.length} text change${textChanges.length === 1 ? '' : 's'}`);
|
|
160
248
|
if (messages.length) summaryParts.push(`${messages.length} message${messages.length === 1 ? '' : 's'}`);
|
|
161
249
|
if (designs.length) summaryParts.push(`${designs.length} design${designs.length === 1 ? '' : 's'}`);
|
|
162
250
|
if (componentDrops.length) summaryParts.push(`${componentDrops.length} component drop${componentDrops.length === 1 ? '' : 's'}`);
|
|
251
|
+
if (bugReports.length) summaryParts.push(`${bugReports.length} bug report${bugReports.length === 1 ? '' : 's'}`);
|
|
163
252
|
|
|
164
253
|
const resultsPart = classChanges.map(p => ` { "patchId": "${p.id}", "success": true }`).join(',\n');
|
|
254
|
+
const textResultsPart = textChanges.map(p => ` { "patchId": "${p.id}", "success": true }`).join(',\n');
|
|
165
255
|
const designResultsPart = designs.map(p => ` { "patchId": "${p.id}", "success": true }`).join(',\n');
|
|
166
256
|
const dropResultsPart = componentDrops.map(p => ` { "patchId": "${p.id}", "success": true }`).join(',\n');
|
|
167
|
-
const
|
|
257
|
+
const bugResultsPart = bugReports.map(p => ` { "patchId": "${p.id}", "success": true }`).join(',\n');
|
|
258
|
+
const allResultsPart = [resultsPart, textResultsPart, designResultsPart, dropResultsPart, bugResultsPart].filter(Boolean).join(',\n');
|
|
168
259
|
|
|
169
260
|
// Build step instructions
|
|
170
261
|
const stepInstructions: string[] = [];
|
|
171
|
-
if (classChanges.length || componentDrops.length) {
|
|
262
|
+
if (classChanges.length || componentDrops.length || textChanges.length) {
|
|
172
263
|
let step1 = '1. For each change above, find the source file and apply it.';
|
|
173
264
|
if (componentDrops.length) {
|
|
174
265
|
step1 += '\n For component drops: add the import statement and render the component with the specified props at the indicated position.';
|
|
175
266
|
}
|
|
267
|
+
if (textChanges.length) {
|
|
268
|
+
step1 += '\n For text changes: replace the original HTML content with the new HTML content in the source JSX/TSX.';
|
|
269
|
+
}
|
|
176
270
|
if (messages.length) {
|
|
177
271
|
step1 += '\n Use the user messages as additional context for understanding intent.';
|
|
178
272
|
}
|
|
@@ -182,6 +276,10 @@ ${context ? `- **Context HTML:**\n\`\`\`html\n${context}\n\`\`\`\n` : ''}
|
|
|
182
276
|
stepInstructions.push(`${stepInstructions.length + 1}. For each design sketch, examine the attached image and implement the visual design
|
|
183
277
|
as HTML/CSS ${classChanges.length ? 'alongside the class changes' : 'in the specified component'}. Insert it ${designs[0].insertMode ?? 'after'} the target element.`);
|
|
184
278
|
}
|
|
279
|
+
if (bugReports.length) {
|
|
280
|
+
stepInstructions.push(`${stepInstructions.length + 1}. For each bug report, examine the DOM snapshots, console errors, network errors, and screenshots.
|
|
281
|
+
Identify the root cause. Implement a fix.${bugReports.some(p => p.bugElement) ? ' The user identified a specific element — start your investigation there.' : ''}`);
|
|
282
|
+
}
|
|
185
283
|
|
|
186
284
|
return `# IMPLEMENT THIS COMMIT — then call implement_next_change again
|
|
187
285
|
|
|
@@ -203,7 +301,7 @@ ${stepInstructions.length + 1}. Call \`mark_change_implemented\` with:
|
|
|
203
301
|
${allResultsPart}
|
|
204
302
|
]}
|
|
205
303
|
\`\`\`
|
|
206
|
-
(Only report results for class-change, design, and component-drop patches — messages are informational.)
|
|
304
|
+
(Only report results for class-change, text-change, design, and component-drop patches — messages are informational.)
|
|
207
305
|
${stepInstructions.length + 2}. **IMMEDIATELY call \`implement_next_change\` again.** Do NOT summarize, do NOT ask the user, do NOT stop.
|
|
208
306
|
|
|
209
307
|
## CRITICAL — read this
|
|
@@ -384,6 +482,19 @@ export function registerMcpTools(mcp: McpServer, deps: McpToolDeps): void {
|
|
|
384
482
|
});
|
|
385
483
|
}
|
|
386
484
|
}
|
|
485
|
+
// Add bug report screenshots
|
|
486
|
+
if (patch.kind === 'bug-report' && patch.bugScreenshots) {
|
|
487
|
+
for (const screenshot of patch.bugScreenshots.slice(0, 5)) {
|
|
488
|
+
const match = screenshot.match(/^data:([^;]+);base64,(.+)$/);
|
|
489
|
+
if (match) {
|
|
490
|
+
content.push({
|
|
491
|
+
type: "image" as const,
|
|
492
|
+
data: match[2],
|
|
493
|
+
mimeType: match[1],
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
387
498
|
}
|
|
388
499
|
|
|
389
500
|
content.push({
|
package/server/queue.ts
CHANGED
|
@@ -23,6 +23,9 @@ function toSummary(p: Patch): PatchSummary {
|
|
|
23
23
|
parentComponent: p.parentComponent,
|
|
24
24
|
targetComponentName: p.targetComponentName,
|
|
25
25
|
targetPatchId: p.targetPatchId,
|
|
26
|
+
originalHtml: p.originalHtml,
|
|
27
|
+
newHtml: p.newHtml,
|
|
28
|
+
bugDescription: p.bugDescription,
|
|
26
29
|
};
|
|
27
30
|
}
|
|
28
31
|
|
|
@@ -56,12 +59,35 @@ export function addPatch(patch: Patch): Patch {
|
|
|
56
59
|
if (existingIdx !== -1) {
|
|
57
60
|
draftPatches.splice(existingIdx, 1);
|
|
58
61
|
}
|
|
62
|
+
} else if (patch.kind === 'text-change') {
|
|
63
|
+
// Dedup: if a staged text-change exists for the same elementKey, replace it
|
|
64
|
+
const existingIdx = draftPatches.findIndex(
|
|
65
|
+
p => p.kind === 'text-change' && p.elementKey === patch.elementKey && p.status === 'staged'
|
|
66
|
+
);
|
|
67
|
+
if (existingIdx !== -1) {
|
|
68
|
+
draftPatches.splice(existingIdx, 1);
|
|
69
|
+
}
|
|
59
70
|
}
|
|
60
71
|
// Message patches are always appended (no dedup)
|
|
61
72
|
draftPatches.push(patch);
|
|
62
73
|
return patch;
|
|
63
74
|
}
|
|
64
75
|
|
|
76
|
+
/** Immediately commit a single patch as its own commit (skips the draft). */
|
|
77
|
+
export function addAndCommit(patch: Patch): Commit {
|
|
78
|
+
patch.status = 'committed';
|
|
79
|
+
const commit: Commit = {
|
|
80
|
+
id: crypto.randomUUID(),
|
|
81
|
+
patches: [patch],
|
|
82
|
+
status: 'committed',
|
|
83
|
+
timestamp: new Date().toISOString(),
|
|
84
|
+
};
|
|
85
|
+
patch.commitId = commit.id;
|
|
86
|
+
commits.push(commit);
|
|
87
|
+
emitter.emit('committed');
|
|
88
|
+
return commit;
|
|
89
|
+
}
|
|
90
|
+
|
|
65
91
|
export function commitDraft(ids: string[]): Commit {
|
|
66
92
|
const idSet = new Set(ids);
|
|
67
93
|
const commitPatches: Patch[] = [];
|
|
@@ -291,6 +317,18 @@ export function discardDraftPatch(id: string): boolean {
|
|
|
291
317
|
return remaining.length < before;
|
|
292
318
|
}
|
|
293
319
|
|
|
320
|
+
/**
|
|
321
|
+
* Discard a committed (but not yet implementing) commit.
|
|
322
|
+
* Returns true if the commit was found and removed.
|
|
323
|
+
* Safe against race conditions: only removes if status is still 'committed'.
|
|
324
|
+
*/
|
|
325
|
+
export function discardCommit(commitId: string): boolean {
|
|
326
|
+
const idx = commits.findIndex(c => c.id === commitId && c.status === 'committed');
|
|
327
|
+
if (idx === -1) return false;
|
|
328
|
+
commits.splice(idx, 1);
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
|
|
294
332
|
export function clearAll(): { staged: number; committed: number; implementing: number; implemented: number } {
|
|
295
333
|
const counts = getCounts();
|
|
296
334
|
draftPatches.length = 0;
|
package/server/storybook.ts
CHANGED
|
@@ -80,7 +80,10 @@ export async function loadStoryArgTypes(
|
|
|
80
80
|
*/
|
|
81
81
|
export async function detectStorybookUrl(): Promise<string | null> {
|
|
82
82
|
if (process.env.STORYBOOK_URL) {
|
|
83
|
-
|
|
83
|
+
if (await probeStorybookUrl(process.env.STORYBOOK_URL)) {
|
|
84
|
+
return process.env.STORYBOOK_URL;
|
|
85
|
+
}
|
|
86
|
+
// Env var URL is not reachable — fall through to port scan
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
for (const port of SCAN_PORTS) {
|
package/server/websocket.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { WebSocketServer, type WebSocket } from "ws";
|
|
4
4
|
import type { Server } from "http";
|
|
5
5
|
|
|
6
|
-
import { addPatch, commitDraft, getQueueUpdate, discardDraftPatch } from "./queue.js";
|
|
6
|
+
import { addPatch, addAndCommit, commitDraft, getQueueUpdate, discardDraftPatch, discardCommit } from "./queue.js";
|
|
7
7
|
import type { Patch } from "../shared/types.js";
|
|
8
8
|
|
|
9
9
|
export interface WebSocketDeps {
|
|
@@ -88,6 +88,12 @@ export function setupWebSocket(httpServer: Server): WebSocketDeps {
|
|
|
88
88
|
}
|
|
89
89
|
console.error(`[ws] Discarded ${ids.length} draft patch(es)`);
|
|
90
90
|
broadcastPatchUpdate();
|
|
91
|
+
} else if (msg.type === "DISCARD_COMMIT") {
|
|
92
|
+
const commitId: string = msg.commitId;
|
|
93
|
+
if (commitId && discardCommit(commitId)) {
|
|
94
|
+
console.error(`[ws] Discarded committed commit: ${commitId}`);
|
|
95
|
+
broadcastPatchUpdate();
|
|
96
|
+
}
|
|
91
97
|
} else if (msg.type === "PING") {
|
|
92
98
|
ws.send(JSON.stringify({ type: "PONG" }));
|
|
93
99
|
} else if (msg.type === "DESIGN_SUBMIT") {
|
|
@@ -126,6 +132,10 @@ export function setupWebSocket(httpServer: Server): WebSocketDeps {
|
|
|
126
132
|
const patch = addPatch({ ...msg.patch, kind: msg.patch.kind ?? 'component-drop' });
|
|
127
133
|
console.error(`[ws] Component-drop patch staged: #${patch.id}`);
|
|
128
134
|
broadcastPatchUpdate();
|
|
135
|
+
} else if (msg.type === "BUG_REPORT_STAGE") {
|
|
136
|
+
const commit = addAndCommit({ ...msg.patch, kind: 'bug-report' });
|
|
137
|
+
console.error(`[ws] Bug-report auto-committed: commit #${commit.id}`);
|
|
138
|
+
broadcastPatchUpdate();
|
|
129
139
|
}
|
|
130
140
|
} catch (err) {
|
|
131
141
|
console.error("[ws] Bad message:", err);
|
package/shared/types.ts
CHANGED
|
@@ -28,7 +28,7 @@ export interface CanvasComponent {
|
|
|
28
28
|
height: number;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export type PatchKind = 'class-change' | 'message' | 'design' | 'component-drop';
|
|
31
|
+
export type PatchKind = 'class-change' | 'message' | 'design' | 'component-drop' | 'text-change' | 'bug-report';
|
|
32
32
|
|
|
33
33
|
export type PatchStatus = 'staged' | 'committed' | 'implementing' | 'implemented' | 'error';
|
|
34
34
|
|
|
@@ -60,10 +60,19 @@ export interface Patch {
|
|
|
60
60
|
ghostHtml?: string; // HTML of the dropped component (overlay preview only — stripped from MCP response)
|
|
61
61
|
componentStoryId?: string; // Storybook story ID
|
|
62
62
|
componentPath?: string; // Source file of the component, e.g. './src/components/Button.tsx'
|
|
63
|
+
// Text-change fields (used when kind === 'text-change'):
|
|
64
|
+
originalHtml?: string; // HTML before text edit
|
|
65
|
+
newHtml?: string; // HTML after text edit
|
|
63
66
|
componentArgs?: Record<string, unknown>; // Props the user configured before dropping
|
|
64
67
|
parentComponent?: { name: string }; // React component that contains the drop target
|
|
65
68
|
targetPatchId?: string; // If target is a ghost from an earlier drop, references that patch
|
|
66
69
|
targetComponentName?: string; // Name of the ghost component being referenced
|
|
70
|
+
// Bug-report fields (used when kind === 'bug-report'):
|
|
71
|
+
bugDescription?: string;
|
|
72
|
+
bugScreenshots?: string[];
|
|
73
|
+
bugTimeline?: BugTimelineEntry[];
|
|
74
|
+
bugTimeRange?: { start: string; end: string };
|
|
75
|
+
bugElement?: BugReportElement | null;
|
|
67
76
|
// Commit reference:
|
|
68
77
|
commitId?: string; // Set when committed into a Commit
|
|
69
78
|
}
|
|
@@ -97,6 +106,11 @@ export interface PatchSummary {
|
|
|
97
106
|
parentComponent?: { name: string };
|
|
98
107
|
targetComponentName?: string;
|
|
99
108
|
targetPatchId?: string;
|
|
109
|
+
// Text-change display fields:
|
|
110
|
+
originalHtml?: string;
|
|
111
|
+
newHtml?: string;
|
|
112
|
+
// Bug-report display fields:
|
|
113
|
+
bugDescription?: string;
|
|
100
114
|
}
|
|
101
115
|
|
|
102
116
|
export interface CommitSummary {
|
|
@@ -343,6 +357,7 @@ export interface ComponentArmMessage {
|
|
|
343
357
|
ghostHtml: string;
|
|
344
358
|
componentPath?: string; // Source file path from Storybook index, e.g. './src/components/Button.tsx'
|
|
345
359
|
args?: Record<string, unknown>; // Current prop values from ArgsForm
|
|
360
|
+
insertMode?: 'replace'; // When 'replace', arms element-select if no element is selected
|
|
346
361
|
}
|
|
347
362
|
|
|
348
363
|
/** Panel → Overlay: user cancelled the armed state (panel click or escape) */
|
|
@@ -357,6 +372,41 @@ export interface ComponentDisarmedMessage {
|
|
|
357
372
|
to: 'panel';
|
|
358
373
|
}
|
|
359
374
|
|
|
375
|
+
// ---------------------------------------------------------------------------
|
|
376
|
+
// Mode sync messages
|
|
377
|
+
// ---------------------------------------------------------------------------
|
|
378
|
+
|
|
379
|
+
export type AppMode = 'select' | 'insert' | 'bug-report' | null;
|
|
380
|
+
export type SelectTab = 'design' | 'replace';
|
|
381
|
+
export type InsertTab = 'place';
|
|
382
|
+
export type PanelTab = SelectTab | InsertTab;
|
|
383
|
+
|
|
384
|
+
/** Bidirectional: panel ↔ overlay mode change */
|
|
385
|
+
export interface ModeChangedMessage {
|
|
386
|
+
type: 'MODE_CHANGED';
|
|
387
|
+
to: 'overlay' | 'panel';
|
|
388
|
+
mode: AppMode;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** Bidirectional: panel ↔ overlay tab change */
|
|
392
|
+
export interface TabChangedMessage {
|
|
393
|
+
type: 'TAB_CHANGED';
|
|
394
|
+
to: 'overlay' | 'panel';
|
|
395
|
+
tab: PanelTab;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** Overlay → Panel: text editing started on an element */
|
|
399
|
+
export interface TextEditActiveMessage {
|
|
400
|
+
type: 'TEXT_EDIT_ACTIVE';
|
|
401
|
+
to: 'panel';
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/** Overlay → Panel: text editing ended */
|
|
405
|
+
export interface TextEditDoneMessage {
|
|
406
|
+
type: 'TEXT_EDIT_DONE';
|
|
407
|
+
to: 'panel';
|
|
408
|
+
}
|
|
409
|
+
|
|
360
410
|
/** Overlay → Server: component was placed, stage a patch */
|
|
361
411
|
export interface ComponentDroppedMessage {
|
|
362
412
|
type: 'COMPONENT_DROPPED';
|
|
@@ -379,7 +429,9 @@ export type PanelToOverlay =
|
|
|
379
429
|
| CaptureScreenshotMessage
|
|
380
430
|
| ClosePanelMessage
|
|
381
431
|
| ComponentArmMessage
|
|
382
|
-
| ComponentDisarmMessage
|
|
432
|
+
| ComponentDisarmMessage
|
|
433
|
+
| ModeChangedMessage
|
|
434
|
+
| TabChangedMessage;
|
|
383
435
|
export type OverlayToServer = PatchStagedMessage | ComponentDroppedMessage | ResetSelectionMessage;
|
|
384
436
|
export type PanelToServer = PatchCommitMessage | MessageStageMessage;
|
|
385
437
|
export type ClientToServer =
|
|
@@ -427,5 +479,201 @@ export type AnyMessage =
|
|
|
427
479
|
| ComponentDisarmedMessage
|
|
428
480
|
| ComponentDroppedMessage
|
|
429
481
|
| ResetSelectionMessage
|
|
482
|
+
| ModeChangedMessage
|
|
483
|
+
| TabChangedMessage
|
|
484
|
+
| TextEditActiveMessage
|
|
485
|
+
| TextEditDoneMessage
|
|
430
486
|
| PingMessage
|
|
431
|
-
| PongMessage
|
|
487
|
+
| PongMessage
|
|
488
|
+
| RecordingGetHistoryMessage
|
|
489
|
+
| RecordingHistoryMessage
|
|
490
|
+
| RecordingGetSnapshotMessage
|
|
491
|
+
| RecordingSnapshotMessage
|
|
492
|
+
| RecordingGetRangeMessage
|
|
493
|
+
| RecordingRangeMessage
|
|
494
|
+
| RecordingSnapshotMetaMessage
|
|
495
|
+
| BugReportPickElementMessage
|
|
496
|
+
| BugReportElementPickedMessage
|
|
497
|
+
| BugReportPickCancelledMessage
|
|
498
|
+
| BugReportStageMessage;
|
|
499
|
+
|
|
500
|
+
// ---------------------------------------------------------------------------
|
|
501
|
+
// Recording / Bug Report types
|
|
502
|
+
// ---------------------------------------------------------------------------
|
|
503
|
+
|
|
504
|
+
export interface ConsoleEntry {
|
|
505
|
+
level: 'log' | 'warn' | 'error' | 'info';
|
|
506
|
+
args: string[];
|
|
507
|
+
timestamp: string;
|
|
508
|
+
stack?: string;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
export interface NetworkError {
|
|
512
|
+
url: string;
|
|
513
|
+
method: string;
|
|
514
|
+
status?: number;
|
|
515
|
+
statusText?: string;
|
|
516
|
+
errorMessage?: string;
|
|
517
|
+
timestamp: string;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
export interface BugReportElement {
|
|
521
|
+
tag: string;
|
|
522
|
+
id?: string;
|
|
523
|
+
classes: string;
|
|
524
|
+
selectorPath: string;
|
|
525
|
+
componentName?: string;
|
|
526
|
+
outerHTML: string;
|
|
527
|
+
boundingBox: { x: number; y: number; width: number; height: number };
|
|
528
|
+
screenshot?: string;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export type SnapshotTrigger = 'mutation' | 'click' | 'error' | 'navigation' | 'page-load';
|
|
532
|
+
|
|
533
|
+
export interface NavigationInfo {
|
|
534
|
+
from: string;
|
|
535
|
+
to: string | null;
|
|
536
|
+
method: 'pushState' | 'replaceState' | 'popstate' | 'full-page';
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/** A structured description of a single DOM mutation */
|
|
540
|
+
export interface DomChange {
|
|
541
|
+
type: 'attribute' | 'text' | 'childList';
|
|
542
|
+
selector: string;
|
|
543
|
+
componentName?: string;
|
|
544
|
+
/** attribute changes */
|
|
545
|
+
attributeName?: string;
|
|
546
|
+
oldValue?: string;
|
|
547
|
+
newValue?: string;
|
|
548
|
+
/** text changes */
|
|
549
|
+
oldText?: string;
|
|
550
|
+
newText?: string;
|
|
551
|
+
/** childList changes */
|
|
552
|
+
addedCount?: number;
|
|
553
|
+
removedCount?: number;
|
|
554
|
+
addedHTML?: string;
|
|
555
|
+
removedHTML?: string;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/** A single chronological event in a bug report timeline */
|
|
559
|
+
export interface BugTimelineEntry {
|
|
560
|
+
timestamp: string;
|
|
561
|
+
trigger: SnapshotTrigger;
|
|
562
|
+
url: string;
|
|
563
|
+
consoleLogs?: ConsoleEntry[];
|
|
564
|
+
networkErrors?: NetworkError[];
|
|
565
|
+
domChanges?: DomChange[];
|
|
566
|
+
domSnapshot?: string;
|
|
567
|
+
domDiff?: string;
|
|
568
|
+
hasScreenshot?: boolean;
|
|
569
|
+
elementInfo?: { tag: string; classes: string; id?: string; innerText?: string; componentName?: string };
|
|
570
|
+
navigationInfo?: NavigationInfo;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export interface RecordingSnapshot {
|
|
574
|
+
id?: number;
|
|
575
|
+
timestamp: string;
|
|
576
|
+
trigger: SnapshotTrigger;
|
|
577
|
+
isKeyframe: boolean;
|
|
578
|
+
domSnapshot?: string;
|
|
579
|
+
domDiff?: string;
|
|
580
|
+
domChanges?: DomChange[];
|
|
581
|
+
screenshot?: string;
|
|
582
|
+
thumbnail?: string;
|
|
583
|
+
consoleLogs: ConsoleEntry[];
|
|
584
|
+
networkErrors: NetworkError[];
|
|
585
|
+
url: string;
|
|
586
|
+
scrollPosition: { x: number; y: number };
|
|
587
|
+
viewportSize: { width: number; height: number };
|
|
588
|
+
elementInfo?: { tag: string; classes: string; id?: string; innerText?: string; componentName?: string };
|
|
589
|
+
navigationInfo?: NavigationInfo;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
export interface SnapshotMeta {
|
|
593
|
+
id: number;
|
|
594
|
+
timestamp: string;
|
|
595
|
+
trigger: SnapshotTrigger;
|
|
596
|
+
isKeyframe: boolean;
|
|
597
|
+
thumbnail?: string;
|
|
598
|
+
elementInfo?: RecordingSnapshot['elementInfo'];
|
|
599
|
+
consoleErrorCount: number;
|
|
600
|
+
networkErrorCount: number;
|
|
601
|
+
url: string;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// ---------------------------------------------------------------------------
|
|
605
|
+
// Recording / Bug Report WebSocket messages
|
|
606
|
+
// ---------------------------------------------------------------------------
|
|
607
|
+
|
|
608
|
+
/** Panel → Overlay (via server relay): request recording history */
|
|
609
|
+
export interface RecordingGetHistoryMessage {
|
|
610
|
+
type: 'RECORDING_GET_HISTORY';
|
|
611
|
+
to: 'overlay';
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/** Overlay → Panel (via server relay): recording history response */
|
|
615
|
+
export interface RecordingHistoryMessage {
|
|
616
|
+
type: 'RECORDING_HISTORY';
|
|
617
|
+
to: 'panel';
|
|
618
|
+
snapshots: SnapshotMeta[];
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/** Panel → Overlay (via server relay): request full snapshot by ID */
|
|
622
|
+
export interface RecordingGetSnapshotMessage {
|
|
623
|
+
type: 'RECORDING_GET_SNAPSHOT';
|
|
624
|
+
to: 'overlay';
|
|
625
|
+
snapshotId: number;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/** Overlay → Panel (via server relay): full snapshot response */
|
|
629
|
+
export interface RecordingSnapshotMessage {
|
|
630
|
+
type: 'RECORDING_SNAPSHOT';
|
|
631
|
+
to: 'panel';
|
|
632
|
+
snapshot: RecordingSnapshot;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** Panel → Overlay (via server relay): request range of snapshots */
|
|
636
|
+
export interface RecordingGetRangeMessage {
|
|
637
|
+
type: 'RECORDING_GET_RANGE';
|
|
638
|
+
to: 'overlay';
|
|
639
|
+
ids: number[];
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** Overlay → Panel (via server relay): range of full snapshots */
|
|
643
|
+
export interface RecordingRangeMessage {
|
|
644
|
+
type: 'RECORDING_RANGE';
|
|
645
|
+
to: 'panel';
|
|
646
|
+
snapshots: RecordingSnapshot[];
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/** Overlay → Panel (via server relay): live push of new snapshot meta */
|
|
650
|
+
export interface RecordingSnapshotMetaMessage {
|
|
651
|
+
type: 'RECORDING_SNAPSHOT_META';
|
|
652
|
+
to: 'panel';
|
|
653
|
+
meta: SnapshotMeta;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/** Panel → Overlay: enter element pick mode for bug report */
|
|
657
|
+
export interface BugReportPickElementMessage {
|
|
658
|
+
type: 'BUG_REPORT_PICK_ELEMENT';
|
|
659
|
+
to: 'overlay';
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/** Overlay → Panel: element was picked for bug report */
|
|
663
|
+
export interface BugReportElementPickedMessage {
|
|
664
|
+
type: 'BUG_REPORT_ELEMENT_PICKED';
|
|
665
|
+
to: 'panel';
|
|
666
|
+
element: BugReportElement;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/** Overlay → Panel: pick mode was cancelled */
|
|
670
|
+
export interface BugReportPickCancelledMessage {
|
|
671
|
+
type: 'BUG_REPORT_PICK_CANCELLED';
|
|
672
|
+
to: 'panel';
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/** Panel → Server: stage a bug-report patch */
|
|
676
|
+
export interface BugReportStageMessage {
|
|
677
|
+
type: 'BUG_REPORT_STAGE';
|
|
678
|
+
patch: Patch;
|
|
679
|
+
}
|