gazesight 0.3.5

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.
Files changed (52) hide show
  1. package/LICENSE +37 -0
  2. package/README.md +102 -0
  3. package/dist/chrome/background-capture-offscreen.html +10 -0
  4. package/dist/chrome/background-capture-offscreen.js +1 -0
  5. package/dist/chrome/background.js +62 -0
  6. package/dist/chrome/content.js +62 -0
  7. package/dist/chrome/diagnostics-page.js +1 -0
  8. package/dist/chrome/fullpage-stitch-offscreen.html +10 -0
  9. package/dist/chrome/fullpage-stitch-offscreen.js +1 -0
  10. package/dist/chrome/icons/icon-128.png +0 -0
  11. package/dist/chrome/icons/icon-16.png +0 -0
  12. package/dist/chrome/icons/icon-32.png +0 -0
  13. package/dist/chrome/icons/icon-48.png +0 -0
  14. package/dist/chrome/icons/icon-64.png +0 -0
  15. package/dist/chrome/icons/icon-96.png +0 -0
  16. package/dist/chrome/logo_sans_background.png +0 -0
  17. package/dist/chrome/manifest.json +70 -0
  18. package/dist/chrome/options.css +66 -0
  19. package/dist/chrome/options.html +76 -0
  20. package/dist/chrome/options.js +82 -0
  21. package/dist/chrome/permission.css +13 -0
  22. package/dist/chrome/permission.html +15 -0
  23. package/dist/chrome/permission.js +1 -0
  24. package/dist/chrome/popup.css +446 -0
  25. package/dist/chrome/popup.html +191 -0
  26. package/dist/chrome/popup.js +1 -0
  27. package/dist/cli/index.js +320 -0
  28. package/dist/firefox/background.js +62 -0
  29. package/dist/firefox/content.js +62 -0
  30. package/dist/firefox/diagnostics-page.js +1 -0
  31. package/dist/firefox/icons/icon-128.png +0 -0
  32. package/dist/firefox/icons/icon-16.png +0 -0
  33. package/dist/firefox/icons/icon-32.png +0 -0
  34. package/dist/firefox/icons/icon-48.png +0 -0
  35. package/dist/firefox/icons/icon-64.png +0 -0
  36. package/dist/firefox/icons/icon-96.png +0 -0
  37. package/dist/firefox/logo_sans_background.png +0 -0
  38. package/dist/firefox/manifest.json +90 -0
  39. package/dist/firefox/options.css +66 -0
  40. package/dist/firefox/options.html +76 -0
  41. package/dist/firefox/options.js +82 -0
  42. package/dist/firefox/permission.css +13 -0
  43. package/dist/firefox/permission.html +15 -0
  44. package/dist/firefox/permission.js +1 -0
  45. package/dist/firefox/popup.css +446 -0
  46. package/dist/firefox/popup.html +191 -0
  47. package/dist/firefox/popup.js +1 -0
  48. package/dist/gazesight-firefox.xpi +0 -0
  49. package/dist/mcp/index.js +1428 -0
  50. package/dist/worker/visual-diff.js +642 -0
  51. package/docs/preview-matrix.csv +6 -0
  52. package/package.json +130 -0
