@bitovi/vybit 0.11.7 → 0.13.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/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
+ }
@@ -1,13 +1,16 @@
1
1
  import { addons } from 'storybook/preview-api';
2
2
 
3
+ // Ghost iframes are created by AdaptiveIframe for component extraction.
4
+ // They must not inject the overlay or trigger story-rendered events.
5
+ const isGhostIframe = new URLSearchParams(window.location.search).get('vybit-ghost') === '1';
6
+
3
7
  let injected = false;
4
8
 
5
9
  export const decorators = [
6
10
  (StoryFn: any, context: any) => {
7
- const serverUrl =
8
- context.parameters?.vybit?.serverUrl ?? 'http://localhost:3333';
9
-
10
- if (!injected) {
11
+ if (!isGhostIframe && !injected) {
12
+ const serverUrl =
13
+ context.parameters?.vybit?.serverUrl ?? 'http://localhost:3333';
11
14
  const script = document.createElement('script');
12
15
  script.src = `${serverUrl}/overlay.js`;
13
16
  script.onerror = (err) => console.error('[vybit-addon] overlay.js FAILED to load', err);
@@ -19,12 +22,14 @@ export const decorators = [
19
22
  },
20
23
  ];
21
24
 
22
- const channel = addons.getChannel();
23
- let lastStoryId: string | undefined;
25
+ if (!isGhostIframe) {
26
+ const channel = addons.getChannel();
27
+ let lastStoryId: string | undefined;
24
28
 
25
- channel.on('storyRendered', (storyId?: string) => {
26
- // Only reset selection on actual story navigation, not HMR updates
27
- if (storyId && storyId === lastStoryId) return;
28
- lastStoryId = storyId;
29
- window.postMessage({ type: 'STORYBOOK_STORY_RENDERED' }, '*');
30
- });
29
+ channel.on('storyRendered', (storyId?: string) => {
30
+ // Only reset selection on actual story navigation, not HMR updates
31
+ if (storyId && storyId === lastStoryId) return;
32
+ lastStoryId = storyId;
33
+ window.postMessage({ type: 'STORYBOOK_STORY_RENDERED' }, '*');
34
+ });
35
+ }
@@ -1,13 +1,16 @@
1
1
  import { addons } from '@storybook/preview-api';
2
2
 
3
+ // Ghost iframes are created by AdaptiveIframe for component extraction.
4
+ // They must not inject the overlay or trigger story-rendered events.
5
+ const isGhostIframe = new URLSearchParams(window.location.search).get('vybit-ghost') === '1';
6
+
3
7
  let injected = false;
4
8
 
5
9
  export const decorators = [
6
10
  (StoryFn: any, context: any) => {
7
- const serverUrl =
8
- context.parameters?.vybit?.serverUrl ?? 'http://localhost:3333';
9
-
10
- if (!injected) {
11
+ if (!isGhostIframe && !injected) {
12
+ const serverUrl =
13
+ context.parameters?.vybit?.serverUrl ?? 'http://localhost:3333';
11
14
  const script = document.createElement('script');
12
15
  script.src = `${serverUrl}/overlay.js`;
13
16
  script.onerror = (err) => console.error('[vybit-addon] overlay.js FAILED to load', err);
@@ -19,12 +22,14 @@ export const decorators = [
19
22
  },
20
23
  ];
21
24
 
22
- const channel = addons.getChannel();
23
- let lastStoryId: string | undefined;
25
+ if (!isGhostIframe) {
26
+ const channel = addons.getChannel();
27
+ let lastStoryId: string | undefined;
24
28
 
25
- channel.on('storyRendered', (storyId?: string) => {
26
- // Only reset selection on actual story navigation, not HMR updates
27
- if (storyId && storyId === lastStoryId) return;
28
- lastStoryId = storyId;
29
- window.postMessage({ type: 'STORYBOOK_STORY_RENDERED' }, '*');
30
- });
29
+ channel.on('storyRendered', (storyId?: string) => {
30
+ // Only reset selection on actual story navigation, not HMR updates
31
+ if (storyId && storyId === lastStoryId) return;
32
+ lastStoryId = storyId;
33
+ window.postMessage({ type: 'STORYBOOK_STORY_RENDERED' }, '*');
34
+ });
35
+ }