@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 +6 -2
- package/overlay/dist/overlay.js +94 -4
- package/package.json +1 -1
- package/panel/dist/assets/{DesignMode-0yrf7kUU.js → DesignMode-DTled2fb.js} +37 -37
- package/panel/dist/assets/index-BT9whNBX.js +62 -0
- package/panel/dist/assets/index-rvhMGLF5.css +1 -0
- package/panel/dist/index.html +2 -2
- package/server/app.ts +3 -3
- package/server/ghost-cache.ts +5 -11
- package/shared/css-utils.ts +21 -0
- package/shared/types.ts +4 -0
- package/panel/dist/assets/index-BmjlwBEu.js +0 -61
- package/panel/dist/assets/index-DN-H7d0q.css +0 -1
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
|
-
|
|
5
|
+
[Watch a video going over its features](https://www.youtube.com/watch?v=dE2rYcyC2xk)
|
|
6
|
+
|
|
7
|
+
[](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 |
|
|
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:
|
package/overlay/dist/overlay.js
CHANGED
|
@@ -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("
|
|
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();
|