cmdzero 0.6.0 → 0.7.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.
Files changed (2) hide show
  1. package/overlay/overlay.js +67 -42
  2. package/package.json +1 -1
@@ -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:12px;top:42px;width:238px;max-height:calc(100vh - 58px);overflow-y:auto;background:#111827;color:#f9fafb;border:1.5px solid #10b981;border-radius:10px;box-shadow:0 8px 30px rgba(0,0,0,.4);padding:9px;pointer-events:auto;display:flex;flex-direction:column;gap:6px;font-size:12px;z-index:2147483100}
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;padding-bottom:5px;border-bottom:1px solid #1f2937}
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;transition:transform .28s cubic-bezier(.4,0,.2,1),opacity .22s ease}
62
- .cz-tray.cz-dodged{transform:translateX(calc(100% + 28px));opacity:0;pointer-events:none}
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,7 +85,7 @@
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+6){display:none}
88
+ .cz-wrap:not(.cz-expanded) > .cz-tweak:nth-child(n+4){display:none}
76
89
  .cz-fade{height:16px;width:160px;background:linear-gradient(to bottom,rgba(17,24,39,0),rgba(17,24,39,.55));pointer-events:none;margin-top:-10px}
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}
@@ -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 — every style attribute broken out into its own
234
- // control, populated by renderPanel() when an element is selected.
235
- const panel = el('div', 'cz-panel');
236
- panel.style.display = 'none';
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'; panel.style.display = 'none'; popLabel.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.style.display = 'none';
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';
@@ -826,10 +861,16 @@
826
861
  // Left inspector panel: every style attribute broken out into its own control.
827
862
  function renderPanel() {
828
863
  const s = state.selected;
829
- if (!s) { panel.style.display = 'none'; return; }
830
- panel.style.display = 'flex';
864
+ applyPanelLayout();
865
+ if (!s) return;
831
866
  panel.textContent = '';
832
- panel.appendChild(el('div', 'cz-ptitle', `<${s.el.tagName.toLowerCase()}> ${shortLoc(s.loc)}${s.instances > 1 ? ` · ${s.instances}×` : ''}`));
867
+ const head = el('div', 'cz-phead');
868
+ head.append(el('div', 'cz-ptitle', `<${s.el.tagName.toLowerCase()}> ${shortLoc(s.loc)}${s.instances > 1 ? ` · ${s.instances}×` : ''}`));
869
+ const min = el('button', 'cz-min', '‹');
870
+ min.title = 'Minimize panel';
871
+ min.onclick = () => setPanelCollapsed(true);
872
+ head.append(min);
873
+ panel.appendChild(head);
833
874
  const tw = state.tailwind;
834
875
 
835
876
  panel.appendChild(el('div', 'cz-sec', 'Spacing'));
@@ -1038,17 +1079,23 @@
1038
1079
  banner.append(brand, bannerStats, reportLink);
1039
1080
  root.appendChild(banner);
1040
1081
  // Push the page down so the banner sits ABOVE the site rather than over it.
1082
+ // Inject a stylesheet instead of mutating body's style attribute: frameworks
1083
+ // that hydrate (React 19+) diff server-rendered attributes and flag a bare
1084
+ // style mutation as a hydration mismatch.
1041
1085
  try {
1042
1086
  const prevPad = getComputedStyle(document.body).paddingTop;
1043
- document.body.style.paddingTop = `calc(${prevPad} + 30px)`;
1087
+ const pad = document.createElement('style');
1088
+ pad.setAttribute('data-cz-banner-pad', '');
1089
+ pad.textContent = `body { padding-top: calc(${prevPad} + 30px) !important; }`;
1090
+ document.head.appendChild(pad);
1044
1091
  } catch { /* no body yet */ }
1045
1092
 
1046
1093
  // ---------- tray ----------
1047
1094
  const tray = el('div', 'cz-tray');
1048
1095
  root.appendChild(tray);
1049
1096
  // Alerts live in their own wrap so history can collapse independently.
1050
- // Newest is inserted at the top; the 5 newest stay visible and
1051
- // older ones fold behind an expander (fades at the bottom).
1097
+ // Newest is inserted at the top; the 3 newest stay visible (compressed; hover to expand) and older
1098
+ // ones fold behind an expander.
1052
1099
  const tweaksWrap = el('div', 'cz-wrap');
1053
1100
  tray.appendChild(tweaksWrap);
1054
1101
  const fade = el('div', 'cz-fade');
@@ -1066,8 +1113,8 @@
1066
1113
  function updateHistoryUI() {
1067
1114
  const rows = tweaksWrap.children.length;
1068
1115
  const expanded = tweaksWrap.classList.contains('cz-expanded');
1069
- const hidden = Math.max(0, rows - 5);
1070
- if (rows <= 5) { historyChip.style.display = 'none'; fade.style.display = 'none'; return; }
1116
+ const hidden = Math.max(0, rows - 3);
1117
+ if (rows <= 3) { historyChip.style.display = 'none'; fade.style.display = 'none'; return; }
1071
1118
  historyChip.style.display = '';
1072
1119
  historyChip.textContent = expanded ? 'collapse ▴' : `+ ${hidden} older ▾`;
1073
1120
  fade.style.display = expanded ? 'none' : '';
@@ -1120,7 +1167,7 @@
1120
1167
  row = el('div', 'cz-tweak');
1121
1168
  row._id = key;
1122
1169
  row._dot = el('span', 'cz-dot');
1123
- row._label = el('span', null, '');
1170
+ row._label = el('span', 'cz-tlabel', '');
1124
1171
  row._meta = el('span', 'cz-meta', '');
1125
1172
  row._cancel = el('button', null, 'cancel');
1126
1173
  row._cancel.style.display = 'none';
@@ -1384,28 +1431,6 @@
1384
1431
  else deselect();
1385
1432
  }, true);
1386
1433
 
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
1434
 
1410
1435
  addEventListener('scroll', () => { reposition(); positionMulti(); setHover(state.hoverEl); }, true);
1411
1436
  addEventListener('resize', () => { reposition(); positionMulti(); });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cmdzero",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
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",