shadok-ai 0.2.9 → 0.2.11

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 +62 -27
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadok-ai",
3
- "version": "0.2.9",
3
+ "version": "0.2.11",
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
@@ -258,10 +258,8 @@
258
258
  .tab {
259
259
  position: relative;
260
260
  display: flex;
261
- /* Aligné en haut : led + icônes se posent sur la ligne du NOM (ligne 1),
262
- pas au centre des deux lignes. */
263
- align-items: flex-start;
264
- gap: 8px;
261
+ flex-direction: column; /* ligne 1 (led+nom+icônes) puis ligne 2 (infos) */
262
+ gap: 1px;
265
263
  font-family: var(--mono);
266
264
  font-size: 12px;
267
265
  color: var(--text-dim);
@@ -271,8 +269,7 @@
271
269
  cursor: pointer;
272
270
  user-select: none;
273
271
  }
274
- /* Recentre le point d'état sur la ligne du nom (sinon collé tout en haut). */
275
- .tab .led { margin-top: 4px; }
272
+ .tab-row1 { display: flex; align-items: center; gap: 8px; }
276
273
  /* Context-window usage: tiny number + thin bar at the bottom of the tab. */
277
274
  .tab .ctxnum { font-size: 9px; color: var(--text-dim); flex: none; }
278
275
  /* Heure du dernier message, discrète (le nom en flex:1 la pousse à droite). */
@@ -310,14 +307,12 @@
310
307
  border-radius: 0 4px 4px 0;
311
308
  box-shadow: inset 3px 0 0 var(--phosphor);
312
309
  }
