@agentprojectcontext/apx 1.41.0 → 1.42.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentprojectcontext/apx",
3
- "version": "1.41.0",
3
+ "version": "1.42.0",
4
4
  "description": "APX — unified CLI + daemon for the Agent Project Context (APC) standard.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -132,9 +132,8 @@
132
132
  <div class="center" id="cap-center"></div>
133
133
  <div class="cap-actions" id="cap-actions"></div>
134
134
  </div>
135
- <div id="caption-slot"></div>
136
135
  <div id="conv-slot"></div>
137
- <div id="session-slot"></div>
136
+ <div id="footer-slot"></div>
138
137
  `;
139
138
  const $cap = $root.querySelector("#cap");
140
139
  const $capBadge = $root.querySelector("#cap-badge");
@@ -142,9 +141,14 @@
142
141
  const $badgeTxt = $root.querySelector("#badge-txt");
143
142
  const $capCenter = $root.querySelector("#cap-center");
144
143
  const $capActions = $root.querySelector("#cap-actions");
145
- const $captionSlot = $root.querySelector("#caption-slot");
146
144
  const $convSlot = $root.querySelector("#conv-slot");
147
- const $sessionSlot = $root.querySelector("#session-slot");
145
+ const $footerSlot = $root.querySelector("#footer-slot");
146
+
147
+ // Footer (hint + session actions) state: the shortcut to display and a
148
+ // signature guard so render() doesn't rebuild the bar (and re-trigger the
149
+ // pill animation) on every keystroke.
150
+ let captionShortcut = "CommandOrControl+G";
151
+ let footerSig = "";
148
152
 
149
153
  $badgeMic.innerHTML = ICON.mic();
150
154
  $badgeTxt.innerHTML = ICON.text();
@@ -172,7 +176,7 @@
172
176
  }
173
177
  document.documentElement.setAttribute("data-theme", theme);
174
178
  setPosition(position);
175
- initialCaption(shortcut);
179
+ captionShortcut = shortcut || "CommandOrControl+G";
176
180
  configReady = true;
177
181
  // If render() already fired the bootstrap paint (because the IPC was
178
182
  // slow), the existing input has the stale placeholder. Patch it in
@@ -184,7 +188,7 @@
184
188
  }).catch(() => {
185
189
  document.documentElement.setAttribute("data-theme", "light");
186
190
  setPosition("right");
187
- initialCaption("CommandOrControl+G");
191
+ captionShortcut = "CommandOrControl+G";
188
192
  configReady = true;
189
193
  render();
190
194
  });
@@ -208,19 +212,36 @@
208
212
  .replace(/\+/g, "");
209
213
  }
210
214
 
211
- function initialCaption(shortcut) {
212
- const sc = formatShortcut(shortcut);
213
- // Empty-idle state has no other affordance to dismiss the floating window
214
- // (the session bar's "Cerrar" only shows once a conversation exists, and
215
- // the window doesn't auto-hide on blur). Pair the hint with a translucent
216
- // "Cerrar ventana" pill in the same glass language so the user can always
217
- // close it without hunting for the tray icon.
218
- $captionSlot.innerHTML = `
219
- <div class="caption">Mantené <span class="kbd">${sc}</span> para hablar
220
- <span class="kbd">⌥ /</span> para escribir</div>
221
- <button class="cap-pill" id="btn-close-idle" title="Cerrar ventana">${ICON.close()}<span>Cerrar ventana</span></button>
215
+ function captionHtml() {
216
+ const sc = formatShortcut(captionShortcut);
217
+ return `<div class="caption">Mantené <span class="kbd">${sc}</span> para hablar
218
+ <span class="kbd">⌥ /</span> para escribir</div>`;
219
+ }
220
+
221
+ // Unified bottom bar: the "Mantené ⌘G…" hint on the LEFT, session actions on
222
+ // the RIGHT — small translucent pills in the same glass language as the hint
223
+ // (not the old big white buttons). "Cerrar ventana" is always present so the
224
+ // empty-idle window can be dismissed; "Nueva sesión" joins once a session
225
+ // exists. The conversation card sits above this bar (input → messages → bar).
226
+ function renderFooter() {
227
+ const hasSession = messages.length > 0;
228
+ // "Cerrar ventana" while it's alone (empty state); shortened to "Cerrar"
229
+ // once "Nueva sesión" joins so all three pills fit on one line.
230
+ const closeLabel = hasSession ? "Cerrar" : "Cerrar ventana";
231
+ const sig = `${hasSession}|${captionShortcut}`;
232
+ if (sig === footerSig && $footerSlot.firstChild) return; // no churn per keystroke
233
+ footerSig = sig;
234
+ $footerSlot.innerHTML = `
235
+ <div class="footer-bar">
236
+ ${captionHtml()}
237
+ <div class="footer-actions">
238
+ ${hasSession ? `<button class="cap-pill act-new" id="btn-new"><span class="ic">${ICON.plus()}</span> Nueva sesión</button>` : ""}
239
+ <button class="cap-pill act-close" id="btn-close"><span class="ic">${ICON.close()}</span> ${closeLabel}</button>
240
+ </div>
241
+ </div>
222
242
  `;
223
- $captionSlot.querySelector("#btn-close-idle")?.addEventListener("click", closeWindow);
243
+ $footerSlot.querySelector("#btn-new")?.addEventListener("click", newSession);
244
+ $footerSlot.querySelector("#btn-close")?.addEventListener("click", closeWindow);
224
245
  }
