shadok-ai 0.2.32 → 0.2.34

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/package.json +1 -1
  2. package/public/index.html +24 -10
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadok-ai",
3
- "version": "0.2.32",
3
+ "version": "0.2.34",
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
@@ -4,7 +4,7 @@
4
4
  <meta charset="utf-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
6
  <title>shadok-ai — cockpit</title>
7
- <link rel="icon" id="favicon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%2314161d'/%3E%3Ctext x='15' y='23' font-family='monospace' font-size='20' fill='%23f0a848' text-anchor='middle'%3E%E2%80%BA%3C/text%3E%3C/svg%3E">
7
+ <link rel="icon" id="favicon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='7' fill='%2314161d'/%3E%3Ctext x='16' y='25' font-family='monospace' font-size='26' font-weight='700' fill='%23f0a848' text-anchor='middle'%3E%E2%80%BA%3C/text%3E%3C/svg%3E">
8
8
  <style>
9
9
  :root {
10
10
  --bg: #14161d;
@@ -2486,22 +2486,25 @@
2486
2486
  const favicon = $("favicon");
2487
2487
  let titleBase = BASE_TITLE; // current title without the notification badge
2488
2488
  /** SVG data-URI favicon: the base mark, plus a coloured corner pip if given. */
2489
- function faviconSVG(dot) {
2490
- const pip = dot ? `<circle cx="24" cy="8" r="7" fill="${dot}"/>` : "";
2489
+ /** L'indicateur remplit TOUT le favicon : au repos, fond sombre + chevron
2490
+ * ambre ; pour un état (rouge = à faire, ambre = à lire) tout le carré prend
2491
+ * la couleur, chevron sombre par-dessus. Bien plus visible qu'une pastille. */
2492
+ function faviconSVG(bg) {
2491
2493
  const svg =
2492
2494
  `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">` +
2493
- `<rect width="32" height="32" rx="7" fill="#14161d"/>` +
2494
- `<text x="15" y="23" font-family="monospace" font-size="20" fill="#f0a848" text-anchor="middle">›</text>` +
2495
- pip + `</svg>`;
2495
+ `<rect width="32" height="32" rx="7" fill="${bg || "#14161d"}"/>` +
2496
+ `<text x="16" y="25" font-family="monospace" font-size="26" font-weight="700" fill="${bg ? "#14161d" : "#f0a848"}" text-anchor="middle">›</text>` +
2497
+ `</svg>`;
2496
2498
  return "data:image/svg+xml," + encodeURIComponent(svg);
2497
2499
  }
2498
- /** Favicon « en train de bosser » : la marque + un arc qui tourne (angle animé). */
2500
+ /** Favicon « en train de bosser » : un GROS spinner centré qui remplit le
2501
+ * favicon (piste faible + arc ambre qui tourne). */
2499
2502
  function faviconWorkingSVG(angle) {
2500
2503
  const svg =
2501
2504
  `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">` +
2502
2505
  `<rect width="32" height="32" rx="7" fill="#14161d"/>` +
2503
- `<text x="15" y="23" font-family="monospace" font-size="20" fill="#f0a848" text-anchor="middle">›</text>` +
2504
- `<g transform="rotate(${angle} 24 8)"><circle cx="24" cy="8" r="6" fill="none" stroke="#f0a848" stroke-width="2.5" stroke-linecap="round" stroke-dasharray="26 40"/></g>` +
2506
+ `<circle cx="16" cy="16" r="11" fill="none" stroke="rgba(240,168,72,0.22)" stroke-width="4"/>` +
2507
+ `<g transform="rotate(${angle} 16 16)"><circle cx="16" cy="16" r="11" fill="none" stroke="#f0a848" stroke-width="4" stroke-linecap="round" stroke-dasharray="34 100"/></g>` +
2505
2508
  `</svg>`;
2506
2509
  return "data:image/svg+xml," + encodeURIComponent(svg);
2507
2510
  }
@@ -3589,6 +3592,11 @@
3589
3592
  setTabState(t, "busy", "responding…");
3590
3593
  clearLivePreview(t);
3591
3594
  t.finalizedNorms = [];
3595
+ // Aucun texte visible produit pour l'instant ce tour. Sert au turn-done à
3596
+ // décider s'il y a vraiment « à lire » : un tour qui ne sort que
3597
+ // NOTHING TO SHOW (filtré par le tail → aucun stream-text) ne doit pas
3598
+ // marquer le canal comme non-lu.
3599
+ t.turnHadText = false;
3592
3600
  break;
3593
3601
  case "prompt-echo":
3594
3602
  // Prompt sent by another tab/interface — or by the server itself
@@ -3625,6 +3633,9 @@
3625
3633
  if (fin) (t.finalizedNorms ||= []).push(fin);
3626
3634
  }
3627
3635
  clearLivePreview(t);
3636
+ // Du texte visible est arrivé (NOTHING TO SHOW est filtré par le tail
3637
+ // avant d'arriver ici) → il y a vraiment quelque chose à lire.
3638
+ t.turnHadText = true;
3628
3639
  // Repli sur l'heure locale si le serveur n'a pas envoyé `at` (transcript
3629
3640
  // sans timestamp) : un bloc en direct vient d'être écrit, l'écart est nul.
3630
3641
  addTurn(t, "claude", "claude", msg.text, "live", msg.at ?? Date.now());
@@ -3648,7 +3659,10 @@
3648
3659
  persistChannels();
3649
3660
  }
3650
3661
  stopTurnTimer(t);
3651
- setTabMood(t, t === active ? null : "unread");
3662
+ // « unread » seulement si le tour a produit du texte visible. Un tour
3663
+ // vide (ex. cron qui conclut NOTHING TO SHOW) ne réclame aucune lecture,
3664
+ // ni sur l'onglet ni sur le favicon.
3665
+ setTabMood(t, (t === active || !t.turnHadText) ? null : "unread");
3652
3666
  setTabState(t, "ready", "ready");
3653
3667
  break;
3654
3668
  case "dialog":