313
- /* Nom + heure empilés : le titre sur sa ligne (jamais tronqué par l'heure),
314
- l'heure en dessous en tout petit. */
315
- .tab-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
316
- /* Ligne 2 : « xxx ago » + % de contexte, ensemble sous le nom. */
317
- .tab-info { display: flex; align-items: baseline; gap: 6px; }
318
- /* Le % de contexte poussé à droite de la ligne (« xxx ago » reste à gauche). */
319
- .tab-info .ctxnum { margin-left: auto; }
310
+ /* Ligne 2 pleine largeur : « xxx ago » à gauche, % de contexte tout à droite. */
311
+ .tab-row2 { display: flex; align-items: baseline; gap: 6px; }
312
+ .tab-row2 .ctxnum { margin-left: auto; }
320
313
  .tab .name {
314
+ flex: 1;
315
+ min-width: 0;
321
316
  overflow: hidden;
322
317
  text-overflow: ellipsis;
323
318
  white-space: nowrap;
@@ -1533,14 +1528,15 @@
1533
1528
  tg.innerHTML = '<svg viewBox="0 0 24 24" width="12" height="12" style="vertical-align:-1px" aria-hidden="true"><path fill="#26A5E4" d="M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z"/></svg>';
1534
1529
  tg.title = "Mirrored to Telegram";
1535
1530
  // Nom + heure empilés (l'heure SOUS le titre, pour ne pas le tronquer).
1536
- const mainCol = document.createElement("div");
1537
- mainCol.className = "tab-main";
1538
- // Ligne 2 sous le nom : heure relative « xxx ago » + % de contexte, ensemble.
1539
- const infoRow = document.createElement("div");
1540
- infoRow.className = "tab-info";
1541
- infoRow.append(lastAt, ctxNum);
1542
- mainCol.append(name, infoRow);
1543
- btn.append(led, mainCol, timer, mute, cron, tg, close);
1531
+ // Deux lignes : ligne 1 = led + nom + icônes ; ligne 2 PLEINE LARGEUR =
1532
+ // « xxx ago » à gauche, % de contexte tout à droite (plus caché par les icônes).
1533
+ const row1 = document.createElement("div");
1534
+ row1.className = "tab-row1";
1535
+ row1.append(led, name, timer, mute, cron, tg, close);
1536
+ const row2 = document.createElement("div");
1537
+ row2.className = "tab-row2";
1538
+ row2.append(lastAt, ctxNum);
1539
+ btn.append(row1, row2);
1544
1540
  // Context-window usage: a thin progress bar at the bottom of the tab.
1545
1541
  const ctxBar = document.createElement("div");
1546
1542
  ctxBar.className = "ctxbar";
@@ -1609,15 +1605,37 @@
1609
1605
 
1610
1606
  // Composer drafts, one per channel, remembered by session id.
1611
1607
  const savedDrafts = JSON.parse(localStorage.getItem("cp.drafts") || "{}");
1608
+ /** Écrit MAINTENANT le brouillon de cet onglet, sans attendre le débounce. */
1609
+ function writeDraft(tab) {
1610
+ if (!tab.sessionId) return;
1611
+ clearTimeout(tab.draftTimer);
1612
+ if (tab.draft) savedDrafts[tab.sessionId] = tab.draft;
1613
+ else delete savedDrafts[tab.sessionId];
1614
+ localStorage.setItem("cp.drafts", JSON.stringify(savedDrafts));
1615
+ }
1612
1616
  function persistDraft(tab) {
1613
1617
  if (!tab.sessionId) return;
1614
1618
  clearTimeout(tab.draftTimer);
1615
- tab.draftTimer = setTimeout(() => {
1616
- if (tab.draft) savedDrafts[tab.sessionId] = tab.draft;
1617
- else delete savedDrafts[tab.sessionId];
1618
- localStorage.setItem("cp.drafts", JSON.stringify(savedDrafts));
1619
- }, 300);
1619
+ tab.draftTimer = setTimeout(() => writeDraft(tab), 300);
1620
1620
  }
1621
+ /**
1622
+ * Vide les écritures en attente de TOUS les onglets.
1623
+ *
1624
+ * Le débounce se réarme à chaque frappe : tant qu'on écrit sans marquer 300 ms
1625
+ * de pause, rien n'était jamais écrit — un rechargement en plein élan (typique
1626
+ * de l'auto-update, qui tombe quand il tombe) emportait tout le message. On
1627
+ * force donc l'écriture au moment où la page s'en va.
1628
+ */
1629
+ function flushDrafts() {
1630
+ for (const t of tabs) writeDraft(t);
1631
+ }
1632
+ // `pagehide` couvre le rechargement, la navigation et la mise en bfcache ;
1633
+ // `visibilitychange` rattrape le mobile, où l'onglet peut être tué sans que
1634
+ // `pagehide` ne parte. localStorage étant synchrone, l'écriture aboutit.
1635
+ window.addEventListener("pagehide", flushDrafts);
1636
+ document.addEventListener("visibilitychange", () => {
1637
+ if (document.visibilityState === "hidden") flushDrafts();
1638
+ });
1621
1639
  function dropDraft(tab) {
1622
1640
  clearTimeout(tab.draftTimer);
1623
1641
  tab.draft = "";
@@ -2160,6 +2178,7 @@
2160
2178
  } else if (!dismissedChannels.has(c.sessionId)) {
2161
2179
  const nt = createTab(c.group);
2162
2180
  nt.sessionId = c.sessionId;
2181
+ nt.draft = savedDrafts[c.sessionId] || "";
2163
2182
  if (c.branch) nt.branch = c.branch;
2164
2183
  nt.profile = c.profile || null;
2165
2184
  setCrons(nt, c.crons);
@@ -3119,7 +3138,14 @@
3119
3138
 
3120
3139
  function handleVersion(msg) {
3121
3140
  if (bootVersion === null) bootVersion = msg.current;
3122
- else if (msg.current && msg.current !== bootVersion) { location.reload(); return; }
3141
+ else if (msg.current && msg.current !== bootVersion) {
3142
+ // Mise à jour : la page part maintenant. On écrit d'abord — c'est LE cas
3143
+ // où l'on perdait un message en cours de frappe, puisque le rechargement
3144
+ // n'est pas déclenché par l'utilisateur.
3145
+ flushDrafts();
3146
+ location.reload();
3147
+ return;
3148
+ }
3123
3149
  lastVersion = msg;
3124
3150
  if (!verView) return;
3125
3151
  const ahead = msg.latest && verIsNewer(msg.latest, msg.current);
@@ -3242,6 +3268,10 @@
3242
3268
  promptInput.value = t.draft;
3243
3269
  autosizePrompt();
3244
3270
  }
3271
+ } else if (t.draft) {
3272
+ // Tapé AVANT que la session ne soit connue : `persistDraft` refuse
3273
+ // d'écrire sans id, ce texte n'était donc dans aucun stockage.
3274
+ writeDraft(t);
3245
3275
  }
3246
3276
  }
3247
3277
  // launchTab reuses the tab object, so a bubble armed before the session
@@ -4398,6 +4428,11 @@
4398
4428
  // Know the session id from the start, so a slow or failed reattach never
4399
4429
  // drops the channel from a persist (which eroded the list to one).
4400
4430
  t.sessionId = s.sessionId;
4431
+ // Le brouillon revient DÈS la création de l'onglet, sans attendre le
4432
+ // `ready` : après une mise à jour le serveur redémarre, la reconnexion
4433
+ // prend plusieurs secondes, et l'utilisateur retrouvait un champ vide —
4434
+ // de quoi croire que son message était perdu et le retaper.
4435
+ t.draft = savedDrafts[s.sessionId] || "";
4401
4436
  if (s.branch) t.branch = s.branch;
4402
4437
  t.profile = s.profile || null;
4403
4438
  setCrons(t, s.crons);