@bitovi/vybit 0.13.2 → 0.13.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/README.md CHANGED
@@ -2,7 +2,10 @@
2
2
 
3
3
  Change designs, draw mockups, provide suggestions, and report bugs __in your browser__ and send them to your favorite coding agent (Claude, Cursor, Copilot, etc) to be implemented. VyBit works with React or Angular apps built with Tailwind v3 or v4.
4
4
 
5
- <img width="1453" height="903" alt="Cursor_and_Carton_Case_Management" src="https://github.com/user-attachments/assets/59b8e280-a827-4fa0-95e3-6c350afacbc9" />
5
+ [Watch a video going over its features](https://www.youtube.com/watch?v=dE2rYcyC2xk)
6
+
7
+ [![Watch the video](https://github.com/user-attachments/assets/99961c66-e2c9-48e3-9e7d-c23d6eda37ca)](https://www.youtube.com/watch?v=dE2rYcyC2xk)
8
+
6
9
 
7
10
  `VyBit` changes how you can design and build an app or website. Instead of building your design system and page designs in Sketch or Figma and then implementing it in code, you:
8
11
 
@@ -12,12 +15,13 @@ Change designs, draw mockups, provide suggestions, and report bugs __in your bro
12
15
  | 2 | __Use VyBit to fine-tune your design system in Storybook__ - Adjust colors, spacing, shadows, layout and more | <img alt="image" src="https://github.com/user-attachments/assets/79ca04be-db8f-458f-8632-87cc040875db" /> |
13
16
  | 3a | __Use VyBit to design features__ - drop your design system components into pages | <img width="1481" height="922" alt="image" src="https://github.com/user-attachments/assets/415acdb7-102a-4c31-910b-10536c59ee4a" /> |
14
17
  | 3b | __Use VyBit to design features__ - sketch a feature with the design canvas | <img width="1482" height="924" alt="image" src="https://github.com/user-attachments/assets/924e9733-baf6-4492-b9da-05fd27c2df93" /> |
15
- | 3c | __Use VyBit to report bugs__ - send recent errors, console.logs, DOM snapshots, and events | Image Coming Soon |
18
+ | 3c | __Use VyBit to report bugs__ - send recent errors, console.logs, DOM snapshots, and events | <img width="1482" height="1080" alt="Untitled Project" src="https://github.com/user-attachments/assets/7754ccb4-0d4c-4f02-8ea3-dca45943fd9a" /> |
16
19
  | 4 | Add text or voice messages for extra context | <img width="376" height="261" alt="image" src="https://github.com/user-attachments/assets/546ea987-a0ad-4809-85c6-52fb91fb987e" /> |
17
20
 
18
21
  Plus, VyBit always knows what page, components, and elements you're editing, making it easier for agents to know exactly what you want!
19
22
 
20
23
 
24
+
21
25
  ## Installation
22
26
 
23
27
  To use VyBit:
@@ -1041,6 +1041,7 @@ ${pad}</${tag}>`;
1041
1041
  componentName: msg.componentName,
1042
1042
  storyId: msg.storyId,
1043
1043
  ghostHtml: msg.ghostHtml,
1044
+ ghostCss: msg.ghostCss ?? "",
1044
1045
  componentPath: msg.componentPath ?? "",
1045
1046
  componentArgs: msg.args ?? {}
1046
1047
  },
@@ -1048,6 +1049,71 @@ ${pad}</${tag}>`;
1048
1049
  `Place: ${msg.componentName}`
1049
1050
  );
1050
1051
  }
1052
+ function placeAtLockedInsert(msg) {
1053
+ if (!locked.target || !locked.position) return false;
1054
+ const target = locked.target;
1055
+ const position = locked.position;
1056
+ const template = document.createElement("template");
1057
+ template.innerHTML = msg.ghostHtml.trim();
1058
+ const inserted = template.content.firstElementChild;
1059
+ if (!inserted) return false;
1060
+ inserted.dataset.twDroppedComponent = msg.componentName;
1061
+ switch (position) {
1062
+ case "before":
1063
+ target.insertAdjacentElement("beforebegin", inserted);
1064
+ break;
1065
+ case "after":
1066
+ target.insertAdjacentElement("afterend", inserted);
1067
+ break;
1068
+ case "first-child":
1069
+ target.insertAdjacentElement("afterbegin", inserted);
1070
+ break;
1071
+ case "last-child":
1072
+ target.appendChild(inserted);
1073
+ break;
1074
+ }
1075
+ if (msg.ghostCss) injectGhostCss(msg.componentName, msg.ghostCss);
1076
+ const targetSelector = buildSelector(target);
1077
+ const isGhostTarget = !!target.dataset.twDroppedComponent;
1078
+ const ghostTargetPatchId = target.dataset.twDroppedPatchId;
1079
+ const ghostTargetName = target.dataset.twDroppedComponent;
1080
+ const ghostAncestor = !isGhostTarget ? findGhostAncestor(target) : null;
1081
+ const effectiveGhostName = isGhostTarget ? ghostTargetName : ghostAncestor?.dataset.twDroppedComponent;
1082
+ const effectiveGhostPatchId = isGhostTarget ? ghostTargetPatchId : ghostAncestor?.dataset.twDroppedPatchId;
1083
+ const context = effectiveGhostName ? `Place "${msg.componentName}" ${position} the <${effectiveGhostName} /> component (pending insertion from an earlier drop)` : buildContext(target, "", "", /* @__PURE__ */ new Map());
1084
+ let parentComponent;
1085
+ const fiber = getFiber(target);
1086
+ if (fiber) {
1087
+ const boundary = findComponentBoundary(fiber);
1088
+ if (boundary) parentComponent = { name: boundary.componentName };
1089
+ }
1090
+ const patch = {
1091
+ id: crypto.randomUUID(),
1092
+ kind: "component-drop",
1093
+ elementKey: targetSelector,
1094
+ status: "staged",
1095
+ originalClass: "",
1096
+ newClass: "",
1097
+ property: "component-drop",
1098
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1099
+ component: { name: msg.componentName },
1100
+ target: isGhostTarget ? { tag: ghostTargetName?.toLowerCase() ?? "unknown", classes: "", innerText: "" } : { tag: target.tagName.toLowerCase(), classes: target.className, innerText: target.innerText.slice(0, 100) },
1101
+ ghostHtml: msg.ghostHtml,
1102
+ ghostCss: msg.ghostCss || void 0,
1103
+ componentStoryId: msg.storyId,
1104
+ componentPath: msg.componentPath || void 0,
1105
+ componentArgs: Object.keys(msg.args ?? {}).length > 0 ? msg.args : void 0,
1106
+ parentComponent,
1107
+ insertMode: position,
1108
+ context,
1109
+ ...effectiveGhostPatchId ? { targetPatchId: effectiveGhostPatchId, targetComponentName: effectiveGhostName } : {}
1110
+ };
1111
+ inserted.dataset.twDroppedPatchId = patch.id;
1112
+ send({ type: "COMPONENT_DROPPED", patch });
1113
+ sendTo("panel", { type: "COMPONENT_DISARMED" });
1114
+ clearLockedInsert();
1115
+ return true;
1116
+ }
1051
1117
  function cancelInsert() {
1052
1118
  cleanup();
1053
1119
  }
@@ -1059,6 +1125,9 @@ ${pad}</${tag}>`;
1059
1125
  inserted.dataset.twDroppedComponent = msg.componentName;
1060
1126
  target.insertAdjacentElement("beforebegin", inserted);
1061
1127
  target.style.display = "none";
1128
+ if (msg.ghostCss) {
1129
+ injectGhostCss(msg.componentName, msg.ghostCss);
1130
+ }
1062
1131
  const targetSelector = buildSelector(target);
1063
1132
  const isGhostTarget = !!target.dataset.twDroppedComponent;
1064
1133
  const ghostTargetPatchId = target.dataset.twDroppedPatchId;
@@ -1089,6 +1158,7 @@ ${pad}</${tag}>`;
1089
1158
  innerText: target.innerText.slice(0, 100)
1090
1159
  },
