cmdzero 0.7.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 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.
@@ -85,8 +85,8 @@
85
85
  .cz-banner .cz-idle{color:#64748b}
86
86
  .cz-wrap{display:flex;flex-direction:column;align-items:flex-end;gap:6px}
87
87
  .cz-wrap.cz-expanded{max-height:calc(100vh - 90px);overflow-y:auto;overflow-x:hidden;padding:2px}
88
- .cz-wrap:not(.cz-expanded) > .cz-tweak:nth-child(n+4){display:none}
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}
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}
90
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)}
91
91
  .cz-history:hover{border-color:#6366f1}
92
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}
@@ -546,10 +546,14 @@
546
546
  multiBar._draft = '';
547
547
  clearMulti();
548
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)}` });
549
551
  try {
550
552
  const r = await api('nl', { loc: t.loc, instruction, model: state.model });
553
+ removeTweak(tempId);
551
554
  addTweak({ id: r.id, status: 'queued', model: r.model, label: `${shortLoc(t.loc)}: ${instruction.slice(0, 40)}` });
552
555
  } catch (e) {
556
+ removeTweak(tempId);
553
557
  addTweak({ id: 'x' + Date.now() + t.loc, status: 'error', label: `${shortLoc(t.loc)}: ${e.message}` });
554
558
  }
555
559
  }
@@ -962,7 +966,7 @@
962
966
 
963
967
  const nlWrap = el('div', 'cz-nlwrap');
964
968
  const input = el('textarea');
965
- input.placeholder = 'Describe a change… (⌘↵ to send)';
969
+ input.placeholder = 'Describe a change… ( to send · ⇧↵ newline)';
966
970
  input.rows = 3;
967
971
  const go = el('button', 'cz-primary', 'Go →');
968
972
  const send = async () => {
@@ -970,16 +974,23 @@
970
974
  if (!instruction) return;
971
975
  input.value = '';
972
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) });
973
982
  try {
974
983
  const r = await api('nl', { loc: s.loc, instruction, model: state.model });
984
+ removeTweak(tempId);
975
985
  addTweak({ id: r.id, status: 'queued', model: r.model, label: instruction.slice(0, 60) });
976
986
  } catch (e) {
977
- addTweak({ id: 'x' + Date.now(), status: 'error', label: e.message });
987
+ removeTweak(tempId);
988
+ addTweak({ id: 'x' + Date.now(), status: 'error', label: `${instruction.slice(0, 40)}: ${e.message}` });
978
989
  }
979
990
  };
980
991
  go.onclick = send;
981
- // ⌘↵ / Ctrl↵ sends; plain Enter inserts a newline (multi-line prompts).
982
- input.onkeydown = (e) => { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) { e.preventDefault(); send(); } e.stopPropagation(); };
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(); };
983
994
  const goRow = el('div', 'cz-row');
984
995
  goRow.style.justifyContent = 'flex-end';
985
996
  goRow.appendChild(go);
@@ -1093,18 +1104,18 @@
1093
1104
  // ---------- tray ----------
1094
1105
  const tray = el('div', 'cz-tray');
1095
1106
  root.appendChild(tray);
1096
- // Alerts live in their own wrap so history can collapse independently.
1097
- // Newest is inserted at the top; the 3 newest stay visible (compressed; hover to expand) and older
1098
- // ones fold behind an expander.
1099
- const tweaksWrap = el('div', 'cz-wrap');
1100
- tray.appendChild(tweaksWrap);
1101
- const fade = el('div', 'cz-fade');
1102
- fade.style.display = 'none';
1103
- 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.
1104
1110
  const historyChip = el('div', 'cz-history');
1105
1111
  historyChip.style.display = 'none';
1112
+ const tweaksWrap = el('div', 'cz-wrap');
1106
1113
  historyChip.onclick = () => { tweaksWrap.classList.toggle('cz-expanded'); updateHistoryUI(); };
1107
1114
  tray.appendChild(historyChip);
1115
+ const fade = el('div', 'cz-fade');
1116
+ fade.style.display = 'none';
1117
+ tray.appendChild(fade);
1118
+ tray.appendChild(tweaksWrap);
1108
1119
  const tweaks = new Map();
1109
1120
  const tweakData = new Map(); // id -> merged record, persisted across reloads
1110
1121
  const HKEY = 'cz-history';
@@ -1160,6 +1171,15 @@
1160
1171
  }
1161
1172
  showTotals(null); // idle banner on load until /api/health arrives
1162
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
+
1163
1183
  function addTweak(t, hydrate) {
1164
1184
  const key = String(t.id);
1165
1185
  let row = tweaks.get(key);
@@ -1185,13 +1205,13 @@
1185
1205
  } catch (e) { row._meta.textContent = e.message; }
1186
1206
  };
1187
1207
  row.append(row._dot, row._label, row._meta, row._cancel, row._undo);
1188
- tweaksWrap.insertBefore(row, tweaksWrap.firstChild);
1208
+ tweaksWrap.appendChild(row); // newest at the end (bottom of the tray)
1189
1209
  tweaks.set(key, row);
1190
1210
  while (tweaksWrap.children.length > MAX_TWEAKS) {
1191
- const last = tweaksWrap.lastChild;
1192
- tweaks.delete(last._id);
1193
- tweakData.delete(last._id);
1194
- last.remove();
1211
+ const oldest = tweaksWrap.firstChild;
1212
+ tweaks.delete(oldest._id);
1213
+ tweakData.delete(oldest._id);
1214
+ oldest.remove();
1195
1215
  }
1196
1216
  }
1197
1217
  if (t.label) row._label.textContent = t.label;
@@ -1234,7 +1254,7 @@
1234
1254
  (function hydrateTweaks() {
1235
1255
  let saved;
1236
1256
  try { saved = JSON.parse(localStorage.getItem(HKEY) || '[]'); } catch { saved = []; }
1237
- for (const rec of saved) addTweak(rec, true); // oldest→newest keeps newest on top
1257
+ for (const rec of saved) addTweak(rec, true); // chronological replay newest ends at the bottom
1238
1258
  })();
1239
1259
 
1240
1260
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cmdzero",
3
- "version": "0.7.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