@@ -0,0 +1,642 @@
1
+ // apps/mcp-server/src/tools/diff-worker.ts
2
+ import { parentPort, workerData } from "node:worker_threads";
3
+
4
+ // apps/mcp-server/src/tools/diff-engine.ts
5
+ import pixelmatch from "pixelmatch";
6
+ import { PNG } from "pngjs";
7
+
8
+ // packages/protocol/src/schemas.ts
9
+ import { z } from "zod";
10
+ var targetSchema = z.strictObject({
11
+ tabId: z.number().int().nonnegative().optional()
12
+ });
13
+ var stabilityDiagnosticsSchema = z.strictObject({
14
+ stable: z.boolean(),
15
+ stabilityWaitMs: z.number().finite().nonnegative(),
16
+ lastMutationAgoMs: z.number().finite().nonnegative(),
17
+ fontsReady: z.boolean(),
18
+ pendingImages: z.number().int().nonnegative().max(1e6),
19
+ timeoutReached: z.boolean()
20
+ });
21
+ var diagnosticsQuerySchema = targetSchema.extend({
22
+ maxEntries: z.number().int().min(1).max(100).default(50),
23
+ reset: z.boolean().default(true)
24
+ });
25
+ var highlightSchema = targetSchema.extend({
26
+ selector: z.string().min(1).max(2048),
27
+ durationMs: z.number().int().min(500).max(3e4).default(3e3)
28
+ });
29
+ var highlightResultSchema = z.strictObject({
30
+ tabId: z.number().int().nonnegative(),
31
+ url: z.url(),
32
+ highlighted: z.boolean()
33
+ });
34
+ var consoleEntrySchema = z.strictObject({
35
+ level: z.enum(["warn", "error"]),
36
+ message: z.string().max(512),
37
+ timestamp: z.number().finite().nonnegative()
38
+ });
39
+ var networkEntrySchema = z.strictObject({
40
+ url: z.url().max(1024),
41
+ method: z.string().max(16),
42
+ resourceType: z.string().max(32),
43
+ status: z.number().int().min(100).max(599).optional(),
44
+ error: z.string().max(128).optional(),
45
+ timestamp: z.number().finite().nonnegative()
46
+ });
47
+ var consoleResultSchema = z.strictObject({
48
+ tabId: z.number().int().nonnegative(),
49
+ entries: z.array(consoleEntrySchema).max(100),
50
+ dropped: z.number().int().nonnegative(),
51
+ forwardingDropped: z.number().int().nonnegative()
52
+ });
53
+ var networkResultSchema = z.strictObject({
54
+ tabId: z.number().int().nonnegative(),
55
+ entries: z.array(networkEntrySchema).max(100),
56
+ dropped: z.number().int().nonnegative()
57
+ });
58
+ var pageStatusContentSchema = z.strictObject({
59
+ url: z.url(),
60
+ title: z.string().max(160),
61
+ readyState: z.enum(["loading", "interactive", "complete"]),
62
+ viewport: z.strictObject({
63
+ width: z.number().int().positive(),
64
+ height: z.number().int().positive(),
65
+ devicePixelRatio: z.number().finite().positive()
66
+ }),
67
+ scroll: z.strictObject({ x: z.number().finite(), y: z.number().finite() }),
68
+ stabilityPending: z.boolean(),
69
+ consoleInstrumentationActive: z.boolean(),
70
+ consoleForwardingDropped: z.number().int().nonnegative()
71
+ });
72
+ var pageStatusResultSchema = pageStatusContentSchema.extend({
73
+ tabId: z.number().int().nonnegative(),
74
+ targetState: z.enum(["authorized", "reauthorization_required", "none"]),
75
+ runtimeErrorCount: z.number().int().nonnegative(),
76
+ consoleEntryCount: z.number().int().nonnegative(),
77
+ networkFailureCount: z.number().int().nonnegative()
78
+ });
79
+ var interactionTypeSchema = z.enum([
80
+ "click",
81
+ "type",
82
+ "press_key",
83
+ "hover",
84
+ "scroll",
85
+ "select",
86
+ "focus"
87
+ ]);
88
+ var interactionTargetSchema = targetSchema.extend({
89
+ selector: z.string().min(1).max(2048)
90
+ });
91
+ var typeSchema = interactionTargetSchema.extend({
92
+ text: z.string().max(4096),
93
+ clear: z.boolean().default(true)
94
+ });
95
+ var pressKeySchema = targetSchema.extend({
96
+ selector: z.string().min(1).max(2048).optional(),
97
+ key: z.string().max(12).refine(
98
+ (key) => key.length === 1 || [
99
+ "Enter",
100
+ "Escape",
101
+ "Tab",
102
+ "Backspace",
103
+ "Delete",
104
+ "ArrowUp",
105
+ "ArrowDown",
106
+ "ArrowLeft",
107
+ "ArrowRight",
108
+ "Home",
109
+ "End"
110
+ ].includes(key)
111
+ ),
112
+ shift: z.boolean().default(false)
113
+ });
114
+ var scrollSchema = targetSchema.extend({
115
+ selector: z.string().min(1).max(2048).optional(),
116
+ x: z.number().finite().min(-1e6).max(1e6).default(0),
117
+ y: z.number().finite().min(-1e6).max(1e6).default(0),
118
+ mode: z.enum(["by", "to"]).default("by")
119
+ });
120
+ var selectSchema = interactionTargetSchema.extend({
121
+ value: z.string().max(4096)
122
+ });
123
+ var interactionResultSchema = z.strictObject({
124
+ tabId: z.number().int().nonnegative(),
125
+ url: z.url(),
126
+ action: interactionTypeSchema,
127
+ inputSource: z.literal("dom"),
128
+ completed: z.literal(true),
129
+ scroll: z.strictObject({ x: z.number().finite(), y: z.number().finite() }).optional()
130
+ });
131
+ var captureSchema = targetSchema.extend({
132
+ url: z.url().optional(),
133
+ format: z.enum(["png", "jpeg"]).default("jpeg"),
134
+ quality: z.number().int().min(0).max(100).default(85),
135
+ waitForStable: z.boolean().default(false),
136
+ includeOverlay: z.boolean().optional(),
137
+ stableWindowMs: z.number().int().min(20).max(1e3).default(100),
138
+ maxWaitMs: z.number().int().min(50).max(5e3).default(2e3)
139
+ });
140
+ var reloadSchema = targetSchema.extend({
141
+ bypassCache: z.boolean().default(false)
142
+ });
143
+ var regionCaptureSchema = captureSchema.extend({
144
+ x: z.number().finite().nonnegative(),
145
+ y: z.number().finite().nonnegative(),
146
+ width: z.number().finite().positive().max(32768),
147
+ height: z.number().finite().positive().max(32768)
148
+ });
149
+ var elementCaptureSchema = captureSchema.extend({
150
+ selector: z.string().min(1).max(2048),
151
+ padding: z.number().finite().min(0).max(1e3).default(0)
152
+ });
153
+ var fullPageCaptureSchema = captureSchema.extend({
154
+ maxPixels: z.number().int().min(1).max(32e6).default(16e6),
155
+ maxSegments: z.number().int().min(1).max(64).default(32),
156
+ timeoutMs: z.number().int().min(500).max(8e3).default(7e3)
157
+ });
158
+ var navigateSchema = targetSchema.extend({ url: z.url() });
159
+ var diffSchema = z.strictObject({
160
+ baseline: z.uuid(),
161
+ current: z.uuid().optional(),
162
+ threshold: z.number().finite().min(0).max(1).default(0.1)
163
+ });
164
+ var viewportProfileSchema = z.enum([
165
+ "mobile-small",
166
+ "mobile",
167
+ "tablet",
168
+ "desktop",
169
+ "desktop-wide"
170
+ ]);
171
+ var setViewportSchema = targetSchema.extend({
172
+ profile: viewportProfileSchema.optional(),
173
+ width: z.number().int().min(200).max(8192).optional(),
174
+ height: z.number().int().min(200).max(8192).optional(),
175
+ dedicatedPreview: z.boolean().default(true)
176
+ });
177
+ var viewportResultSchema = z.strictObject({
178
+ tabId: z.number().int().nonnegative(),
179
+ url: z.url(),
180
+ windowId: z.number().int().nonnegative(),
181
+ width: z.number().int().positive(),
182
+ height: z.number().int().positive(),
183
+ devicePixelRatio: z.number().finite().positive(),
184
+ zoom: z.number().finite().positive(),
185
+ outerWidth: z.number().int().positive(),
186
+ outerHeight: z.number().int().positive(),
187
+ dedicatedPreview: z.boolean(),
188
+ sourceTabId: z.number().int().nonnegative().optional(),
189
+ requested: z.strictObject({
190
+ width: z.number().int().positive(),
191
+ height: z.number().int().positive(),
192
+ profile: viewportProfileSchema.optional()
193
+ }).optional(),
194
+ exact: z.boolean().optional(),
195
+ attempts: z.number().int().min(0).max(3).optional(),
196
+ requiresTargetSelection: z.boolean().optional(),
197
+ sourceWindowUnchanged: z.boolean().optional()
198
+ });
199
+ var domSchema = targetSchema.extend({
200
+ selector: z.string().min(1).max(2048).optional(),
201
+ depth: z.number().int().min(0).max(8).default(3),
202
+ maxNodes: z.number().int().min(1).max(300).default(100),
203
+ textLength: z.number().int().min(0).max(300).default(160),
204
+ maxBytes: z.number().int().min(2048).max(65536).default(32768)
205
+ });
206
+ var getElementSchema = domSchema.extend({
207
+ selector: z.string().min(1).max(2048)
208
+ });
209
+ var changedRegionsSchema = targetSchema.extend({
210
+ maxRegions: z.number().int().min(1).max(100).default(50),
211
+ reset: z.boolean().default(true)
212
+ });
213
+ var capabilitySchema = z.enum([
214
+ "highlight",
215
+ "clear_highlight",
216
+ "capture_region",
217
+ "capture_element",
218
+ "capture_full_page",
219
+ "get_dom",
220
+ "get_element",
221
+ "get_changed_regions",
222
+ "get_viewport",
223
+ "set_viewport",
224
+ ...interactionTypeSchema.options,
225
+ "get_console",
226
+ "get_network_failures",
227
+ "get_page_status"
228
+ ]);
229
+ var boundsSchema = z.strictObject({
230
+ x: z.number().finite(),
231
+ y: z.number().finite(),
232
+ width: z.number().finite().nonnegative(),
233
+ height: z.number().finite().nonnegative()
234
+ });
235
+ var domNodeSchema = z.strictObject({
236
+ index: z.number().int().nonnegative(),
237
+ parent: z.number().int().nonnegative().optional(),
238
+ depth: z.number().int().min(0).max(8),
239
+ tag: z.string().max(80),
240
+ id: z.string().max(160).optional(),
241
+ classes: z.array(z.string().max(80)).max(6).optional(),
242
+ role: z.string().max(80).optional(),
243
+ text: z.string().max(300).optional(),
244
+ ariaLabel: z.string().max(300).optional(),
245
+ name: z.string().max(160).optional(),
246
+ type: z.string().max(80).optional(),
247
+ href: z.string().max(1024).optional(),
248
+ bounds: boundsSchema,
249
+ visible: z.boolean(),
250
+ redacted: z.boolean().optional(),
251
+ disabled: z.boolean().optional(),
252
+ checked: z.boolean().optional(),
253
+ selected: z.boolean().optional()
254
+ });
255
+ var domResultSchema = z.strictObject({
256
+ tabId: z.number().int().nonnegative(),
257
+ url: z.url(),
258
+ nodes: z.array(domNodeSchema).max(300),
259
+ truncated: z.boolean(),
260
+ payloadBytes: z.number().int().max(65536)
261
+ });
262
+ var changedRegionsResultSchema = z.strictObject({
263
+ tabId: z.number().int().nonnegative(),
264
+ url: z.url(),
265
+ regions: z.array(
266
+ boundsSchema.extend({
267
+ reason: z.enum(["mutation", "resize", "attribute", "text"]),
268
+ selector: z.string().max(240).optional()
269
+ })
270
+ ).max(100),
271
+ trackingStarted: z.boolean(),
272
+ dropped: z.number().int().nonnegative(),
273
+ coverage: z.literal("potential-dom-and-layout-changes")
274
+ });
275
+ var errorSchema = z.strictObject({
276
+ type: z.enum(["error", "unhandledrejection", "console-error"]),
277
+ message: z.string().max(4096),
278
+ source: z.string().max(2048).optional(),
279
+ line: z.number().int().nonnegative().optional(),
280
+ column: z.number().int().nonnegative().optional(),
281
+ timestamp: z.number().nonnegative()
282
+ });
283
+ var stateSchema = z.strictObject({
284
+ extensionVersion: z.string().max(80).optional(),
285
+ extensionId: z.string().min(1).max(256).optional(),
286
+ capabilities: z.array(capabilitySchema).max(20).optional(),
287
+ selectedTab: z.number().int().nonnegative().nullable(),
288
+ url: z.string().max(2048).nullable(),
289
+ targetState: z.enum(["none", "authorized", "reauthorization_required"]).optional(),
290
+ browser: z.strictObject({
291
+ name: z.enum(["firefox", "chrome"]),
292
+ version: z.string().max(80)
293
+ }).optional(),
294
+ backgroundCapture: z.strictObject({
295
+ enabled: z.boolean(),
296
+ permissionGranted: z.boolean()
297
+ }).optional()
298
+ });
299
+ var requestSchema = z.discriminatedUnion("type", [
300
+ z.strictObject({
301
+ id: z.uuid(),
302
+ version: z.literal(1),
303
+ type: z.literal("highlight"),
304
+ payload: highlightSchema
305
+ }),
306
+ z.strictObject({
307
+ id: z.uuid(),
308
+ version: z.literal(1),
309
+ type: z.literal("clear_highlight"),
310
+ payload: targetSchema
311
+ }),
312
+ z.strictObject({
313
+ id: z.uuid(),
314
+ version: z.literal(1),
315
+ type: z.literal("get_console"),
316
+ payload: diagnosticsQuerySchema
317
+ }),
318
+ z.strictObject({
319
+ id: z.uuid(),
320
+ version: z.literal(1),
321
+ type: z.literal("get_network_failures"),
322
+ payload: diagnosticsQuerySchema
323
+ }),
324
+ z.strictObject({
325
+ id: z.uuid(),
326
+ version: z.literal(1),
327
+ type: z.literal("get_page_status"),
328
+ payload: targetSchema
329
+ }),
330
+ z.strictObject({
331
+ id: z.uuid(),
332
+ version: z.literal(1),
333
+ type: z.literal("click"),
334
+ payload: interactionTargetSchema
335
+ }),
336
+ z.strictObject({
337
+ id: z.uuid(),
338
+ version: z.literal(1),
339
+ type: z.literal("type"),
340
+ payload: typeSchema
341
+ }),
342
+ z.strictObject({
343
+ id: z.uuid(),
344
+ version: z.literal(1),
345
+ type: z.literal("press_key"),
346
+ payload: pressKeySchema
347
+ }),
348
+ z.strictObject({
349
+ id: z.uuid(),
350
+ version: z.literal(1),
351
+ type: z.literal("hover"),
352
+ payload: interactionTargetSchema
353
+ }),
354
+ z.strictObject({
355
+ id: z.uuid(),
356
+ version: z.literal(1),
357
+ type: z.literal("scroll"),
358
+ payload: scrollSchema
359
+ }),
360
+ z.strictObject({
361
+ id: z.uuid(),
362
+ version: z.literal(1),
363
+ type: z.literal("select"),
364
+ payload: selectSchema
365
+ }),
366
+ z.strictObject({
367
+ id: z.uuid(),
368
+ version: z.literal(1),
369
+ type: z.literal("focus"),
370
+ payload: interactionTargetSchema
371
+ }),
372
+ z.strictObject({
373
+ id: z.uuid(),
374
+ version: z.literal(1),
375
+ type: z.literal("get_viewport"),
376
+ payload: targetSchema
377
+ }),
378
+ z.strictObject({
379
+ id: z.uuid(),
380
+ version: z.literal(1),
381
+ type: z.literal("set_viewport"),
382
+ payload: setViewportSchema
383
+ }),
384
+ z.strictObject({
385
+ id: z.uuid(),
386
+ version: z.literal(1),
387
+ type: z.literal("get_dom"),
388
+ payload: domSchema
389
+ }),
390
+ z.strictObject({
391
+ id: z.uuid(),
392
+ version: z.literal(1),
393
+ type: z.literal("get_element"),
394
+ payload: getElementSchema
395
+ }),
396
+ z.strictObject({
397
+ id: z.uuid(),
398
+ version: z.literal(1),
399
+ type: z.literal("get_changed_regions"),
400
+ payload: changedRegionsSchema
401
+ }),
402
+ z.strictObject({
403
+ id: z.uuid(),
404
+ version: z.literal(1),
405
+ type: z.literal("capture_region"),
406
+ payload: regionCaptureSchema
407
+ }),
408
+ z.strictObject({
409
+ id: z.uuid(),
410
+ version: z.literal(1),
411
+ type: z.literal("capture_element"),
412
+ payload: elementCaptureSchema
413
+ }),
414
+ z.strictObject({
415
+ id: z.uuid(),
416
+ version: z.literal(1),
417
+ type: z.literal("capture_full_page"),
418
+ payload: fullPageCaptureSchema
419
+ }),
420
+ z.strictObject({
421
+ id: z.uuid(),
422
+ version: z.literal(1),
423
+ type: z.literal("capture"),
424
+ payload: captureSchema
425
+ }),
426
+ z.strictObject({
427
+ id: z.uuid(),
428
+ version: z.literal(1),
429
+ type: z.literal("reload"),
430
+ payload: reloadSchema
431
+ }),
432
+ z.strictObject({
433
+ id: z.uuid(),
434
+ version: z.literal(1),
435
+ type: z.literal("navigate"),
436
+ payload: navigateSchema
437
+ }),
438
+ z.strictObject({
439
+ id: z.uuid(),
440
+ version: z.literal(1),
441
+ type: z.literal("get_errors"),
442
+ payload: targetSchema
443
+ })
444
+ ]);
445
+ var responseSchema = z.discriminatedUnion("ok", [
446
+ z.strictObject({
447
+ id: z.uuid(),
448
+ version: z.literal(1),
449
+ ok: z.literal(true),
450
+ result: z.unknown()
451
+ }),
452
+ z.strictObject({
453
+ id: z.uuid(),
454
+ version: z.literal(1),
455
+ ok: z.literal(false),
456
+ error: z.strictObject({
457
+ code: z.string().max(80),
458
+ message: z.string().max(4096),
459
+ stability: stabilityDiagnosticsSchema.optional()
460
+ })
461
+ })
462
+ ]);
463
+ var helloSchema = z.strictObject({
464
+ version: z.literal(1),
465
+ type: z.literal("hello"),
466
+ token: z.string().regex(/^[a-f0-9]{64}$/),
467
+ state: stateSchema
468
+ });
469
+ var stateMessageSchema = z.strictObject({
470
+ version: z.literal(1),
471
+ type: z.literal("state"),
472
+ state: stateSchema
473
+ });
474
+ var captureResultSchema = z.strictObject({
475
+ tabId: z.number().int().nonnegative(),
476
+ url: z.url(),
477
+ width: z.number().int().positive(),
478
+ height: z.number().int().positive(),
479
+ format: z.enum(["png", "jpeg"]),
480
+ captureMs: z.number().nonnegative(),
481
+ processingMs: z.number().nonnegative().optional(),
482
+ totalMs: z.number().nonnegative().optional(),
483
+ segments: z.number().int().positive().max(64).optional(),
484
+ scrollRestored: z.boolean().optional(),
485
+ stabilityWaitMs: z.number().nonnegative().optional(),
486
+ extensionTotalMs: z.number().nonnegative().optional(),
487
+ renderAppearsStable: z.boolean().optional(),
488
+ stable: z.boolean().optional(),
489
+ lastMutationAgoMs: z.number().finite().nonnegative().optional(),
490
+ fontsReady: z.boolean().optional(),
491
+ pendingImages: z.number().int().nonnegative().optional(),
492
+ timeoutReached: z.boolean().optional(),
493
+ dataUrl: z.string().max(16 * 1024 * 1024)
494
+ });
495
+ var tabResultSchema = z.strictObject({
496
+ tabId: z.number().int().nonnegative(),
497
+ url: z.url()
498
+ });
499
+ var errorsResultSchema = z.strictObject({
500
+ tabId: z.number().int().nonnegative(),
501
+ errors: z.array(errorSchema).max(200)
502
+ });
503
+ var GSError = class extends Error {
504
+ constructor(code, message, stability) {
505
+ super(message);
506
+ this.code = code;
507
+ this.stability = stability;
508
+ }
509
+ };
510
+
511
+ // apps/mcp-server/src/tools/diff-engine.ts
512
+ var signature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
513
+ function decodeBoundedPng(data) {
514
+ if (data.length > 16 * 1024 * 1024)
515
+ throw new GSError("GS_DIFF_LIMIT", "Encoded PNG exceeds 16 MiB.");
516
+ const bytes = Buffer.from(data, "base64");
517
+ if (bytes.length < 33 || !bytes.subarray(0, 8).equals(signature) || bytes.readUInt32BE(8) !== 13 || bytes.toString("ascii", 12, 16) !== "IHDR")
518
+ throw new GSError("GS_DIFF_FORMAT", "Use a PNG capture reference.");
519
+ const width = bytes.readUInt32BE(16), height = bytes.readUInt32BE(20);
520
+ if (!width || !height || width > 8192 || height > 8192 || width * height > 4e6)
521
+ throw new GSError(
522
+ "GS_DIFF_LIMIT",
523
+ "Diff is limited to 4 million pixels and 8192 pixels per side."
524
+ );
525
+ if (bytes[24] !== 8 || ![2, 6].includes(bytes[25]) || bytes[26] || bytes[27] || bytes[28])
526
+ throw new GSError(
527
+ "GS_DIFF_FORMAT",
528
+ "Use a non-interlaced 8-bit RGB/RGBA PNG capture."
529
+ );
530
+ try {
531
+ return PNG.sync.read(bytes, { checkCRC: true });
532
+ } catch {
533
+ throw new GSError("GS_DIFF_FORMAT", "PNG decoding failed.");
534
+ }
535
+ }
536
+ function diffPixels(baseline, current, threshold) {
537
+ const start = performance.now(), a = decodeBoundedPng(baseline), b = decodeBoundedPng(current);
538
+ if (a.width !== b.width || a.height !== b.height)
539
+ throw new GSError(
540
+ "GS_DIFF_DIMENSIONS",
541
+ "Capture both images at the same pixel dimensions."
542
+ );
543
+ const output = Buffer.alloc(a.data.length);
544
+ const changedPixelCount = pixelmatch(
545
+ a.data,
546
+ b.data,
547
+ output,
548
+ a.width,
549
+ a.height,
550
+ { threshold, includeAA: true, diffMask: true }
551
+ );
552
+ const columns = Math.ceil(a.width / 32), rows = Math.ceil(a.height / 32);
553
+ const tiles = /* @__PURE__ */ new Map();
554
+ for (let y = 0; y < a.height; y++)
555
+ for (let x = 0; x < a.width; x++)
556
+ if (output[(y * a.width + x) * 4 + 3]) {
557
+ const index = Math.floor(y / 32) * columns + Math.floor(x / 32), box = tiles.get(index);
558
+ if (box) {
559
+ box.left = Math.min(box.left, x);
560
+ box.top = Math.min(box.top, y);
561
+ box.right = Math.max(box.right, x + 1);
562
+ box.bottom = Math.max(box.bottom, y + 1);
563
+ } else
564
+ tiles.set(index, { left: x, top: y, right: x + 1, bottom: y + 1 });
565
+ }
566
+ const visited = /* @__PURE__ */ new Set(), boundingBoxes = [];
567
+ for (const [index, first] of tiles) {
568
+ if (visited.has(index)) continue;
569
+ const queue = [index], box = { ...first };
570
+ visited.add(index);
571
+ for (let i = 0; i < queue.length; i++) {
572
+ const key = queue[i], tile = tiles.get(key);
573
+ box.left = Math.min(box.left, tile.left);
574
+ box.top = Math.min(box.top, tile.top);
575
+ box.right = Math.max(box.right, tile.right);
576
+ box.bottom = Math.max(box.bottom, tile.bottom);
577
+ const col = key % columns, row = Math.floor(key / columns);
578
+ for (let dy = -1; dy <= 1; dy++)
579
+ for (let dx = -1; dx <= 1; dx++) {
580
+ const nx = col + dx, ny = row + dy, next = ny * columns + nx;
581
+ if (nx >= 0 && ny >= 0 && nx < columns && ny < rows && tiles.has(next) && !visited.has(next)) {
582
+ visited.add(next);
583
+ queue.push(next);
584
+ }
585
+ }
586
+ }
587
+ boundingBoxes.push({
588
+ x: box.left,
589
+ y: box.top,
590
+ width: box.right - box.left,
591
+ height: box.bottom - box.top
592
+ });
593
+ }
594
+ const boundingBoxesCoalesced = boundingBoxes.length > 100;
595
+ if (boundingBoxesCoalesced) {
596
+ const x = Math.min(...boundingBoxes.map((r) => r.x)), y = Math.min(...boundingBoxes.map((r) => r.y));
597
+ const right = Math.max(...boundingBoxes.map((r) => r.x + r.width)), bottom = Math.max(...boundingBoxes.map((r) => r.y + r.height));
598
+ boundingBoxes.splice(0, boundingBoxes.length, {
599
+ x,
600
+ y,
601
+ width: right - x,
602
+ height: bottom - y
603
+ });
604
+ }
605
+ const image = PNG.sync.write(
606
+ Object.assign(new PNG(), {
607
+ width: a.width,
608
+ height: a.height,
609
+ data: output
610
+ })
611
+ );
612
+ if (image.length * 4 / 3 > 16 * 1024 * 1024)
613
+ throw new GSError("GS_DIFF_LIMIT", "Encoded diff image exceeds 16 MiB.");
614
+ return {
615
+ changed: changedPixelCount > 0,
616
+ changedPixelCount,
617
+ differencePercent: changedPixelCount / (a.width * a.height) * 100,
618
+ boundingBoxes,
619
+ boundingBoxesCoalesced,
620
+ width: a.width,
621
+ height: a.height,
622
+ data: image.toString("base64"),
623
+ processingMs: performance.now() - start
624
+ };
625
+ }
626
+
627
+ // apps/mcp-server/src/tools/diff-worker.ts
628
+ try {
629
+ parentPort?.postMessage({
630
+ ok: true,
631
+ value: diffPixels(
632
+ workerData.baseline,
633
+ workerData.current,
634
+ workerData.threshold
635
+ )
636
+ });
637
+ } catch (error) {
638
+ parentPort?.postMessage({
639
+ ok: false,
640
+ code: error instanceof GSError ? error.code : "GS_DIFF_FAILED"
641
+ });
642
+ }
@@ -0,0 +1,6 @@
1
+ Tester,OS,Firefox version,Coding agent,Install success,Setup success,Doctor result,TTFC seconds,FAST capture,STABLE capture,HMR verification,Reauthorization understood,Problems encountered
2
+ Tester 1,,,,,,,,,,,,
3
+ Tester 2,,,,,,,,,,,,
4
+ Tester 3,,,,,,,,,,,,
5
+ Tester 4,,,,,,,,,,,,
6
+ Tester 5,,,,,,,,,,,,