shadok-ai 0.8.17 → 0.8.19
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 +1 -1
- package/public/index.html +167 -43
- package/public/notify.js +2 -2
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
fields (see the coarse-pointer query) fix it at the source instead. -->
|
|
10
10
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
|
11
11
|
<title>shadok-ai — cockpit</title>
|
|
12
|
-
|
|
12
|
+
<!-- The idle favicon: a resting Shadok. paint() swaps in the pumping / "!" / "?"
|
|
13
|
+
variants once the JS runs; this is only the pre-script icon. -->
|
|
14
|
+
<link rel="icon" id="favicon" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2032%2032%22%3E%3Crect%20width%3D%2232%22%20height%3D%2232%22%20rx%3D%227%22%20fill%3D%22%2314161d%22%2F%3E%3Cg%20fill%3D%22%23f0a848%22%3E%3Crect%20x%3D%2211.4%22%20y%3D%2221%22%20width%3D%221.7%22%20height%3D%229%22%20rx%3D%220.85%22%2F%3E%3Crect%20x%3D%2217.4%22%20y%3D%2221%22%20width%3D%221.7%22%20height%3D%229%22%20rx%3D%220.85%22%2F%3E%3Crect%20x%3D%229.2%22%20y%3D%2228.7%22%20width%3D%224.6%22%20height%3D%221.7%22%20rx%3D%220.85%22%2F%3E%3Crect%20x%3D%2216.4%22%20y%3D%2228.7%22%20width%3D%224.6%22%20height%3D%221.7%22%20rx%3D%220.85%22%2F%3E%3C%2Fg%3E%3Cg%20fill%3D%22%23f0a848%22%3E%3Cellipse%20cx%3D%2215%22%20cy%3D%2216.8%22%20rx%3D%227.4%22%20ry%3D%227.6%22%2F%3E%3Cg%20transform%3D%22rotate(6%2019.6%2015.6)%22%3E%3Crect%20x%3D%2218.6%22%20y%3D%2215%22%20width%3D%222.1%22%20height%3D%228.4%22%20rx%3D%221.05%22%2F%3E%3C%2Fg%3E%3Cpath%20d%3D%22M14.6%2010%20C%2014.6%206.6%2C%2016.6%204.8%2C%2018.9%204.8%22%20stroke%3D%22%23f0a848%22%20stroke-width%3D%223%22%20fill%3D%22none%22%20stroke-linecap%3D%22round%22%2F%3E%3Ccircle%20cx%3D%2219.5%22%20cy%3D%224.7%22%20r%3D%223%22%2F%3E%3Cpath%20d%3D%22M22.1%203.5%20L26.4%205%20L22.1%206.5%20Z%22%2F%3E%3Ccircle%20cx%3D%2220.2%22%20cy%3D%224.2%22%20r%3D%220.95%22%20fill%3D%22%2314161d%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E">
|
|
13
15
|
<style>
|
|
14
16
|
:root {
|
|
15
17
|
--bg: #14161d;
|
|
@@ -679,6 +681,12 @@
|
|
|
679
681
|
.prof-drift { margin-left: 8px; font-size: 11px; color: var(--amber); }
|
|
680
682
|
/* À qui ce secret est injecté. Discret : c'est une information de contexte,
|
|
681
683
|
pas une alerte — contrairement à sec-orphan juste en dessous. */
|
|
684
|
+
/* Le pont ESM n'a jamais chargé : le dire plutôt que d'afficher un écran
|
|
685
|
+
amputé sans explication. */
|
|
686
|
+
.bridge-dead {
|
|
687
|
+
position: fixed; left: 50%; transform: translateX(-50%); bottom: 12px; z-index: 200;
|
|
688
|
+
background: #e2564a; color: #fff; padding: 8px 14px; border-radius: 8px; font-size: 13px;
|
|
689
|
+
}
|
|
682
690
|
.sec-users { margin-left: 8px; font-size: 11px; color: var(--text-dim); }
|
|
683
691
|
.sec-orphan { margin-left: 8px; font-size: 11px; color: var(--amber); font-family: var(--sans, inherit); }
|
|
684
692
|
/* Profile picker for a running agent (the tab's context menu). */
|
|
@@ -999,7 +1007,7 @@
|
|
|
999
1007
|
.freetext-row input { flex: 1; }
|
|
1000
1008
|
#thinking { display: none; align-self: flex-start; color: var(--text-dim); font-family: var(--mono); font-size: 12.5px; padding: 0 22px 6px; }
|
|
1001
1009
|
#thinking.visible { display: flex; align-items: center; gap: 6px; }
|
|
1002
|
-
/* Shimmering text (an amber sweep) +
|
|
1010
|
+
/* Shimmering text (an amber sweep) + a little Shadok pumping alongside. */
|
|
1003
1011
|
#thinking .tw-text {
|
|
1004
1012
|
background: linear-gradient(90deg, var(--text-dim) 30%, var(--phosphor) 50%, var(--text-dim) 70%);
|
|
1005
1013
|
background-size: 220% 100%;
|
|
@@ -1008,17 +1016,20 @@
|
|
|
1008
1016
|
animation: tw-shimmer 1.9s linear infinite;
|
|
1009
1017
|
}
|
|
1010
1018
|
@keyframes tw-shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
+
/* The pumping Shadok: `color` feeds its `currentColor` fill so it follows the
|
|
1020
|
+
palette. The body bobs and the arm swings — the pump — on the same beat. */
|
|
1021
|
+
#thinking .tw-shadok { display: inline-flex; color: var(--amber); }
|
|
1022
|
+
#thinking .tw-shadok svg { width: 20px; height: 20px; overflow: visible; }
|
|
1023
|
+
/* Body bobs with translate (no transform-origin needed — that unit is a
|
|
1024
|
+
cross-browser trap on SVG). The arm swings from its own top via fill-box, so
|
|
1025
|
+
it reads as working a pump handle. */
|
|
1026
|
+
#thinking .tw-shadok .sh-body { animation: tw-pump 0.7s ease-in-out infinite; }
|
|
1027
|
+
#thinking .tw-shadok .sh-arm { animation: tw-arm 0.7s ease-in-out infinite; transform-box: fill-box; transform-origin: top center; }
|
|
1028
|
+
@keyframes tw-pump { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(1.5px); } }
|
|
1029
|
+
@keyframes tw-arm { 0%, 100% { transform: rotate(-10deg); } 50% { transform: rotate(28deg); } }
|
|
1019
1030
|
@media (prefers-reduced-motion: reduce) {
|
|
1020
1031
|
#thinking .tw-text { animation: none; color: var(--phosphor); -webkit-text-fill-color: currentColor; }
|
|
1021
|
-
#thinking .tw-
|
|
1032
|
+
#thinking .tw-shadok .sh-body, #thinking .tw-shadok .sh-arm { animation: none; }
|
|
1022
1033
|
}
|
|
1023
1034
|
|
|
1024
1035
|
/* Composer */
|
|
@@ -1894,7 +1905,9 @@
|
|
|
1894
1905
|
</div>
|
|
1895
1906
|
|
|
1896
1907
|
<div id="transcripts"></div>
|
|
1897
|
-
|
|
1908
|
+
<!-- The Shadok is injected into .tw-shadok by the script (one drawing shared
|
|
1909
|
+
with the favicon, `shadokInner`); it pumps via CSS. -->
|
|
1910
|
+
<div id="thinking"><span class="tw-text">Shadok is pumping</span><span class="tw-shadok" aria-hidden="true"></span></div>
|
|
1898
1911
|
|
|
1899
1912
|
<form id="composer" hidden>
|
|
1900
1913
|
<textarea id="promptInput" rows="1"
|
|
@@ -1985,8 +1998,71 @@
|
|
|
1985
1998
|
window.arcSegments = arcSegments;
|
|
1986
1999
|
window.dialTitle = dialTitle;
|
|
1987
2000
|
window.SWEEP_DEG = SWEEP_DEG;
|
|
2001
|
+
// Les bouchons du script classique sont maintenant remplacés par les vraies
|
|
2002
|
+
// fonctions : tout ce qui a pu être peint entre-temps est incomplet.
|
|
2003
|
+
window.dispatchEvent(new Event("shadok-bridge-ready"));
|
|
1988
2004
|
</script>
|
|
1989
2005
|
<script nonce="__CSP_NONCE__">
|
|
2006
|
+
// ── Bouchons du pont ESM (invariant 10) ─────────────────────────────────
|
|
2007
|
+
// Le bloc module ci-dessus est DIFFÉRÉ : il s'exécute après le parsing,
|
|
2008
|
+
// alors que ce script-ci tourne PENDANT et câble déjà les boutons.
|
|
2009
|
+
// Un clic dans cet intervalle appelait un window.<fn> inexistant — c'est
|
|
2010
|
+
// ainsi que `profileBadges` a levé sur un clic Secrets, à cinq lignes d'un
|
|
2011
|
+
// appel voisin correctement gardé. Garder chaque site d'appel était un
|
|
2012
|
+
// combat perdu : le suivant oubliait. Un bouchon neutre par nom, remplacé à
|
|
2013
|
+
// l'arrivée du module, rend tous les sites sûrs par construction.
|
|
2014
|
+
// test/esm-bridge.test.ts verrouille la correspondance nom ↔ bouchon.
|
|
2015
|
+
const BRIDGE_STUBS = {
|
|
2016
|
+
profileBlurb: () => "",
|
|
2017
|
+
profileBadges: () => [],
|
|
2018
|
+
defaultAgentName: (_p, cwd) => String(cwd ?? "").split("/").filter(Boolean).pop() || "agent",
|
|
2019
|
+
hasReadonlyPreset: () => false,
|
|
2020
|
+
applyReadonlyPreset: (deny) => deny,
|
|
2021
|
+
isManagedProfile: () => false,
|
|
2022
|
+
profileSaveBody: (form) => ({ ...form }),
|
|
2023
|
+
secretUsers: () => [],
|
|
2024
|
+
extractLiveText: () => "",
|
|
2025
|
+
livePreviewDecision: () => null,
|
|
2026
|
+
echoAuthor: () => "",
|
|
2027
|
+
notifyState: () => ({ color: null, badge: "", blink: false }),
|
|
2028
|
+
dialPos: () => 0,
|
|
2029
|
+
dialColor: () => "",
|
|
2030
|
+
arcSegments: () => [],
|
|
2031
|
+
dialTitle: () => "",
|
|
2032
|
+
bubblePlacement: () => null,
|
|
2033
|
+
unionRect: () => null,
|
|
2034
|
+
visibleSteps: (steps) => steps ?? [],
|
|
2035
|
+
};
|
|
2036
|
+
for (const [name, stub] of Object.entries(BRIDGE_STUBS)) {
|
|
2037
|
+
if (typeof window[name] !== "function") { stub.__stub = true; window[name] = stub; }
|
|
2038
|
+
}
|
|
2039
|
+
// Un bouchon empêche le plantage, il ne remplace pas la fonction : si le
|
|
2040
|
+
// module n'arrive JAMAIS (un des six fichiers en échec, une coupure), l'écran
|
|
2041
|
+
// resterait silencieusement amputé — pas de badges, pas d'aperçu en direct,
|
|
2042
|
+
// pas de notifications. Une dégradation muette est pire que l'erreur qu'elle
|
|
2043
|
+
// masque : on le dit.
|
|
2044
|
+
window.addEventListener("load", () => setTimeout(() => {
|
|
2045
|
+
const dead = Object.keys(BRIDGE_STUBS).filter((n) => window[n] && window[n].__stub);
|
|
2046
|
+
if (!dead.length) return;
|
|
2047
|
+
console.error("shadok-ai: le pont ESM n'a pas chargé — " + dead.join(", "));
|
|
2048
|
+
const bar = document.createElement("div");
|
|
2049
|
+
bar.className = "bridge-dead";
|
|
2050
|
+
bar.textContent = "⚠ This page did not load fully — some panels will look empty. Reload it.";
|
|
2051
|
+
document.body.appendChild(bar);
|
|
2052
|
+
}, 3000));
|
|
2053
|
+
// Un écran peint pendant que les bouchons étaient en place est incomplet
|
|
2054
|
+
// (une carte sans badges, une jauge à plat). Le module signale son arrivée :
|
|
2055
|
+
// on repeint ce qui est à l'écran, plutôt que de laisser un rendu dégradé.
|
|
2056
|
+
window.addEventListener("shadok-bridge-ready", () => {
|
|
2057
|
+
try {
|
|
2058
|
+
if (typeof renderProfileGrid === "function" && $("profileGrid")) renderProfileGrid(false);
|
|
2059
|
+
if (typeof renderProfilesList === "function" && !$("profilesOverlay").hidden) renderProfilesList();
|
|
2060
|
+
if (typeof renderSecrets === "function" && !$("secretsOverlay").hidden) renderSecrets();
|
|
2061
|
+
} catch {
|
|
2062
|
+
/* une repeinte ratée ne doit pas casser le chargement */
|
|
2063
|
+
}
|
|
2064
|
+
});
|
|
2065
|
+
|
|
1990
2066
|
const $ = (id) => document.getElementById(id);
|
|
1991
2067
|
const tabbar = $("tabbar"), newTabBtn = $("newTab"), newGroupBtn = $("newGroup");
|
|
1992
2068
|
const ungroupedEl = $("ungrouped"), groupsEl = $("groups");
|
|
@@ -3091,33 +3167,67 @@
|
|
|
3091
3167
|
const v = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
3092
3168
|
return v || fallback;
|
|
3093
3169
|
}
|
|
3094
|
-
/**
|
|
3095
|
-
|
|
3096
|
-
*
|
|
3097
|
-
*
|
|
3098
|
-
|
|
3170
|
+
/** The Shadok itself — the cockpit's mascot, which famously PUMPS. One drawing
|
|
3171
|
+
* serves every use: the favicon states here and the in-page "pumping"
|
|
3172
|
+
* indicator. `dx` shifts it left to clear room for a "!" / "?"; `bob`/`armRot`
|
|
3173
|
+
* pose a single frame of the pump (the favicon animates by re-generating with
|
|
3174
|
+
* new values, like the old spinner did); `anim` instead tags the body and arm
|
|
3175
|
+
* groups so CSS can drive them (used only in the DOM indicator, never a data
|
|
3176
|
+
* URI — CSS animation inside a favicon is unreliable). Legs stay planted. */
|
|
3177
|
+
function shadokInner(fill, { dx = 0, bob = 0, armRot = 6, anim = false } = {}) {
|
|
3099
3178
|
const base = themeColor("--bg", "#14161d");
|
|
3100
|
-
const
|
|
3101
|
-
const
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3179
|
+
const bodyCls = anim ? ' class="sh-body"' : "";
|
|
3180
|
+
const armCls = anim ? ' class="sh-arm"' : "";
|
|
3181
|
+
const bodyTf = anim ? "" : ` transform="translate(0 ${bob})"`;
|
|
3182
|
+
const armTf = anim ? "" : ` transform="rotate(${armRot} 19.6 15.6)"`;
|
|
3183
|
+
return (
|
|
3184
|
+
`<g transform="translate(${dx} 0)">` +
|
|
3185
|
+
// legs + feet (planted, not part of the bob)
|
|
3186
|
+
`<g fill="${fill}">` +
|
|
3187
|
+
`<rect x="11.4" y="21" width="1.7" height="9" rx="0.85"/><rect x="17.4" y="21" width="1.7" height="9" rx="0.85"/>` +
|
|
3188
|
+
`<rect x="9.2" y="28.7" width="4.6" height="1.7" rx="0.85"/><rect x="16.4" y="28.7" width="4.6" height="1.7" rx="0.85"/>` +
|
|
3189
|
+
`</g>` +
|
|
3190
|
+
// body + pumping arm + long neck + head + beak + eye
|
|
3191
|
+
`<g${bodyCls} fill="${fill}"${bodyTf}>` +
|
|
3192
|
+
`<ellipse cx="15" cy="16.8" rx="7.4" ry="7.6"/>` +
|
|
3193
|
+
`<g${armCls}${armTf}><rect x="18.6" y="15" width="2.1" height="8.4" rx="1.05"/></g>` +
|
|
3194
|
+
`<path d="M14.6 10 C 14.6 6.6, 16.6 4.8, 18.9 4.8" stroke="${fill}" stroke-width="3" fill="none" stroke-linecap="round"/>` +
|
|
3195
|
+
`<circle cx="19.5" cy="4.7" r="3"/><path d="M22.1 3.5 L26.4 5 L22.1 6.5 Z"/>` +
|
|
3196
|
+
`<circle cx="20.2" cy="4.2" r="0.95" fill="${base}"/>` +
|
|
3197
|
+
`</g></g>`
|
|
3198
|
+
);
|
|
3199
|
+
}
|
|
3200
|
+
function faviconWrap(inner) {
|
|
3111
3201
|
const base = themeColor("--bg", "#14161d");
|
|
3202
|
+
return (
|
|
3203
|
+
"data:image/svg+xml," +
|
|
3204
|
+
encodeURIComponent(
|
|
3205
|
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">` +
|
|
3206
|
+
`<rect width="32" height="32" rx="7" fill="${base}"/>${inner}</svg>`,
|
|
3207
|
+
)
|
|
3208
|
+
);
|
|
3209
|
+
}
|
|
3210
|
+
/** The favicon: an accent Shadok on the dark ground. A waiting state adds a
|
|
3211
|
+
* bold "!" (something to answer) or "?" (something to read) in its colour, and
|
|
3212
|
+
* the Shadok slides left to make room — the coloured glyph is what the user
|
|
3213
|
+
* asked to distinguish the two. No symbol → the idle Shadok. */
|
|
3214
|
+
function faviconSVG(symbol, symColor) {
|
|
3112
3215
|
const accent = themeColor("--amber", "#f0a848");
|
|
3113
|
-
const
|
|
3114
|
-
const
|
|
3115
|
-
`<
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3216
|
+
const shadok = shadokInner(accent, symbol ? { dx: -3.6 } : {});
|
|
3217
|
+
const sym = symbol
|
|
3218
|
+
? `<text x="25.8" y="13" font-family="monospace" font-size="15" font-weight="800" fill="${symColor || accent}" text-anchor="middle">${symbol}</text>`
|
|
3219
|
+
: "";
|
|
3220
|
+
return faviconWrap(shadok + sym);
|
|
3221
|
+
}
|
|
3222
|
+
/** The "working" favicon: the Shadok pumping. `phase` (0..PUMP_FRAMES-1) rides
|
|
3223
|
+
* a cycle — the body bobs and the arm swings, re-generated each tick. */
|
|
3224
|
+
const PUMP_FRAMES = 8;
|
|
3225
|
+
function faviconPumpingSVG(phase) {
|
|
3226
|
+
const accent = themeColor("--amber", "#f0a848");
|
|
3227
|
+
const p = (Math.sin((phase / PUMP_FRAMES) * 2 * Math.PI - Math.PI / 2) + 1) / 2; // 0..1..0
|
|
3228
|
+
const bob = +(p * 1.8).toFixed(2);
|
|
3229
|
+
const armRot = Math.round(-8 + p * 32); // -8 (up) .. +24 (down)
|
|
3230
|
+
return faviconWrap(shadokInner(accent, { bob, armRot }));
|
|
3121
3231
|
}
|
|
3122
3232
|
/** What each channel is asking for, as the pure module wants it. */
|
|
3123
3233
|
function attentionInput() {
|
|
@@ -3133,8 +3243,8 @@
|
|
|
3133
3243
|
// costs no timer at all.
|
|
3134
3244
|
let blinkTimer = null;
|
|
3135
3245
|
let blinkPhase = 0;
|
|
3136
|
-
let workTimer = null; // animates the "working" favicon (
|
|
3137
|
-
let
|
|
3246
|
+
let workTimer = null; // animates the "working" favicon (the pumping Shadok)
|
|
3247
|
+
let workPhase = 0;
|
|
3138
3248
|
function stopWork() { if (workTimer) { clearInterval(workTimer); workTimer = null; } }
|
|
3139
3249
|
function paint() {
|
|
3140
3250
|
// The ESM bridge lands after the document is parsed (gotcha #10): until it
|
|
@@ -3154,20 +3264,24 @@
|
|
|
3154
3264
|
phase: blinkPhase,
|
|
3155
3265
|
});
|
|
3156
3266
|
document.title = s.badge + titleBase;
|
|
3157
|
-
// "working" = animated
|
|
3267
|
+
// "working" = the animated pumping Shadok. The module only returns that mode
|
|
3158
3268
|
// when NOTHING of higher priority (blocked / unread) is waiting — priority is
|
|
3159
3269
|
// therefore handled upstream: a waiting agent always beats a busy one.
|
|
3160
3270
|
if (s.mode === "working") {
|
|
3161
3271
|
if (blinkTimer) { clearInterval(blinkTimer); blinkTimer = null; blinkPhase = 0; }
|
|
3162
3272
|
if (!workTimer) {
|
|
3163
|
-
const tick = () => {
|
|
3273
|
+
const tick = () => { workPhase = (workPhase + 1) % PUMP_FRAMES; favicon.href = faviconPumpingSVG(workPhase); };
|
|
3164
3274
|
tick();
|
|
3165
|
-
workTimer = setInterval(tick,
|
|
3275
|
+
workTimer = setInterval(tick, 110);
|
|
3166
3276
|
}
|
|
3167
3277
|
return;
|
|
3168
3278
|
}
|
|
3169
3279
|
stopWork();
|
|
3170
|
-
|
|
3280
|
+
// A waiting Shadok carries the glyph the user asked for: "!" when it needs an
|
|
3281
|
+
// answer, "?" when something's there to read. The colour is notifyState's
|
|
3282
|
+
// (RED / RED_DIM on the away-blink, AMBER for unread); idle → no glyph.
|
|
3283
|
+
const symbol = s.mode === "blocked" ? "!" : s.mode === "unread" ? "?" : null;
|
|
3284
|
+
favicon.href = faviconSVG(symbol, s.color);
|
|
3171
3285
|
if (s.blink && !blinkTimer) {
|
|
3172
3286
|
blinkTimer = setInterval(() => { blinkPhase ^= 1; paint(); }, window.BLINK_MS || 900);
|
|
3173
3287
|
} else if (!s.blink && blinkTimer) {
|
|
@@ -3177,6 +3291,16 @@
|
|
|
3177
3291
|
}
|
|
3178
3292
|
}
|
|
3179
3293
|
function refreshBadge() { paint(); }
|
|
3294
|
+
// The in-page "pumping" Shadok, drawn once from the SAME `shadokInner` as the
|
|
3295
|
+
// favicon. `currentColor` lets the CSS palette (var(--amber)) tint it, so it
|
|
3296
|
+
// follows a theme change with no redraw; `anim` tags the body/arm for the CSS
|
|
3297
|
+
// pump. The element sits earlier in the document, so it's here to fill now.
|
|
3298
|
+
{
|
|
3299
|
+
const twShadok = document.querySelector("#thinking .tw-shadok");
|
|
3300
|
+
if (twShadok)
|
|
3301
|
+
twShadok.innerHTML =
|
|
3302
|
+
`<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">${shadokInner("currentColor", { anim: true })}</svg>`;
|
|
3303
|
+
}
|
|
3180
3304
|
// Coming back must stop the blink at once, not at the next tick. Both signals
|
|
3181
3305
|
// matter: switching tabs fires visibilitychange, switching application only
|
|
3182
3306
|
// fires focus/blur.
|
|
@@ -3324,7 +3448,7 @@
|
|
|
3324
3448
|
$("chanBarName").title = t.cwd || "";
|
|
3325
3449
|
updateSendDisabled(); // t === active here; also honours a pending upload
|
|
3326
3450
|
thinking.classList.toggle("visible", t.status === "busy");
|
|
3327
|
-
// Showing/hiding "Shadok is
|
|
3451
|
+
// Showing/hiding "Shadok is pumping" changes the transcript area's height:
|
|
3328
3452
|
// without re-pinning to the bottom, the last message slips below the fold
|
|
3329
3453
|
// (typically "cut off" right after sending a prompt).
|
|
3330
3454
|
pinBottom(t);
|
package/public/notify.js
CHANGED
|
@@ -65,8 +65,8 @@ export function notifyState(channels, view) {
|
|
|
65
65
|
: { color: RED, badge: "● ", blink: true, mode: "blocked" };
|
|
66
66
|
}
|
|
67
67
|
if (unread) return { color: AMBER, badge: "● ", blink: false, mode: "unread" };
|
|
68
|
-
// "working" has neither pip colour nor badge: the
|
|
69
|
-
//
|
|
68
|
+
// "working" has neither pip colour nor badge: the caller animates the favicon
|
|
69
|
+
// (the Shadok pumping) for as long as the mode is "working".
|
|
70
70
|
if (working) return { color: null, badge: "", blink: false, mode: "working" };
|
|
71
71
|
return { color: null, badge: "", blink: false, mode: null };
|
|
72
72
|
}
|