225
246
 
226
247
  // ── Render: capsule center + actions vary by mode ────────────────────────
@@ -345,15 +366,9 @@
345
366
  addBtn("ghost", "Detener", ICON.stop(), () => stopSpeaking());
346
367
  }
347
368
 
348
- // caption visible only when idle AND no messages yet
349
- $captionSlot.style.display = (mode === "idle" && messages.length === 0) ? "" : "none";
350
-
351
- // session bar visible when there are messages
352
- if (messages.length > 0 || mode !== "idle") {
353
- renderSessionBar();
354
- } else {
355
- $sessionSlot.innerHTML = "";
356
- }
369
+ // Unified bottom bar (hint on the left + session actions on the right) —
370
+ // always present, so there's always a way to close the window.
371
+ renderFooter();
357
372
 
358
373
  // conv card visible when there's any content
359
374
  const wantConv = messages.length > 0 || mode === "transcribing" || mode === "thinking" || mode === "speaking";
@@ -363,18 +378,6 @@
363
378
  requestWindowResize();
364
379
  }
365
380
 
366
- function renderSessionBar() {
367
- if ($sessionSlot.querySelector(".session-bar")) return; // keep DOM stable
368
- $sessionSlot.innerHTML = `
369
- <div class="session-bar">
370
- <button class="sbtn new" id="btn-new"><span class="ic">${ICON.plus()}</span> Nueva sesión</button>
371
- <button class="sbtn close" id="btn-close"><span class="ic">${ICON.close()}</span> Cerrar</button>
372
- </div>
373
- `;
374
- $sessionSlot.querySelector("#btn-new").addEventListener("click", newSession);
375
- $sessionSlot.querySelector("#btn-close").addEventListener("click", closeWindow);
376
- }
377
-
378
381
  // ── Conversation card ────────────────────────────────────────────────────
379
382
  let $convScroll = null;
380
383
  function ensureConv() {
@@ -1122,9 +1125,8 @@
1122
1125
  toolPillsByName = {};
1123
1126
  pendingUserText = "";
1124
1127
  $convSlot.innerHTML = "";
1125
- $sessionSlot.innerHTML = "";
1126
1128
  mode = "idle";
1127
- render();
1129
+ render(); // footer rebuilds (drops "Nueva sesión" now that there's no session)
1128
1130
  }
1129
1131
  function closeWindow() { window.apx?.close?.(); }
1130
1132
 
@@ -193,12 +193,17 @@ button { font-family: inherit; }
193
193
  }
194
194
  [data-theme="dark"] .kbd { color: color-mix(in oklch, var(--accent) 60%, white); }
195
195
 
196
- /* Stack the hint + the close pill, centered, in the empty-idle state.
197
- render() toggles this slot's display between "" ( flex) and "none". */
198
- #caption-slot { display: flex; flex-direction: column; align-items: center; gap: 7px; }
196
+ /* Bottom bar: the "Mantené ⌘G…" hint on the left, small session-action pills
197
+ on the right. The conversation card (when present) sits above this bar. */
198
+ .footer-bar {
199
+ display: flex; align-items: center; justify-content: space-between;
200
+ gap: 10px; flex-wrap: wrap;
201
+ }
202
+ .footer-bar .caption { align-self: auto; }
203
+ .footer-actions { display: flex; align-items: center; gap: 7px; flex-shrink: 0; margin-left: auto; }
199
204
 
200
- /* Translucent "Cerrar ventana" pill — same glass language as .caption so the
201
- empty-idle state always has a way to dismiss the window. */
205
+ /* Translucent action pill — same glass language as .caption so the bar reads
206
+ as one hint layer rather than the old big white buttons. */
202
207
  .cap-pill {
203
208
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
204
209
  font: inherit; font-size: 11px; font-weight: 600; color: rgba(255,255,255,.92);
@@ -206,12 +211,16 @@ button { font-family: inherit; }
206
211
  background: rgba(20,22,32,.28); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
207
212
  border: 1px solid rgba(255,255,255,.14);
208
213
  text-shadow: 0 0.5px 1.5px rgba(0,0,0,.3);
209
- transition: transform .14s ease, background .15s ease;
214
+ transition: transform .14s ease, background .15s ease, border-color .15s ease, color .15s ease;
210
215
  animation: captionIn .4s ease both;
216
+ white-space: nowrap;
211
217
  }
218
+ .cap-pill .ic { display: inline-flex; align-items: center; }
212
219
  .cap-pill svg { width: 13px; height: 13px; opacity: .9; }
213
220
  .cap-pill:hover { transform: translateY(-1px); background: rgba(20,22,32,.42); }
214
221
  .cap-pill:active { transform: scale(.96); }
222
+ .cap-pill.act-new:hover { border-color: color-mix(in oklch, var(--accent) 55%, transparent); }
223
+ .cap-pill.act-close:hover { border-color: rgba(255,120,110,.5); color: #fff; }
215
224
  [data-theme="dark"] .cap-pill { background: rgba(0,0,0,.34); }
216
225
 
217
226
  /* ============================================================