shadok-ai 0.12.6 → 0.12.8

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 +56 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadok-ai",
3
- "version": "0.12.6",
3
+ "version": "0.12.8",
4
4
  "main": "dist/session.js",
5
5
  "scripts": {
6
6
  "test": "node --import tsx --test test/*.ts context/*/test/*.test.mjs",
package/public/index.html CHANGED
@@ -504,7 +504,19 @@
504
504
  border-radius: 0 4px 4px 0;
505
505
  color: var(--text);
506
506
  }
507
- @media (prefers-reduced-motion: reduce) { .tab.working { animation: none; } }
507
+ /* Background work still running (shells and/or monitors the agent left going,
508
+ the ⏳/👁 on the row): a green diagonal HATCH that breathes, the way
509
+ .working breathes amber — so a tab with something ticking in the background
510
+ reads at a glance, distinct from the solid fill of an active turn. Layered as
511
+ a background-IMAGE, not the `background` shorthand, so a tab that is ALSO
512
+ working / needs-answer / unread keeps its state colour underneath the hatch. */
513
+ .tab.has-bg {
514
+ background-image: repeating-linear-gradient(45deg,
515
+ color-mix(in srgb, var(--ok) 22%, transparent) 0 5px,
516
+ transparent 5px 10px);
517
+ animation: breathe 1.8s ease-in-out infinite;
518
+ }
519
+ @media (prefers-reduced-motion: reduce) { .tab.working, .tab.has-bg { animation: none; } }
508
520
  #newTab, #newGroup {
509
521
  margin-top: 8px;
510
522
  border: 1px dashed var(--line);
@@ -897,6 +909,22 @@
897
909
  overflow-x: auto;
898
910
  }
899
911
  .bubble.md pre code { background: none; padding: 0; }
912
+ /* Copy on a fenced code block. The button sits on a WRAPPER around the <pre>,
913
+ not inside it: the <pre> scrolls sideways, and a button within would scroll
914
+ away. Always visible (a phone has no hover), small, top-right — the <pre>'s
915
+ own top padding keeps the first line clear of it. */
916
+ .bubble.md .md-code { position: relative; }
917
+ .bubble.md .md-code-copy {
918
+ position: absolute; top: 6px; right: 6px;
919
+ padding: 2px 7px; font-family: var(--mono); font-size: 10px; line-height: 1.4;
920
+ cursor: pointer; background: var(--bg-inset); border: 1px solid var(--line);
921
+ border-radius: 6px; color: var(--text-dim); opacity: 0.75;
922
+ transition: color 0.12s, border-color 0.12s, opacity 0.12s;
923
+ }
924
+ .bubble.md .md-code-copy:hover, .bubble.md .md-code-copy:focus-visible {
925
+ opacity: 1; color: var(--amber); border-color: var(--amber);
926
+ }
927
+ .bubble.md .md-code-copy.done { opacity: 1; color: var(--ok, #7ac77a); border-color: currentColor; }
900
928
  /* The horizontal scroll for a wide table lives on this wrapper (added in JS
901
929
  after sanitising), NOT on the <table>: `display:block` on a table breaks its
902
930
  own layout — thead and tbody stop sharing column widths, so the header row
@@ -2630,6 +2658,9 @@
2630
2658
  paintCount(tab.monitorsEl, Number(monitors) || 0, "monitor");
2631
2659
  tab.shells = Number(shells) || 0;
2632
2660
  tab.monitors = Number(monitors) || 0;
2661
+ // The green hatched breathe on the whole tab: on as soon as anything is left
2662
+ // running in the background, off when the last one stops.
2663
+ tab.btnEl.classList.toggle("has-bg", tab.shells > 0 || tab.monitors > 0);
2633
2664
  }
2634
2665
  function paintCount(el, n, noun) {
2635
2666
  if (!el) return;
@@ -4144,6 +4175,30 @@
4144
4175
  t.replaceWith(wrap);
4145
4176
  wrap.appendChild(t);
4146
4177
  }
4178
+ // A copy button on every fenced code block — the thing you most often want
4179
+ // to lift out of an answer. It goes on a wrapper AROUND the <pre> (the
4180
+ // <pre> scrolls; a button inside would scroll off), and copies the code's
4181
+ // own text, not the whole message.
4182
+ for (const pre of bubble.querySelectorAll("pre")) {
4183
+ const code = pre.querySelector("code") || pre;
4184
+ const wrap = document.createElement("div");
4185
+ wrap.className = "md-code";
4186
+ pre.replaceWith(wrap);
4187
+ wrap.appendChild(pre);
4188
+ const btn = document.createElement("button");
4189
+ btn.type = "button";
4190
+ btn.className = "md-code-copy";
4191
+ btn.textContent = "⧉ copy";
4192
+ btn.title = "Copy this code";
4193
+ btn.addEventListener("click", async (e) => {
4194
+ e.stopPropagation();
4195
+ const ok = await copyText(code.textContent.replace(/\n$/, ""));
4196
+ btn.textContent = ok ? "✓ copied" : "⚠ failed";
4197
+ btn.classList.toggle("done", ok);
4198
+ setTimeout(() => { btn.textContent = "⧉ copy"; btn.classList.remove("done"); }, 1500);
4199
+ });
4200
+ wrap.appendChild(btn);
4201
+ }
4147
4202
  } else {
4148
4203
  bubble.textContent = text;
4149
4204
  }