1091
1160
  ghostHtml: msg.ghostHtml,
1161
+ ghostCss: msg.ghostCss || void 0,
1092
1162
  componentStoryId: msg.storyId,
1093
1163
  componentPath: msg.componentPath || void 0,
1094
1164
  componentArgs: Object.keys(msg.args ?? {}).length > 0 ? msg.args : void 0,
@@ -1487,7 +1557,7 @@ ${pad}</${tag}>`;
1487
1557
  e.preventDefault();
1488
1558
  e.stopPropagation();
1489
1559
  if (mode.kind !== "component-insert") return;
1490
- const { componentName: cName, storyId: sId, ghostHtml: gHtml, componentPath: cPath, componentArgs: cArgs } = mode;
1560
+ const { componentName: cName, storyId: sId, ghostHtml: gHtml, ghostCss: gCss, componentPath: cPath, componentArgs: cArgs } = mode;
1491
1561
  const template = document.createElement("template");
1492
1562
  template.innerHTML = gHtml.trim();
1493
1563
  const inserted = template.content.firstElementChild;
@@ -1513,6 +1583,9 @@ ${pad}</${tag}>`;
1513
1583
  target.appendChild(inserted);
1514
1584
  break;
1515
1585
  }
1586
+ if (gCss) {
1587
+ injectGhostCss(cName, gCss);
1588
+ }
1516
1589
  const targetSelector = buildSelector(target);
1517
1590
  const isGhostTarget = !!target.dataset.twDroppedComponent;
