@bitovi/vybit 0.4.4
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/LICENSE +21 -0
- package/README.md +155 -0
- package/loader.mjs +11 -0
- package/overlay/dist/.gitkeep +0 -0
- package/overlay/dist/overlay.js +1547 -0
- package/package.json +57 -0
- package/panel/dist/assets/index-BUKLf5aN.css +1 -0
- package/panel/dist/assets/index-Cr2RD_Gn.js +549 -0
- package/panel/dist/index.html +25 -0
- package/server/app.ts +117 -0
- package/server/index.ts +57 -0
- package/server/mcp-tools.ts +356 -0
- package/server/queue.ts +281 -0
- package/server/tailwind-adapter.ts +17 -0
- package/server/tailwind-v3.ts +159 -0
- package/server/tailwind-v4.ts +160 -0
- package/server/tailwind.ts +50 -0
- package/server/tests/server.integration.test.ts +698 -0
- package/server/websocket.ts +130 -0
- package/shared/types.ts +304 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// WebSocket server setup
|
|
2
|
+
|
|
3
|
+
import { WebSocketServer, type WebSocket } from "ws";
|
|
4
|
+
import type { Server } from "http";
|
|
5
|
+
|
|
6
|
+
import { addPatch, commitDraft, getQueueUpdate, discardDraftPatch } from "./queue.js";
|
|
7
|
+
import type { Patch } from "../shared/types.js";
|
|
8
|
+
|
|
9
|
+
export interface WebSocketDeps {
|
|
10
|
+
broadcastPatchUpdate: () => void;
|
|
11
|
+
broadcastTo: (role: string, data: object, exclude?: WebSocket) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function setupWebSocket(httpServer: Server): WebSocketDeps {
|
|
15
|
+
const wss = new WebSocketServer({ server: httpServer, maxPayload: 10 * 1024 * 1024 });
|
|
16
|
+
const clientRoles = new Map<WebSocket, string>();
|
|
17
|
+
|
|
18
|
+
function broadcastTo(role: string, data: object, exclude?: WebSocket): void {
|
|
19
|
+
const payload = JSON.stringify(data);
|
|
20
|
+
for (const [client, clientRole] of clientRoles) {
|
|
21
|
+
if (clientRole === role && client !== exclude && client.readyState === 1) {
|
|
22
|
+
client.send(payload);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function broadcastPatchUpdate(): void {
|
|
28
|
+
broadcastTo("panel", { type: "QUEUE_UPDATE", ...getQueueUpdate() });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
wss.on("connection", (ws: WebSocket) => {
|
|
32
|
+
console.error("[ws] Client connected");
|
|
33
|
+
|
|
34
|
+
ws.on("message", (raw) => {
|
|
35
|
+
try {
|
|
36
|
+
const msg = JSON.parse(String(raw));
|
|
37
|
+
|
|
38
|
+
if (msg.type === "REGISTER") {
|
|
39
|
+
const role = msg.role;
|
|
40
|
+
if (role === "overlay" || role === "panel" || role === "design") {
|
|
41
|
+
clientRoles.set(ws, role);
|
|
42
|
+
console.error(`[ws] Client registered as: ${role}`);
|
|
43
|
+
if (role === "panel") {
|
|
44
|
+
ws.send(JSON.stringify({ type: "QUEUE_UPDATE", ...getQueueUpdate() }));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Route messages with a "to" field to all clients of that role
|
|
51
|
+
if (msg.to) {
|
|
52
|
+
broadcastTo(msg.to, msg, ws);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Server-handled messages (no "to" field)
|
|
57
|
+
if (msg.type === "PATCH_STAGED") {
|
|
58
|
+
const patch = addPatch({ ...msg.patch, kind: msg.patch.kind ?? 'class-change' });
|
|
59
|
+
console.error(`[ws] Patch staged: #${patch.id}`);
|
|
60
|
+
broadcastPatchUpdate();
|
|
61
|
+
} else if (msg.type === "MESSAGE_STAGE") {
|
|
62
|
+
const patch = addPatch({
|
|
63
|
+
id: msg.id,
|
|
64
|
+
kind: 'message',
|
|
65
|
+
elementKey: msg.elementKey ?? '',
|
|
66
|
+
status: 'staged',
|
|
67
|
+
originalClass: '',
|
|
68
|
+
newClass: '',
|
|
69
|
+
property: '',
|
|
70
|
+
timestamp: new Date().toISOString(),
|
|
71
|
+
message: msg.message,
|
|
72
|
+
component: msg.component,
|
|
73
|
+
});
|
|
74
|
+
console.error(`[ws] Message patch staged: #${patch.id}`);
|
|
75
|
+
broadcastPatchUpdate();
|
|
76
|
+
} else if (msg.type === "PATCH_COMMIT") {
|
|
77
|
+
const commit = commitDraft(msg.ids);
|
|
78
|
+
console.error(`[ws] Commit created: #${commit.id} (${commit.patches.length} patches)`);
|
|
79
|
+
broadcastPatchUpdate();
|
|
80
|
+
} else if (msg.type === "DISCARD_DRAFTS") {
|
|
81
|
+
const ids: string[] = msg.ids ?? [];
|
|
82
|
+
for (const id of ids) {
|
|
83
|
+
discardDraftPatch(id);
|
|
84
|
+
}
|
|
85
|
+
console.error(`[ws] Discarded ${ids.length} draft patch(es)`);
|
|
86
|
+
broadcastPatchUpdate();
|
|
87
|
+
} else if (msg.type === "PING") {
|
|
88
|
+
ws.send(JSON.stringify({ type: "PONG" }));
|
|
89
|
+
} else if (msg.type === "DESIGN_SUBMIT") {
|
|
90
|
+
const patch: Patch = {
|
|
91
|
+
id: crypto.randomUUID(),
|
|
92
|
+
kind: 'design',
|
|
93
|
+
elementKey: `${msg.target?.tag ?? ''}.${(msg.target?.classes ?? '').split(' ')[0]}`,
|
|
94
|
+
status: 'staged',
|
|
95
|
+
originalClass: '',
|
|
96
|
+
newClass: '',
|
|
97
|
+
property: 'design',
|
|
98
|
+
timestamp: new Date().toISOString(),
|
|
99
|
+
component: msg.componentName ? { name: msg.componentName } : undefined,
|
|
100
|
+
target: msg.target,
|
|
101
|
+
context: msg.context,
|
|
102
|
+
image: msg.image,
|
|
103
|
+
insertMode: msg.insertMode,
|
|
104
|
+
canvasWidth: msg.canvasWidth,
|
|
105
|
+
canvasHeight: msg.canvasHeight,
|
|
106
|
+
};
|
|
107
|
+
addPatch(patch);
|
|
108
|
+
broadcastPatchUpdate();
|
|
109
|
+
// Tell the overlay to replace the canvas with a static preview
|
|
110
|
+
broadcastTo("overlay", {
|
|
111
|
+
type: "DESIGN_SUBMITTED",
|
|
112
|
+
image: msg.image,
|
|
113
|
+
}, ws);
|
|
114
|
+
console.error(`[ws] Design patch staged: ${patch.id}`);
|
|
115
|
+
} else if (msg.type === "DESIGN_CLOSE") {
|
|
116
|
+
broadcastTo("overlay", { type: "DESIGN_CLOSE" }, ws);
|
|
117
|
+
}
|
|
118
|
+
} catch (err) {
|
|
119
|
+
console.error("[ws] Bad message:", err);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
ws.on("close", () => {
|
|
124
|
+
clientRoles.delete(ws);
|
|
125
|
+
console.error("[ws] Client disconnected");
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return { broadcastPatchUpdate, broadcastTo };
|
|
130
|
+
}
|
package/shared/types.ts
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
// Shared types for the PATCH protocol.
|
|
2
|
+
// Imported by overlay (esbuild), panel (Vite), and server (tsx).
|
|
3
|
+
|
|
4
|
+
export type ContainerName = 'modal' | 'popover' | 'sidebar' | 'popup';
|
|
5
|
+
|
|
6
|
+
export type PatchKind = 'class-change' | 'message' | 'design';
|
|
7
|
+
|
|
8
|
+
export type PatchStatus = 'staged' | 'committed' | 'implementing' | 'implemented' | 'error';
|
|
9
|
+
|
|
10
|
+
export interface Patch {
|
|
11
|
+
id: string; // UUID
|
|
12
|
+
kind: PatchKind; // discriminator
|
|
13
|
+
elementKey: string; // stable identifier (empty string for general messages)
|
|
14
|
+
status: PatchStatus;
|
|
15
|
+
// Class-change fields (used when kind === 'class-change'):
|
|
16
|
+
originalClass: string; // classToken before edit ('' if adding new)
|
|
17
|
+
newClass: string; // classToken after edit
|
|
18
|
+
property: string; // prefix of the change
|
|
19
|
+
timestamp: string; // ISO 8601
|
|
20
|
+
// Populated at stage time by the overlay (has DOM access):
|
|
21
|
+
pageUrl?: string; // URL of the inspected page
|
|
22
|
+
component?: { name: string; instanceCount?: number };
|
|
23
|
+
target?: { tag: string; classes: string; innerText: string };
|
|
24
|
+
context?: string;
|
|
25
|
+
errorMessage?: string;
|
|
26
|
+
// Message field (used when kind === 'message'):
|
|
27
|
+
message?: string; // free-form text
|
|
28
|
+
// Design fields (used when kind === 'design'):
|
|
29
|
+
image?: string; // data URL of the canvas snapshot
|
|
30
|
+
insertMode?: string; // before | after | first-child | last-child
|
|
31
|
+
canvasWidth?: number;
|
|
32
|
+
canvasHeight?: number;
|
|
33
|
+
// Commit reference:
|
|
34
|
+
commitId?: string; // Set when committed into a Commit
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type CommitStatus = 'staged' | 'committed' | 'implementing' | 'implemented' | 'partial' | 'error';
|
|
38
|
+
|
|
39
|
+
export interface Commit {
|
|
40
|
+
id: string; // UUID
|
|
41
|
+
patches: Patch[]; // Ordered: class-changes AND messages interleaved
|
|
42
|
+
status: CommitStatus;
|
|
43
|
+
timestamp: string; // ISO 8601 — set when committed
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Lightweight patch info for UI display (omits context/target for smaller WS payloads) */
|
|
47
|
+
export interface PatchSummary {
|
|
48
|
+
id: string;
|
|
49
|
+
kind: PatchKind;
|
|
50
|
+
elementKey: string;
|
|
51
|
+
status: PatchStatus;
|
|
52
|
+
originalClass: string;
|
|
53
|
+
newClass: string;
|
|
54
|
+
property: string;
|
|
55
|
+
timestamp: string;
|
|
56
|
+
component?: { name: string; instanceCount?: number };
|
|
57
|
+
errorMessage?: string;
|
|
58
|
+
message?: string;
|
|
59
|
+
image?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface CommitSummary {
|
|
63
|
+
id: string;
|
|
64
|
+
status: CommitStatus;
|
|
65
|
+
timestamp: string;
|
|
66
|
+
patches: PatchSummary[]; // ordered — class-changes and messages interleaved
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// WebSocket messages
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
// Kept unchanged
|
|
74
|
+
export interface RegisterMessage {
|
|
75
|
+
type: 'REGISTER';
|
|
76
|
+
role: 'overlay' | 'panel';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface ElementSelectedMessage {
|
|
80
|
+
type: 'ELEMENT_SELECTED';
|
|
81
|
+
to: 'panel';
|
|
82
|
+
componentName: string;
|
|
83
|
+
instanceCount: number;
|
|
84
|
+
classes: string;
|
|
85
|
+
tailwindConfig: any;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ClearHighlightsMessage {
|
|
89
|
+
type: 'CLEAR_HIGHLIGHTS';
|
|
90
|
+
to: 'overlay';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface SwitchContainerMessage {
|
|
94
|
+
type: 'SWITCH_CONTAINER';
|
|
95
|
+
to: 'overlay';
|
|
96
|
+
container: ContainerName;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export interface PingMessage {
|
|
100
|
+
type: 'PING';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface PongMessage {
|
|
104
|
+
type: 'PONG';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// New PATCH_* messages
|
|
108
|
+
|
|
109
|
+
/** Panel → Overlay: live-preview a class swap */
|
|
110
|
+
export interface PatchPreviewMessage {
|
|
111
|
+
type: 'PATCH_PREVIEW';
|
|
112
|
+
to: 'overlay';
|
|
113
|
+
oldClass: string;
|
|
114
|
+
newClass: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Panel → Overlay: revert any active preview */
|
|
118
|
+
export interface PatchRevertMessage {
|
|
119
|
+
type: 'PATCH_REVERT';
|
|
120
|
+
to: 'overlay';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Panel → Overlay: stage a change (overlay fills context, sends PATCH_STAGED to server) */
|
|
124
|
+
export interface PatchStageMessage {
|
|
125
|
+
type: 'PATCH_STAGE';
|
|
126
|
+
to: 'overlay';
|
|
127
|
+
id: string;
|
|
128
|
+
oldClass: string;
|
|
129
|
+
newClass: string;
|
|
130
|
+
property: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Overlay → Server: patch with full context, added to server queue */
|
|
134
|
+
export interface PatchStagedMessage {
|
|
135
|
+
type: 'PATCH_STAGED';
|
|
136
|
+
patch: Patch;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Panel → Server: move staged patches to committed status */
|
|
140
|
+
export interface PatchCommitMessage {
|
|
141
|
+
type: 'PATCH_COMMIT';
|
|
142
|
+
ids: string[]; // includes both class-change AND message patch IDs
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Panel → Server: stage a message patch */
|
|
146
|
+
export interface MessageStageMessage {
|
|
147
|
+
type: 'MESSAGE_STAGE';
|
|
148
|
+
id: string; // UUID (generated by panel)
|
|
149
|
+
message: string; // the user's text
|
|
150
|
+
elementKey?: string; // optional — current element, or empty for general context
|
|
151
|
+
component?: { name: string; instanceCount?: number };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Server → Panel/Overlay: broadcast full queue state */
|
|
155
|
+
export interface QueueUpdateMessage {
|
|
156
|
+
type: 'QUEUE_UPDATE';
|
|
157
|
+
// Counts for the footer pills
|
|
158
|
+
draftCount: number; // patches in the draft (staged, not yet committed)
|
|
159
|
+
committedCount: number; // commits with status 'committed'
|
|
160
|
+
implementingCount: number;
|
|
161
|
+
implementedCount: number;
|
|
162
|
+
partialCount: number;
|
|
163
|
+
errorCount: number;
|
|
164
|
+
// Draft: the in-progress group (ordered by insertion)
|
|
165
|
+
draft: PatchSummary[]; // all staged patches in order (class-changes + messages)
|
|
166
|
+
// Finalized commits by status
|
|
167
|
+
commits: CommitSummary[];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** @deprecated Use QueueUpdateMessage instead */
|
|
171
|
+
export interface PatchUpdateMessage {
|
|
172
|
+
type: 'PATCH_UPDATE';
|
|
173
|
+
staged: number;
|
|
174
|
+
committed: number;
|
|
175
|
+
implementing: number;
|
|
176
|
+
implemented: number;
|
|
177
|
+
patches: {
|
|
178
|
+
staged: PatchSummary[];
|
|
179
|
+
committed: PatchSummary[];
|
|
180
|
+
implementing: PatchSummary[];
|
|
181
|
+
implemented: PatchSummary[];
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Server → Panel: agent reports work-in-progress */
|
|
186
|
+
export interface PatchImplementingMessage {
|
|
187
|
+
type: 'PATCH_IMPLEMENTING';
|
|
188
|
+
ids: string[];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Server → Panel: agent marks changes done */
|
|
192
|
+
export interface PatchImplementedMessage {
|
|
193
|
+
type: 'PATCH_IMPLEMENTED';
|
|
194
|
+
ids: string[];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Server → Panel: error on a specific patch */
|
|
198
|
+
export interface PatchErrorMessage {
|
|
199
|
+
type: 'PATCH_ERROR';
|
|
200
|
+
id: string;
|
|
201
|
+
errorMessage: string;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// Design canvas messages
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
export type InsertMode = 'before' | 'after' | 'first-child' | 'last-child';
|
|
209
|
+
|
|
210
|
+
/** Panel → Overlay: request to inject a design canvas */
|
|
211
|
+
export interface InsertDesignCanvasMessage {
|
|
212
|
+
type: 'INSERT_DESIGN_CANVAS';
|
|
213
|
+
to: 'overlay';
|
|
214
|
+
insertMode: InsertMode;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Overlay → Design iframe: element context for the canvas */
|
|
218
|
+
export interface ElementContextMessage {
|
|
219
|
+
type: 'ELEMENT_CONTEXT';
|
|
220
|
+
to: 'design';
|
|
221
|
+
componentName: string;
|
|
222
|
+
instanceCount: number;
|
|
223
|
+
target: {
|
|
224
|
+
tag: string;
|
|
225
|
+
classes: string;
|
|
226
|
+
innerText: string;
|
|
227
|
+
};
|
|
228
|
+
context: string;
|
|
229
|
+
insertMode: InsertMode;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Design iframe → Server: submit the sketch */
|
|
233
|
+
export interface DesignSubmitMessage {
|
|
234
|
+
type: 'DESIGN_SUBMIT';
|
|
235
|
+
image: string;
|
|
236
|
+
componentName: string;
|
|
237
|
+
target: {
|
|
238
|
+
tag: string;
|
|
239
|
+
classes: string;
|
|
240
|
+
innerText: string;
|
|
241
|
+
};
|
|
242
|
+
context: string;
|
|
243
|
+
insertMode: InsertMode;
|
|
244
|
+
canvasWidth: number;
|
|
245
|
+
canvasHeight: number;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Design iframe → Overlay: close the canvas wrapper */
|
|
249
|
+
export interface DesignCloseMessage {
|
|
250
|
+
type: 'DESIGN_CLOSE';
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
// Union types
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
|
|
257
|
+
export type OverlayToPanel = ElementSelectedMessage;
|
|
258
|
+
export type PanelToOverlay =
|
|
259
|
+
| PatchPreviewMessage
|
|
260
|
+
| PatchRevertMessage
|
|
261
|
+
| PatchStageMessage
|
|
262
|
+
| ClearHighlightsMessage
|
|
263
|
+
| SwitchContainerMessage
|
|
264
|
+
| InsertDesignCanvasMessage;
|
|
265
|
+
export type OverlayToServer = PatchStagedMessage;
|
|
266
|
+
export type PanelToServer = PatchCommitMessage | MessageStageMessage;
|
|
267
|
+
export type ClientToServer =
|
|
268
|
+
| RegisterMessage
|
|
269
|
+
| PatchStagedMessage
|
|
270
|
+
| PatchCommitMessage
|
|
271
|
+
| MessageStageMessage
|
|
272
|
+
| DesignSubmitMessage
|
|
273
|
+
| DesignCloseMessage
|
|
274
|
+
| PingMessage;
|
|
275
|
+
export type ServerToClient =
|
|
276
|
+
| PongMessage
|
|
277
|
+
| QueueUpdateMessage
|
|
278
|
+
| PatchUpdateMessage
|
|
279
|
+
| PatchImplementingMessage
|
|
280
|
+
| PatchImplementedMessage
|
|
281
|
+
| PatchErrorMessage;
|
|
282
|
+
|
|
283
|
+
export type AnyMessage =
|
|
284
|
+
| RegisterMessage
|
|
285
|
+
| ElementSelectedMessage
|
|
286
|
+
| PatchPreviewMessage
|
|
287
|
+
| PatchRevertMessage
|
|
288
|
+
| PatchStageMessage
|
|
289
|
+
| PatchStagedMessage
|
|
290
|
+
| PatchCommitMessage
|
|
291
|
+
| MessageStageMessage
|
|
292
|
+
| QueueUpdateMessage
|
|
293
|
+
| PatchUpdateMessage
|
|
294
|
+
| PatchImplementingMessage
|
|
295
|
+
| PatchImplementedMessage
|
|
296
|
+
| PatchErrorMessage
|
|
297
|
+
| ClearHighlightsMessage
|
|
298
|
+
| SwitchContainerMessage
|
|
299
|
+
| InsertDesignCanvasMessage
|
|
300
|
+
| ElementContextMessage
|
|
301
|
+
| DesignSubmitMessage
|
|
302
|
+
| DesignCloseMessage
|
|
303
|
+
| PingMessage
|
|
304
|
+
| PongMessage;
|