cmdzero 0.6.0 → 0.7.1
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 +4 -0
- package/overlay/overlay.js +104 -59
- package/package.json +1 -1
- package/src/server.js +3 -0
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/adamcwade/cmdzero/main/assets/hero.png" alt="CmdZero — tweak your UI live in the browser; changes land in your source" width="900">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# CmdZero
|
|
2
6
|
|
|
3
7
|
Tweak your UI live in the browser — the changes are written straight to your source files.
|
package/overlay/overlay.js
CHANGED
|
@@ -43,12 +43,19 @@
|
|
|
43
43
|
.cz-pop button.cz-primary{background:#6366f1}
|
|
44
44
|
.cz-pop input{flex:1;background:#1f2937;border:1px solid #374151;border-radius:6px;color:#f9fafb;padding:5px 8px;font-size:12px;outline:none}
|
|
45
45
|
.cz-pop select{background:#1f2937;border:1px solid #374151;border-radius:6px;color:#f9fafb;padding:3px 4px;font-size:11.5px;outline:none}
|
|
46
|
-
.cz-panel{position:fixed;left:
|
|
46
|
+
.cz-panel{position:fixed;left:0;top:30px;bottom:0;width:264px;box-sizing:border-box;overflow-y:auto;background:#0f1523;color:#f9fafb;border-right:1.5px solid #10b981;box-shadow:8px 0 30px rgba(0,0,0,.4);padding:12px 12px 28px;pointer-events:auto;display:flex;flex-direction:column;gap:6px;font-size:12px;z-index:2147483100;transform:translateX(0);transition:transform .26s cubic-bezier(.4,0,.2,1)}
|
|
47
|
+
.cz-panel.cz-collapsed{transform:translateX(-100%)}
|
|
48
|
+
.cz-phead{display:flex;align-items:center;justify-content:space-between;gap:8px;padding-bottom:7px;border-bottom:1px solid #1f2937}
|
|
49
|
+
.cz-min{background:#1f2937;color:#cbd5e1;border:none;border-radius:6px;padding:2px 9px;font-size:14px;line-height:1;cursor:pointer}
|
|
50
|
+
.cz-min:hover{background:#374151;color:#fff}
|
|
51
|
+
.cz-ptab{position:fixed;left:0;top:calc(50% + 15px);transform:translateY(-50%);z-index:2147483110;display:none;align-items:center;gap:5px;background:#10b981;color:#052e1b;border:none;border-radius:0 9px 9px 0;padding:13px 6px;cursor:pointer;pointer-events:auto;writing-mode:vertical-rl;font-size:11px;font-weight:700;letter-spacing:.05em;box-shadow:2px 0 10px rgba(0,0,0,.35)}
|
|
52
|
+
.cz-ptab.cz-show{display:flex}
|
|
53
|
+
.cz-ptab:hover{background:#34d399}
|
|
47
54
|
.cz-panel .cz-row{display:flex;gap:6px;align-items:center;flex-wrap:wrap}
|
|
48
55
|
.cz-panel button{background:#374151;color:#f9fafb;border:none;border-radius:6px;padding:4px 9px;font-size:12px;cursor:pointer}
|
|
49
56
|
.cz-panel button:hover{background:#4b5563}
|
|
50
57
|
.cz-panel select{background:#1f2937;border:1px solid #374151;border-radius:6px;color:#f9fafb;padding:3px 4px;font-size:11.5px;outline:none}
|
|
51
|
-
.cz-ptitle{font-family:ui-monospace,monospace;font-size:11px;color:#6ee7b7;
|
|
58
|
+
.cz-ptitle{font-family:ui-monospace,monospace;font-size:11px;color:#6ee7b7;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
52
59
|
.cz-sec{color:#94a3b8;font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.07em;margin:5px 0 1px}
|
|
53
60
|
.cz-label{color:#9ca3af;min-width:50px}
|
|
54
61
|
.cz-note{color:#fbbf24;font-size:11px;line-height:1.35;background:rgba(245,158,11,.1);border-radius:6px;padding:4px 7px}
|
|
@@ -58,8 +65,14 @@
|
|
|
58
65
|
.cz-swatch{width:18px;height:18px;border-radius:4px;border:1px solid rgba(255,255,255,.25);cursor:pointer;padding:0}
|
|
59
66
|
.cz-swatch:hover{transform:scale(1.15)}
|
|
60
67
|
.cz-chip{font-size:10.5px !important;padding:2px 6px !important}
|
|
61
|
-
.cz-tray{position:fixed;right:14px;bottom:14px;display:flex;flex-direction:column;align-items:flex-end;gap:6px;pointer-events:auto
|
|
62
|
-
|
|
68
|
+
.cz-tray{position:fixed;right:14px;bottom:14px;display:flex;flex-direction:column;align-items:flex-end;gap:6px;pointer-events:auto}
|
|
69
|
+
/* Compressed by default (just a dot + short label); hovering the tray expands
|
|
70
|
+
every alert to full detail with undo/cancel. */
|
|
71
|
+
.cz-tweak{transition:padding .12s ease}
|
|
72
|
+
.cz-tray:not(:hover) .cz-tweak{padding:4px 10px;font-size:12px}
|
|
73
|
+
.cz-tray:not(:hover) .cz-tweak .cz-meta,
|
|
74
|
+
.cz-tray:not(:hover) .cz-tweak button{display:none}
|
|
75
|
+
.cz-tray:not(:hover) .cz-tlabel{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:230px;display:inline-block;vertical-align:bottom}
|
|
63
76
|
.cz-banner{position:fixed;top:0;left:0;right:0;height:30px;display:flex;align-items:center;justify-content:space-between;gap:14px;padding:0 14px;background:rgba(9,13,20,.94);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border-bottom:1px solid #1f2937;color:#cbd5e1;font-size:12px;line-height:1;pointer-events:auto;z-index:2147483200;box-sizing:border-box}
|
|
64
77
|
.cz-banner .cz-brand{display:flex;align-items:center;gap:6px;font-weight:600;color:#e2e8f0;flex:none}
|
|
65
78
|
.cz-banner .cz-brand kbd{background:#10b981;color:#052e1b;border-radius:5px;padding:2px 6px;font-size:10.5px;font-family:ui-monospace,monospace;font-weight:700}
|
|
@@ -72,8 +85,8 @@
|
|
|
72
85
|
.cz-banner .cz-idle{color:#64748b}
|
|
73
86
|
.cz-wrap{display:flex;flex-direction:column;align-items:flex-end;gap:6px}
|
|
74
87
|
.cz-wrap.cz-expanded{max-height:calc(100vh - 90px);overflow-y:auto;overflow-x:hidden;padding:2px}
|
|
75
|
-
.cz-wrap:not(.cz-expanded) > .cz-tweak:nth-child(n+
|
|
76
|
-
.cz-fade{height:
|
|
88
|
+
.cz-wrap:not(.cz-expanded) > .cz-tweak:nth-last-child(n+4){display:none}
|
|
89
|
+
.cz-fade{height:14px;width:160px;background:linear-gradient(to top,rgba(17,24,39,0),rgba(17,24,39,.5));pointer-events:none;margin-bottom:-8px}
|
|
77
90
|
.cz-history{background:#0b1220;color:#93c5fd;border:1px solid #263041;border-radius:8px;padding:4px 10px;font-size:11.5px;cursor:pointer;pointer-events:auto;box-shadow:0 4px 14px rgba(0,0,0,.3)}
|
|
78
91
|
.cz-history:hover{border-color:#6366f1}
|
|
79
92
|
.cz-tweak{background:#111827;color:#e5e7eb;border-radius:8px;padding:7px 11px;font-size:13px;display:flex;gap:8px;align-items:center;box-shadow:0 4px 14px rgba(0,0,0,.3);white-space:nowrap;width:max-content;max-width:720px;overflow:hidden;text-overflow:ellipsis}
|
|
@@ -104,8 +117,10 @@
|
|
|
104
117
|
model: 'auto', // NL model override; 'auto' = router picks
|
|
105
118
|
multi: [], // [{ el, loc }] shift-click multi-selection
|
|
106
119
|
autoReload: true, // seamlessly reload after a write so changes always show live
|
|
120
|
+
panelCollapsed: false, // left style panel minimized to a tab
|
|
107
121
|
};
|
|
108
122
|
try { state.autoReload = localStorage.getItem('cz-autoreload') !== '0'; } catch { /* no storage */ }
|
|
123
|
+
try { state.panelCollapsed = localStorage.getItem('cz-panel-collapsed') === '1'; } catch { /* no storage */ }
|
|
109
124
|
|
|
110
125
|
// Stack of undoable tweak ids (LIFO) for ⌘Z / Ctrl-Z global undo.
|
|
111
126
|
const undoStack = [];
|
|
@@ -230,11 +245,31 @@
|
|
|
230
245
|
const pop = el('div', 'cz-pop');
|
|
231
246
|
pop.style.display = 'none';
|
|
232
247
|
root.appendChild(pop);
|
|
233
|
-
// Left-side style inspector —
|
|
234
|
-
//
|
|
235
|
-
const
|
|
236
|
-
panel
|
|
248
|
+
// Left-side style inspector — a full-height dock that slides in from the left
|
|
249
|
+
// and pushes the page over (no overlap). Minimizable to a left-edge tab.
|
|
250
|
+
const PANEL_W = 264;
|
|
251
|
+
const panel = el('div', 'cz-panel cz-collapsed');
|
|
237
252
|
root.appendChild(panel);
|
|
253
|
+
const panelTab = el('button', 'cz-ptab');
|
|
254
|
+
panelTab.innerHTML = '<span>▸</span><span>STYLE</span>';
|
|
255
|
+
panelTab.title = 'Show style panel';
|
|
256
|
+
panelTab.onclick = () => setPanelCollapsed(false);
|
|
257
|
+
root.appendChild(panelTab);
|
|
258
|
+
try { document.body.style.transition = 'margin-left .26s cubic-bezier(.4,0,.2,1)'; } catch { /* ignore */ }
|
|
259
|
+
|
|
260
|
+
function setPanelCollapsed(v) {
|
|
261
|
+
state.panelCollapsed = v;
|
|
262
|
+
try { localStorage.setItem('cz-panel-collapsed', v ? '1' : '0'); } catch { /* no storage */ }
|
|
263
|
+
applyPanelLayout();
|
|
264
|
+
}
|
|
265
|
+
// Slide the panel in/out and push the page so nothing is covered.
|
|
266
|
+
function applyPanelLayout() {
|
|
267
|
+
const open = !!state.selected && !state.panelCollapsed;
|
|
268
|
+
panel.classList.toggle('cz-collapsed', !open);
|
|
269
|
+
panelTab.classList.toggle('cz-show', !!state.selected && state.panelCollapsed);
|
|
270
|
+
try { document.body.style.marginLeft = open ? PANEL_W + 'px' : '0px'; } catch { /* ignore */ }
|
|
271
|
+
}
|
|
272
|
+
|
|
238
273
|
const popLabel = el('div', 'cz-pop-label');
|
|
239
274
|
popLabel.style.display = 'none';
|
|
240
275
|
root.appendChild(popLabel);
|
|
@@ -351,7 +386,7 @@
|
|
|
351
386
|
grip.style.cursor = 'grabbing';
|
|
352
387
|
s.el.style.opacity = '0.45'; // show what's moving
|
|
353
388
|
// hide the panels/popover so they don't block the drop targets
|
|
354
|
-
pop.style.display = 'none';
|
|
389
|
+
pop.style.display = 'none'; popLabel.style.display = 'none'; // panel is docked left, doesn't block drop targets
|
|
355
390
|
};
|
|
356
391
|
|
|
357
392
|
const onMove = (ev) => {
|
|
@@ -439,7 +474,7 @@
|
|
|
439
474
|
state.selected = null;
|
|
440
475
|
selBox.style.display = 'none';
|
|
441
476
|
pop.style.display = 'none';
|
|
442
|
-
panel
|
|
477
|
+
applyPanelLayout(); // slides the panel out + un-shifts the page
|
|
443
478
|
popLabel.style.display = 'none';
|
|
444
479
|
deleteBtn.style.display = 'none';
|
|
445
480
|
moveBar.style.display = 'none';
|
|
@@ -511,10 +546,14 @@
|
|
|
511
546
|
multiBar._draft = '';
|
|
512
547
|
clearMulti();
|
|
513
548
|
for (const t of targets) {
|
|
549
|
+
const tempId = 'nl-' + Date.now() + t.loc;
|
|
550
|
+
addTweak({ id: tempId, status: 'queued', label: `${shortLoc(t.loc)}: ${instruction.slice(0, 40)}` });
|
|
514
551
|
try {
|
|
515
552
|
const r = await api('nl', { loc: t.loc, instruction, model: state.model });
|
|
553
|
+
removeTweak(tempId);
|
|
516
554
|
addTweak({ id: r.id, status: 'queued', model: r.model, label: `${shortLoc(t.loc)}: ${instruction.slice(0, 40)}` });
|
|
517
555
|
} catch (e) {
|
|
556
|
+
removeTweak(tempId);
|
|
518
557
|
addTweak({ id: 'x' + Date.now() + t.loc, status: 'error', label: `${shortLoc(t.loc)}: ${e.message}` });
|
|
519
558
|
}
|
|
520
559
|
}
|
|
@@ -826,10 +865,16 @@
|
|
|
826
865
|
// Left inspector panel: every style attribute broken out into its own control.
|
|
827
866
|
function renderPanel() {
|
|
828
867
|
const s = state.selected;
|
|
829
|
-
|
|
830
|
-
|
|
868
|
+
applyPanelLayout();
|
|
869
|
+
if (!s) return;
|
|
831
870
|
panel.textContent = '';
|
|
832
|
-
|
|
871
|
+
const head = el('div', 'cz-phead');
|
|
872
|
+
head.append(el('div', 'cz-ptitle', `<${s.el.tagName.toLowerCase()}> ${shortLoc(s.loc)}${s.instances > 1 ? ` · ${s.instances}×` : ''}`));
|
|
873
|
+
const min = el('button', 'cz-min', '‹');
|
|
874
|
+
min.title = 'Minimize panel';
|
|
875
|
+
min.onclick = () => setPanelCollapsed(true);
|
|
876
|
+
head.append(min);
|
|
877
|
+
panel.appendChild(head);
|
|
833
878
|
const tw = state.tailwind;
|
|
834
879
|
|
|
835
880
|
panel.appendChild(el('div', 'cz-sec', 'Spacing'));
|
|
@@ -921,7 +966,7 @@
|
|
|
921
966
|
|
|
922
967
|
const nlWrap = el('div', 'cz-nlwrap');
|
|
923
968
|
const input = el('textarea');
|
|
924
|
-
input.placeholder = 'Describe a change… (
|
|
969
|
+
input.placeholder = 'Describe a change… (↵ to send · ⇧↵ newline)';
|
|
925
970
|
input.rows = 3;
|
|
926
971
|
const go = el('button', 'cz-primary', 'Go →');
|
|
927
972
|
const send = async () => {
|
|
@@ -929,16 +974,23 @@
|
|
|
929
974
|
if (!instruction) return;
|
|
930
975
|
input.value = '';
|
|
931
976
|
input.blur(); // so the reload after the model finishes isn't blocked by focus
|
|
977
|
+
// Immediate feedback — the server runs the router (which may call the
|
|
978
|
+
// classifier for ~1-3s) before it broadcasts its own 'queued', so show a
|
|
979
|
+
// placeholder now and swap in the real task once the request returns.
|
|
980
|
+
const tempId = 'nl-' + Date.now();
|
|
981
|
+
addTweak({ id: tempId, status: 'queued', label: instruction.slice(0, 60) });
|
|
932
982
|
try {
|
|
933
983
|
const r = await api('nl', { loc: s.loc, instruction, model: state.model });
|
|
984
|
+
removeTweak(tempId);
|
|
934
985
|
addTweak({ id: r.id, status: 'queued', model: r.model, label: instruction.slice(0, 60) });
|
|
935
986
|
} catch (e) {
|
|
936
|
-
|
|
987
|
+
removeTweak(tempId);
|
|
988
|
+
addTweak({ id: 'x' + Date.now(), status: 'error', label: `${instruction.slice(0, 40)}: ${e.message}` });
|
|
937
989
|
}
|
|
938
990
|
};
|
|
939
991
|
go.onclick = send;
|
|
940
|
-
//
|
|
941
|
-
input.onkeydown = (e) => { if (e.key === 'Enter' &&
|
|
992
|
+
// Enter sends; Shift+Enter inserts a newline for multi-line prompts.
|
|
993
|
+
input.onkeydown = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } e.stopPropagation(); };
|
|
942
994
|
const goRow = el('div', 'cz-row');
|
|
943
995
|
goRow.style.justifyContent = 'flex-end';
|
|
944
996
|
goRow.appendChild(go);
|
|
@@ -1038,26 +1090,32 @@
|
|
|
1038
1090
|
banner.append(brand, bannerStats, reportLink);
|
|
1039
1091
|
root.appendChild(banner);
|
|
1040
1092
|
// Push the page down so the banner sits ABOVE the site rather than over it.
|
|
1093
|
+
// Inject a stylesheet instead of mutating body's style attribute: frameworks
|
|
1094
|
+
// that hydrate (React 19+) diff server-rendered attributes and flag a bare
|
|
1095
|
+
// style mutation as a hydration mismatch.
|
|
1041
1096
|
try {
|
|
1042
1097
|
const prevPad = getComputedStyle(document.body).paddingTop;
|
|
1043
|
-
|
|
1098
|
+
const pad = document.createElement('style');
|
|
1099
|
+
pad.setAttribute('data-cz-banner-pad', '');
|
|
1100
|
+
pad.textContent = `body { padding-top: calc(${prevPad} + 30px) !important; }`;
|
|
1101
|
+
document.head.appendChild(pad);
|
|
1044
1102
|
} catch { /* no body yet */ }
|
|
1045
1103
|
|
|
1046
1104
|
// ---------- tray ----------
|
|
1047
1105
|
const tray = el('div', 'cz-tray');
|
|
1048
1106
|
root.appendChild(tray);
|
|
1049
|
-
//
|
|
1050
|
-
//
|
|
1051
|
-
//
|
|
1052
|
-
const tweaksWrap = el('div', 'cz-wrap');
|
|
1053
|
-
tray.appendChild(tweaksWrap);
|
|
1054
|
-
const fade = el('div', 'cz-fade');
|
|
1055
|
-
fade.style.display = 'none';
|
|
1056
|
-
tray.appendChild(fade);
|
|
1107
|
+
// Reads like a log: newest alert sits at the bottom-right corner, older ones
|
|
1108
|
+
// stack upward. The 3 newest stay visible (compressed; hover to expand); the
|
|
1109
|
+
// rest fold behind an expander that sits ABOVE them.
|
|
1057
1110
|
const historyChip = el('div', 'cz-history');
|
|
1058
1111
|
historyChip.style.display = 'none';
|
|
1112
|
+
const tweaksWrap = el('div', 'cz-wrap');
|
|
1059
1113
|
historyChip.onclick = () => { tweaksWrap.classList.toggle('cz-expanded'); updateHistoryUI(); };
|
|
1060
1114
|
tray.appendChild(historyChip);
|
|
1115
|
+
const fade = el('div', 'cz-fade');
|
|
1116
|
+
fade.style.display = 'none';
|
|
1117
|
+
tray.appendChild(fade);
|
|
1118
|
+
tray.appendChild(tweaksWrap);
|
|
1061
1119
|
const tweaks = new Map();
|
|
1062
1120
|
const tweakData = new Map(); // id -> merged record, persisted across reloads
|
|
1063
1121
|
const HKEY = 'cz-history';
|
|
@@ -1066,8 +1124,8 @@
|
|
|
1066
1124
|
function updateHistoryUI() {
|
|
1067
1125
|
const rows = tweaksWrap.children.length;
|
|
1068
1126
|
const expanded = tweaksWrap.classList.contains('cz-expanded');
|
|
1069
|
-
const hidden = Math.max(0, rows -
|
|
1070
|
-
if (rows <=
|
|
1127
|
+
const hidden = Math.max(0, rows - 3);
|
|
1128
|
+
if (rows <= 3) { historyChip.style.display = 'none'; fade.style.display = 'none'; return; }
|
|
1071
1129
|
historyChip.style.display = '';
|
|
1072
1130
|
historyChip.textContent = expanded ? 'collapse ▴' : `+ ${hidden} older ▾`;
|
|
1073
1131
|
fade.style.display = expanded ? 'none' : '';
|
|
@@ -1113,6 +1171,15 @@
|
|
|
1113
1171
|
}
|
|
1114
1172
|
showTotals(null); // idle banner on load until /api/health arrives
|
|
1115
1173
|
|
|
1174
|
+
function removeTweak(id) {
|
|
1175
|
+
const key = String(id);
|
|
1176
|
+
const row = tweaks.get(key);
|
|
1177
|
+
if (row) row.remove();
|
|
1178
|
+
tweaks.delete(key);
|
|
1179
|
+
tweakData.delete(key);
|
|
1180
|
+
updateHistoryUI();
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1116
1183
|
function addTweak(t, hydrate) {
|
|
1117
1184
|
const key = String(t.id);
|
|
1118
1185
|
let row = tweaks.get(key);
|
|
@@ -1120,7 +1187,7 @@
|
|
|
1120
1187
|
row = el('div', 'cz-tweak');
|
|
1121
1188
|
row._id = key;
|
|
1122
1189
|
row._dot = el('span', 'cz-dot');
|
|
1123
|
-
row._label = el('span',
|
|
1190
|
+
row._label = el('span', 'cz-tlabel', '');
|
|
1124
1191
|
row._meta = el('span', 'cz-meta', '');
|
|
1125
1192
|
row._cancel = el('button', null, 'cancel');
|
|
1126
1193
|
row._cancel.style.display = 'none';
|
|
@@ -1138,13 +1205,13 @@
|
|
|
1138
1205
|
} catch (e) { row._meta.textContent = e.message; }
|
|
1139
1206
|
};
|
|
1140
1207
|
row.append(row._dot, row._label, row._meta, row._cancel, row._undo);
|
|
1141
|
-
tweaksWrap.
|
|
1208
|
+
tweaksWrap.appendChild(row); // newest at the end (bottom of the tray)
|
|
1142
1209
|
tweaks.set(key, row);
|
|
1143
1210
|
while (tweaksWrap.children.length > MAX_TWEAKS) {
|
|
1144
|
-
const
|
|
1145
|
-
tweaks.delete(
|
|
1146
|
-
tweakData.delete(
|
|
1147
|
-
|
|
1211
|
+
const oldest = tweaksWrap.firstChild;
|
|
1212
|
+
tweaks.delete(oldest._id);
|
|
1213
|
+
tweakData.delete(oldest._id);
|
|
1214
|
+
oldest.remove();
|
|
1148
1215
|
}
|
|
1149
1216
|
}
|
|
1150
1217
|
if (t.label) row._label.textContent = t.label;
|
|
@@ -1187,7 +1254,7 @@
|
|
|
1187
1254
|
(function hydrateTweaks() {
|
|
1188
1255
|
let saved;
|
|
1189
1256
|
try { saved = JSON.parse(localStorage.getItem(HKEY) || '[]'); } catch { saved = []; }
|
|
1190
|
-
for (const rec of saved) addTweak(rec, true); //
|
|
1257
|
+
for (const rec of saved) addTweak(rec, true); // chronological replay → newest ends at the bottom
|
|
1191
1258
|
})();
|
|
1192
1259
|
|
|
1193
1260
|
try {
|
|
@@ -1384,28 +1451,6 @@
|
|
|
1384
1451
|
else deselect();
|
|
1385
1452
|
}, true);
|
|
1386
1453
|
|
|
1387
|
-
// Alert tray dodges the cursor: when the pointer enters its region, slide it
|
|
1388
|
-
// off to the right so you can click the elements underneath; slide back once
|
|
1389
|
-
// the pointer leaves the original region (hysteresis avoids flicker).
|
|
1390
|
-
let trayDodged = false, trayZone = null;
|
|
1391
|
-
addEventListener('mousemove', (e) => {
|
|
1392
|
-
const x = e.clientX, y = e.clientY;
|
|
1393
|
-
if (!trayDodged) {
|
|
1394
|
-
if (!tweaksWrap.children.length) return; // nothing to dodge
|
|
1395
|
-
const r = tray.getBoundingClientRect();
|
|
1396
|
-
if (r.width && x >= r.left - 8 && x <= r.right + 8 && y >= r.top - 8 && y <= r.bottom + 8) {
|
|
1397
|
-
trayZone = r;
|
|
1398
|
-
trayDodged = true;
|
|
1399
|
-
tray.classList.add('cz-dodged');
|
|
1400
|
-
}
|
|
1401
|
-
} else {
|
|
1402
|
-
const r = trayZone, m = 34;
|
|
1403
|
-
if (!r || x < r.left - m || x > r.right + m || y < r.top - m || y > r.bottom + m) {
|
|
1404
|
-
trayDodged = false;
|
|
1405
|
-
tray.classList.remove('cz-dodged');
|
|
1406
|
-
}
|
|
1407
|
-
}
|
|
1408
|
-
}, true);
|
|
1409
1454
|
|
|
1410
1455
|
addEventListener('scroll', () => { reposition(); positionMulti(); setHover(state.hoverEl); }, true);
|
|
1411
1456
|
addEventListener('resize', () => { reposition(); positionMulti(); });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cmdzero",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Tweak your UI live in the browser and write the changes straight to source. Copy and Tailwind edits cost zero tokens; everything else routes to a right-sized model via headless claude.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/server.js
CHANGED
|
@@ -70,6 +70,9 @@ export function startServer({ root, port = 4100 }) {
|
|
|
70
70
|
try {
|
|
71
71
|
if (req.method === 'GET' && url.pathname === '/overlay.js') {
|
|
72
72
|
res.setHeader('Content-Type', 'text/javascript');
|
|
73
|
+
// Never cache the overlay — the daemon is the source of truth, so a
|
|
74
|
+
// reload always picks up the latest build (no stale-overlay confusion).
|
|
75
|
+
res.setHeader('Cache-Control', 'no-store, must-revalidate');
|
|
73
76
|
return res.end(fs.readFileSync(OVERLAY_PATH));
|
|
74
77
|
}
|
|
75
78
|
|