1518
1591
  const ghostTargetPatchId = target.dataset.twDroppedPatchId;
@@ -1543,6 +1616,7 @@ ${pad}</${tag}>`;
1543
1616
  innerText: target.innerText.slice(0, 100)
1544
1617
  },
1545
1618
  ghostHtml: gHtml,
1619
+ ghostCss: gCss || void 0,
1546
1620
  componentStoryId: sId,
1547
1621
  componentPath: cPath || void 0,
1548
1622
  componentArgs: Object.keys(cArgs).length > 0 ? cArgs : void 0,
@@ -1594,6 +1668,15 @@ ${pad}</${tag}>`;
1594
1668
  dom.currentTarget = null;
1595
1669
  dom.currentPosition = null;
1596
1670
  }
1671
+ function injectGhostCss(componentName, css2) {
1672
+ const id = `vybit-ghost-css-${componentName}`;
1673
+ const existing = document.getElementById(id);
1674
+ if (existing) existing.remove();
1675
+ const style = document.createElement("style");
1676
+ style.id = id;
1677
+ style.textContent = css2;
1678
+ document.head.appendChild(style);
1679
+ }
1597
1680
 
1598
1681
  // node_modules/@floating-ui/utils/dist/floating-ui.utils.mjs
1599
1682
  var min = Math.min;
@@ -6882,7 +6965,7 @@ ${pad}</${tag}>`;
6882
6965
  req.onsuccess = () => resolve(req.result);
6883
6966
  req.onerror = () => reject(req.error);
6884
6967
  });
6885
- await this.pruneIfNeeded();
6968
+ await this.pruneIfNeeded().catch((err) => console.warn("[SnapshotStore] pruneIfNeeded failed:", err));
6886
6969
  return id;
6887
6970
  }
6888
6971
  /** Get a single snapshot by ID. */
@@ -7416,8 +7499,10 @@ ${pad}</${tag}>`;
7416
7499
  return hex;
7417
7500
  }
7418
7501
  async function clickHandler(e) {
7502
+ console.log("[overlay-debug] clickHandler fired", { target: e.target?.tagName, selectModeOn: state.selectModeOn, active: state.active });
7419
7503
  const composed = e.composedPath();
7420
7504
  if (composed.some((el) => el === state.shadowHost)) {
7505
+ console.log("[overlay-debug] click ignored: shadowHost");
7421
7506
  return;
7422
7507
  }
7423
7508
  if (isActive()) return;
@@ -7539,6 +7624,7 @@ ${pad}</${tag}>`;
7539
7624
  }
7540
7625
  }
7541
7626
  function setSelectMode2(on) {
7627
+ console.log("[overlay-debug] setSelectMode", on, "active:", state.active, "currentMode:", state.currentMode);
7542
7628
  state.selectModeOn = on;
7543
7629
  if (on) {
7544
7630
  document.documentElement.style.cursor = "crosshair";
@@ -7626,7 +7712,7 @@ ${pad}</${tag}>`;
7626
7712
  }
7627
7713
  function init() {
7628
7714
  const params = new URLSearchParams(location.search);
7629
- if (params.get("viewMode") === "story" || params.get("vybit-ghost") === "1") return;
7715
+ if (params.get("vybit-ghost") === "1") return;
7630
7716
  state.shadowHost = document.createElement("div");
7631
7717
  state.shadowHost.id = "tw-visual-editor-host";
7632
7718
  state.shadowHost.style.cssText = css(SHADOW_HOST);
@@ -7695,6 +7781,7 @@ ${pad}</${tag}>`;
7695
7781
  const wsUrl = SERVER_ORIGIN.replace(/^http/, "ws");
7696
7782
  connect(wsUrl);
7697
7783
  onMessage((msg) => {
7784
+ console.log("[overlay-debug] WS message from panel:", msg.type, msg);
7698
7785
  if (msg.type === "TOGGLE_SELECT_MODE") {
7699
7786
  if (msg.active) {
7700
7787
  state.active = true;
@@ -7894,8 +7981,11 @@ ${pad}</${tag}>`;
7894
7981
  } else {
7895
7982
  armElementSelect(`Replace: ${msg.componentName}`, state.shadowHost, doReplace);
7896
7983
  }
7897
- } else {
7984
+ } else if (!placeAtLockedInsert(msg)) {
7898
7985
  armInsert(msg, state.shadowHost);
7986
+ } else {
7987
+ clearHighlights();
7988
+ clearSelectionState();
7899
7989
  }
7900
7990
  } else if (msg.type === "COMPONENT_DISARM") {
7901
7991
  cancelInsert();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitovi/vybit",
3
- "version": "0.13.2",
3
+ "version": "0.13.4",
4
4
  "description": "Browser overlay + inspector panel + MCP server for visually editing Tailwind CSS classes on a running React app",
5
5
  "keywords": [
6
6
  "tailwind",