shadok-ai 0.2.4 → 0.2.6

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": "shadok-ai",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "main": "dist/session.js",
5
5
  "scripts": {
6
6
  "test": "node --import tsx --test test/*.ts .claude/skills/shadok-ai-agents/test/*.test.mjs",
package/public/index.html CHANGED
@@ -1519,7 +1519,7 @@
1519
1519
  infoRow.className = "tab-info";
1520
1520
  infoRow.append(lastAt, ctxNum);
1521
1521
  mainCol.append(name, infoRow);
1522
- btn.append(led, mainCol, mute, cron, tg, timer, close);
1522
+ btn.append(led, mainCol, timer, mute, cron, tg, close);
1523
1523
  // Context-window usage: a thin progress bar at the bottom of the tab.
1524
1524
  const ctxBar = document.createElement("div");
1525
1525
  ctxBar.className = "ctxbar";
@@ -2027,7 +2027,11 @@
2027
2027
  // A NEW "agent is blocked" in a channel you're not already looking at →
2028
2028
  // chime once. (Not on unread, not on re-renders of an existing question,
2029
2029
  // never for a muted channel.)
2030
- if (mood === "needs-answer" && !wasNeeds && !tab.muted && (document.hidden || tab !== active)) ding();
2030
+ // Même correction que pour le clignotement : « pas devant » inclut la
2031
+ // fenêtre affichée mais sans focus, sinon un agent qui se bloque dans le
2032
+ // canal actif pendant que tu es dans ton terminal ne fait aucun bruit.
2033
+ const away = document.hidden || !document.hasFocus();
2034
+ if (mood === "needs-answer" && !wasNeeds && !tab.muted && (away || tab !== active)) ding();
2031
2035
  refreshBadge();
2032
2036
  }
2033
2037
 
@@ -2229,8 +2233,12 @@
2229
2233
  document.title = titleBase;
2230
2234
  return;
2231
2235
  }
2236
+ // "Ailleurs" = onglet caché OU fenêtre sans focus. document.hidden seul ne
2237
+ // suffit pas : il reste faux tant que la fenêtre est affichée, même si tu
2238
+ // travailles dans une autre application — l'usage normal du cockpit. Avec
2239
+ // lui seul, le clignotement ne se déclenchait jamais quand il servait.
2232
2240
  const s = window.notifyState(attentionInput(), {
2233
- hidden: document.hidden,
2241
+ away: document.hidden || !document.hasFocus(),
2234
2242
  phase: blinkPhase,
2235
2243
  });
2236
2244
  favicon.href = faviconSVG(s.color);
@@ -2244,8 +2252,13 @@
2244
2252
  }
2245
2253
  }
2246
2254
  function refreshBadge() { paint(); }
2247
- // Coming back to the tab must stop the blink at once, not at the next tick.
2248
- document.addEventListener("visibilitychange", () => { blinkPhase = 0; paint(); });
2255
+ // Coming back must stop the blink at once, not at the next tick. Both signals
2256
+ // matter: switching tabs fires visibilitychange, switching application only
2257
+ // fires focus/blur.
2258
+ const repaintOnReturn = () => { blinkPhase = 0; paint(); };
2259
+ document.addEventListener("visibilitychange", repaintOnReturn);
2260
+ window.addEventListener("focus", repaintOnReturn);
2261
+ window.addEventListener("blur", repaintOnReturn);
2249
2262
  // The bridge is ready by DOMContentLoaded: paint once for real.
2250
2263
  document.addEventListener("DOMContentLoaded", () => paint());
2251
2264
 
package/public/notify.js CHANGED
@@ -23,6 +23,13 @@ export const BLINK_MS = 900;
23
23
  /**
24
24
  * L'état d'attention agrégé des canaux.
25
25
  *
26
+ * `away` = l'utilisateur n'est pas sur la page. La v1 lisait `document.hidden`,
27
+ * qui ne dit PAS « tu regardes ailleurs » : il est faux tant que la fenêtre
28
+ * reste affichée, même si une autre application a le focus. Or c'est l'usage
29
+ * normal du cockpit — fenêtre ouverte sur un écran, utilisateur dans son
30
+ * terminal — donc le clignotement ne se déclenchait jamais là où il servait.
31
+ * L'appelant compose désormais visibilité ET focus.
32
+ *
26
33
  * `phase` alterne 0/1 au rythme du clignotement. Les deux phases renvoient
27
34
  * toujours une couleur ET un badge : Chrome étrangle les timers d'un onglet
28
35
  * caché (jusqu'à un réveil par minute après ~5 min), et un on/off gelé sur
@@ -30,11 +37,11 @@ export const BLINK_MS = 900;
30
37
  * pire cas est un clignotement lent.
31
38
  *
32
39
  * @param {Array<{mood?: string|null, muted?: boolean}>} channels
33
- * @param {{hidden: boolean, phase: number}} view
40
+ * @param {{away: boolean, phase: number}} view
34
41
  * @returns {{color: string|null, badge: string, blink: boolean}}
35
42
  */
36
43
  export function notifyState(channels, view) {
37
- const hidden = !!(view && view.hidden);
44
+ const away = !!(view && view.away);
38
45
  const phase = view && view.phase ? 1 : 0;
39
46
 
40
47
  let blocked = false;
@@ -49,7 +56,7 @@ export function notifyState(channels, view) {
49
56
 
50
57
  // Seul un agent bloqué justifie de clignoter : une réponse non lue signale
51
58
  // qu'il s'est passé quelque chose, pas qu'on attend après toi.
52
- const blink = blocked && hidden;
59
+ const blink = blocked && away;
53
60
  if (!blink) return { color: blocked ? RED : AMBER, badge: "● ", blink: false };
54
61
  return phase
55
62
  ? { color: RED_DIM, badge: "◉ ", blink: true }