bloby-bot 0.64.0 → 0.65.2

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/bin/cli.js CHANGED
@@ -278,8 +278,15 @@ const c = {
278
278
  yellow: '\x1b[33m',
279
279
  red: '\x1b[31m',
280
280
  white: '\x1b[97m',
281
- blue: '\x1b[38;2;50;165;247m',
282
- pink: '\x1b[38;2;219;54;163m',
281
+ blue: '\x1b[38;2;0;173;254m',
282
+ pink: '\x1b[38;2;1;88;251m',
283
+ g1: '\x1b[38;2;0;173;254m',
284
+ g2: '\x1b[38;2;0;159;254m',
285
+ g3: '\x1b[38;2;0;145;253m',
286
+ g4: '\x1b[38;2;1;131;253m',
287
+ g5: '\x1b[38;2;1;116;252m',
288
+ g6: '\x1b[38;2;1;102;251m',
289
+ g7: '\x1b[38;2;1;88;251m',
283
290
  };
284
291
 
285
292
  const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
@@ -287,9 +294,9 @@ const BAR_WIDTH = 30;
287
294
 
288
295
  function gradientChar(i, total) {
289
296
  const t = total > 1 ? i / (total - 1) : 0;
290
- const r = Math.round(50 + t * (219 - 50));
291
- const g = Math.round(165 + t * (54 - 165));
292
- const b = Math.round(247 + t * (163 - 247));
297
+ const r = Math.round(0 + t * (1 - 0));
298
+ const g = Math.round(173 + t * (88 - 173));
299
+ const b = Math.round(254 + t * (251 - 254));
293
300
  return `\x1b[38;2;${r};${g};${b}m`;
294
301
  }
295
302
 
@@ -617,15 +624,13 @@ class Stepper {
617
624
 
618
625
  function banner() {
619
626
  console.log(`
620
- ${c.blue}${c.bold} ${c.reset}
621
- ${c.blue}${c.bold} ▄▄▄ ▄▄ ${c.reset}
622
- ${c.blue}${c.bold} ██▀▀█▄ ██ █▄ ${c.reset}
623
- ${c.blue}${c.bold} ██ ▄█▀ ██ ██ ${c.reset}
624
- ${c.blue}${c.bold} ██▀▀█▄ ██ ▄███▄ ████▄ ██ ██${c.reset}
625
- ${c.pink}${c.bold}██ ▄█ ██ ██ ██ ██ ██ ██▄██${c.reset}
626
- ${c.pink}${c.bold} ▀██████▀▄██▄▀███▀▄████▀▄▄▀██▀${c.reset}
627
- ${c.pink}${c.bold} ██ ${c.reset}
628
- ${c.pink}${c.bold} ▀▀▀ ${c.reset}
627
+ ${c.g1}${c.bold} █▄ ${c.reset}
628
+ ${c.g2}${c.bold} ▄ ▄ ██ ${c.reset}
629
+ ${c.g3}${c.bold} ███▄███▄ ▄███▄ ████▄████▄ ████▄ ██ ██${c.reset}
630
+ ${c.g4}${c.bold} ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██▄██${c.reset}
631
+ ${c.g5}${c.bold} ▄██ ██ ▀█▄▀███▀▄█▀ ▄████▀▄██ ██▄▄▀██▀${c.reset}
632
+ ${c.g6}${c.bold} ██ ██ ${c.reset}
633
+ ${c.g7}${c.bold} ▀ ▀▀▀ ${c.reset}
629
634
  ${c.dim}v${pkg.version} · Self-hosted AI agent${c.reset}`);
630
635
  }
631
636
 
package/cli/utils/ui.ts CHANGED
@@ -1,24 +1,38 @@
1
1
  import pc from 'picocolors';
2
2
  import { pkg } from '../core/config.js';
3
3
 
4
- export function banner() {
5
- const b1 = (s: string) => pc.bold(pc.blue(s));
6
- const b2 = (s: string) => pc.bold(pc.magenta(s));
7
- const dim = pc.dim;
4
+ // 24-bit truecolor wrapper (picocolors only ships the 16-color set). Respects
5
+ // picocolors' own support detection so NO_COLOR / piped output stays clean.
6
+ const tc = (r: number, g: number, b: number) => (s: string) =>
7
+ pc.isColorSupported ? `\x1b[38;2;${r};${g};${b}m${s}\x1b[39m` : s;
8
+
9
+ // Morphy logo gradient: #00ADFE (top) -> #0158FB (bottom)
10
+ const grad = [
11
+ tc(0, 173, 254),
12
+ tc(0, 159, 254),
13
+ tc(0, 145, 253),
14
+ tc(1, 131, 253),
15
+ tc(1, 116, 252),
16
+ tc(1, 102, 251),
17
+ tc(1, 88, 251),
18
+ ];
8
19
 
9
- console.log([
10
- ``,
11
- b1(` _______ _ `),
12
- b1(` (_______) | `),
13
- b1(` _____ | |_ _ _ _ _ _ `),
14
- b1(` | ___) | | | | ( \\ / ) | | | `),
15
- b2(` | | | | |_| |) X (| |_| | `),
16
- b2(` |_| |_|\\____(_/ \\_)\\__ | `),
17
- b2(` (____/ `),
18
- dim(`v${pkg.version || '1.0.0'} · Self-hosted AI agent `),
19
- ].join('\n'));
20
+ const logo = [
21
+ ' █▄ ',
22
+ ' ▄ ▄ ██ ',
23
+ ' ███▄███▄ ▄███▄ ████▄████▄ ████▄ ██ ██',
24
+ ' ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██▄██',
25
+ ' ▄██ ██ ▀█▄▀███▀▄█▀ ▄████▀▄██ ██▄▄▀██▀',
26
+ ' ██ ██ ',
27
+ ' ▀ ▀▀▀ ',
28
+ ];
29
+
30
+ export function banner() {
31
+ console.log('');
32
+ logo.forEach((row, i) => console.log(grad[i](pc.bold(row))));
33
+ console.log(pc.dim(`v${pkg.version || '1.0.0'} · Self-hosted AI agent`));
20
34
  }
21
35
 
22
36
  export function commandExample(name: string, cmd: string) {
23
- return ` ${pc.dim(name)} ${pc.magenta(cmd)}`;
37
+ return ` ${pc.dim(name)} ${tc(0, 173, 254)(cmd)}`;
24
38
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bloby-bot",
3
- "version": "0.64.0",
3
+ "version": "0.65.2",
4
4
  "releaseNotes": [
5
5
  "1. Fix: image (and audio) attachments now render in chat again — /api/files is fetched with the auth token instead of a raw <img> src that 401'd after the endpoint hardening",
6
6
  "2. Affects chat thumbnails, the image lightbox, voice-note playback, and agent image cards",
package/scripts/install CHANGED
@@ -15,9 +15,17 @@ NODE_DIR="$TOOLS_DIR/node"
15
15
  BIN_DIR="$BLOBY_HOME/bin"
16
16
  USE_SYSTEM_NODE=false
17
17
 
18
- # Brand colors: #32A5F7 (blue) and #DB36A3 (pink) via 256-color approximation
19
- BLUE='\033[38;2;50;165;247m'
20
- PINK='\033[38;2;219;54;163m'
18
+ # Brand colors: #00ADFE (light) and #0158FB (deep) -- Morphy palette, 24-bit truecolor
19
+ BLUE='\033[38;2;0;173;254m'
20
+ PINK='\033[38;2;1;88;251m'
21
+ # Logo gradient: #00ADFE (top) -> #0158FB (bottom)
22
+ G1='\033[38;2;0;173;254m'
23
+ G2='\033[38;2;0;159;254m'
24
+ G3='\033[38;2;0;145;253m'
25
+ G4='\033[38;2;1;131;253m'
26
+ G5='\033[38;2;1;116;252m'
27
+ G6='\033[38;2;1;102;251m'
28
+ G7='\033[38;2;1;88;251m'
21
29
  YELLOW='\033[33m'
22
30
  RED='\033[31m'
23
31
  DIM='\033[2m'
@@ -25,13 +33,13 @@ BOLD='\033[1m'
25
33
  RESET='\033[0m'
26
34
 
27
35
  printf "\n"
28
- printf "${BLUE}${BOLD} _______ _ ${RESET}\n"
29
- printf "${BLUE}${BOLD} (_______) | ${RESET}\n"
30
- printf "${BLUE}${BOLD} _____ | |_ _ _ _ _ _ ${RESET}\n"
31
- printf "${BLUE}${BOLD} | ___) | | | | ( \\ / ) | | | ${RESET}\n"
32
- printf "${PINK}${BOLD} | | | | |_| |) X (| |_| | ${RESET}\n"
33
- printf "${PINK}${BOLD} |_| |_|\\____(_/ \\_)\\__ | ${RESET}\n"
34
- printf "${PINK}${BOLD} (____/ ${RESET}\n"
36
+ printf "${G1}${BOLD} █▄ ${RESET}\n"
37
+ printf "${G2}${BOLD} ▄ ▄ ██ ${RESET}\n"
38
+ printf "${G3}${BOLD} ███▄███▄ ▄███▄ ████▄████▄ ████▄ ██ ██${RESET}\n"
39
+ printf "${G4}${BOLD} ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██▄██${RESET}\n"
40
+ printf "${G5}${BOLD} ▄██ ██ ▀█▄▀███▀▄█▀ ▄████▀▄██ ██▄▄▀██▀${RESET}\n"
41
+ printf "${G6}${BOLD} ██ ██ ${RESET}\n"
42
+ printf "${G7}${BOLD} ▀ ▀▀▀ ${RESET}\n"
35
43
  printf "\n"
36
44
  printf "${DIM} Self-hosted, self-evolving AI agent with its own dashboard.${RESET}\n"
37
45
  printf "${DIM} ─────────────────────────────${RESET}\n\n"
@@ -17,9 +17,17 @@ $USE_SYSTEM_NODE = $false
17
17
  # Ensure UTF-8 output for proper rendering
18
18
  [Console]::OutputEncoding = [System.Text.Encoding]::UTF8
19
19
 
20
- # Brand colors via ANSI escape sequences: #32A5F7 (blue) and #DB36A3 (pink)
21
- $BLUE = "`e[38;2;50;165;247m"
22
- $PINK = "`e[38;2;219;54;163m"
20
+ # Brand colors: #00ADFE (light) and #0158FB (deep) -- Morphy palette
21
+ $BLUE = "`e[38;2;0;173;254m"
22
+ $PINK = "`e[38;2;1;88;251m"
23
+ # Logo gradient: #00ADFE (top) -> #0158FB (bottom)
24
+ $G1 = "`e[38;2;0;173;254m"
25
+ $G2 = "`e[38;2;0;159;254m"
26
+ $G3 = "`e[38;2;0;145;253m"
27
+ $G4 = "`e[38;2;1;131;253m"
28
+ $G5 = "`e[38;2;1;116;252m"
29
+ $G6 = "`e[38;2;1;102;251m"
30
+ $G7 = "`e[38;2;1;88;251m"
23
31
  $BOLD = "`e[1m"
24
32
  $RSET = "`e[0m"
25
33
 
@@ -38,21 +46,21 @@ function Write-Down($text) {
38
46
 
39
47
  Write-Host ""
40
48
  if ($vtSupported) {
41
- Write-Host "${BLUE}${BOLD} _______ _ ${RSET}"
42
- Write-Host "${BLUE}${BOLD} (_______) | ${RSET}"
43
- Write-Host "${BLUE}${BOLD} _____ | |_ _ _ _ _ _ ${RSET}"
44
- Write-Host "${BLUE}${BOLD} | ___) | | | | ( \ / ) | | | ${RSET}"
45
- Write-Host "${PINK}${BOLD} | | | | |_| |) X (| |_| | ${RSET}"
46
- Write-Host "${PINK}${BOLD} |_| |_|\____(_/ \_)\__ | ${RSET}"
47
- Write-Host "${PINK}${BOLD} (____/ ${RSET}"
49
+ Write-Host "${G1}${BOLD} █▄ ${RSET}"
50
+ Write-Host "${G2}${BOLD} ▄ ▄ ██ ${RSET}"
51
+ Write-Host "${G3}${BOLD} ███▄███▄ ▄███▄ ████▄████▄ ████▄ ██ ██${RSET}"
52
+ Write-Host "${G4}${BOLD} ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██▄██${RSET}"
53
+ Write-Host "${G5}${BOLD} ▄██ ██ ▀█▄▀███▀▄█▀ ▄████▀▄██ ██▄▄▀██▀${RSET}"
54
+ Write-Host "${G6}${BOLD} ██ ██ ${RSET}"
55
+ Write-Host "${G7}${BOLD} ▀ ▀▀▀ ${RSET}"
48
56
  } else {
49
- Write-Host " _______ _ " -ForegroundColor Cyan
50
- Write-Host " (_______) | " -ForegroundColor Cyan
51
- Write-Host " _____ | |_ _ _ _ _ _ " -ForegroundColor Cyan
52
- Write-Host " | ___) | | | | ( \ / ) | | | " -ForegroundColor Cyan
53
- Write-Host " | | | | |_| |) X (| |_| | " -ForegroundColor Magenta
54
- Write-Host " |_| |_|\____(_/ \_)\__ | " -ForegroundColor Magenta
55
- Write-Host " (____/ " -ForegroundColor Magenta
57
+ Write-Host " █▄ " -ForegroundColor Cyan
58
+ Write-Host " ▄ ▄ ██ " -ForegroundColor Cyan
59
+ Write-Host " ███▄███▄ ▄███▄ ████▄████▄ ████▄ ██ ██" -ForegroundColor Cyan
60
+ Write-Host " ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██▄██" -ForegroundColor Blue
61
+ Write-Host " ▄██ ██ ▀█▄▀███▀▄█▀ ▄████▀▄██ ██▄▄▀██▀" -ForegroundColor Blue
62
+ Write-Host " ██ ██ " -ForegroundColor Blue
63
+ Write-Host " ▀ ▀▀▀ " -ForegroundColor Blue
56
64
  }
57
65
  Write-Host ""
58
66
  Write-Host " Self-hosted, self-evolving AI agent with its own dashboard." -ForegroundColor DarkGray
@@ -15,9 +15,17 @@ NODE_DIR="$TOOLS_DIR/node"
15
15
  BIN_DIR="$BLOBY_HOME/bin"
16
16
  USE_SYSTEM_NODE=false
17
17
 
18
- # Brand colors: #32A5F7 (blue) and #DB36A3 (pink) via 256-color approximation
19
- BLUE='\033[38;2;50;165;247m'
20
- PINK='\033[38;2;219;54;163m'
18
+ # Brand colors: #00ADFE (light) and #0158FB (deep) -- Morphy palette, 24-bit truecolor
19
+ BLUE='\033[38;2;0;173;254m'
20
+ PINK='\033[38;2;1;88;251m'
21
+ # Logo gradient: #00ADFE (top) -> #0158FB (bottom)
22
+ G1='\033[38;2;0;173;254m'
23
+ G2='\033[38;2;0;159;254m'
24
+ G3='\033[38;2;0;145;253m'
25
+ G4='\033[38;2;1;131;253m'
26
+ G5='\033[38;2;1;116;252m'
27
+ G6='\033[38;2;1;102;251m'
28
+ G7='\033[38;2;1;88;251m'
21
29
  YELLOW='\033[33m'
22
30
  RED='\033[31m'
23
31
  DIM='\033[2m'
@@ -25,13 +33,13 @@ BOLD='\033[1m'
25
33
  RESET='\033[0m'
26
34
 
27
35
  printf "\n"
28
- printf "${BLUE}${BOLD} _______ _ ${RESET}\n"
29
- printf "${BLUE}${BOLD} (_______) | ${RESET}\n"
30
- printf "${BLUE}${BOLD} _____ | |_ _ _ _ _ _ ${RESET}\n"
31
- printf "${BLUE}${BOLD} | ___) | | | | ( \\ / ) | | | ${RESET}\n"
32
- printf "${PINK}${BOLD} | | | | |_| |) X (| |_| | ${RESET}\n"
33
- printf "${PINK}${BOLD} |_| |_|\\____(_/ \\_)\\__ | ${RESET}\n"
34
- printf "${PINK}${BOLD} (____/ ${RESET}\n"
36
+ printf "${G1}${BOLD} █▄ ${RESET}\n"
37
+ printf "${G2}${BOLD} ▄ ▄ ██ ${RESET}\n"
38
+ printf "${G3}${BOLD} ███▄███▄ ▄███▄ ████▄████▄ ████▄ ██ ██${RESET}\n"
39
+ printf "${G4}${BOLD} ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██▄██${RESET}\n"
40
+ printf "${G5}${BOLD} ▄██ ██ ▀█▄▀███▀▄█▀ ▄████▀▄██ ██▄▄▀██▀${RESET}\n"
41
+ printf "${G6}${BOLD} ██ ██ ${RESET}\n"
42
+ printf "${G7}${BOLD} ▀ ▀▀▀ ${RESET}\n"
35
43
  printf "\n"
36
44
  printf "${DIM} Self-hosted, self-evolving AI agent with its own dashboard.${RESET}\n"
37
45
  printf "${DIM} ─────────────────────────────${RESET}\n\n"
@@ -32,7 +32,6 @@
32
32
  function sendChatSubscribe() {
33
33
  if (!ws || ws.readyState !== WebSocket.OPEN) return;
34
34
  ws.send(JSON.stringify({ type: 'chat:subscribe', data: { clientId: chatClientId } }));
35
- console.log('[app-ws] chat:subscribe sent clientId=' + chatClientId);
36
35
  }
37
36
 
38
37
  function buildWsUrl() {
@@ -59,7 +58,6 @@
59
58
  for (var i = 0; i < ids.length; i++) {
60
59
  var p = pendingRequests[ids[i]];
61
60
  clearTimeout(p.timer);
62
- console.log('[app-ws] WS dropped, falling back to fetch: ' + p.method + ' ' + p.path);
63
61
  p.resolve(originalFetch(p.input, p.init));
64
62
  }
65
63
  pendingRequests = {};
@@ -74,7 +72,6 @@
74
72
  connected = true;
75
73
  reconnectDelay = RECONNECT_BASE;
76
74
  startHeartbeat();
77
- console.log('[app-ws] Connected to /app/ws');
78
75
  if (chatSubscribed) sendChatSubscribe();
79
76
  };
80
77
 
@@ -94,7 +91,6 @@
94
91
  }
95
92
 
96
93
  if (msg.type === 'chat:subscribed') {
97
- console.log('[app-ws] chat:subscribed ack clientId=' + (msg.data && msg.data.clientId));
98
94
  return;
99
95
  }
100
96
 
@@ -104,7 +100,6 @@
104
100
  clearTimeout(pending.timer);
105
101
  delete pendingRequests[msg.data.id];
106
102
 
107
- console.log('[app-ws] Response via WS: ' + msg.data.status + ' ' + pending.method + ' ' + pending.path);
108
103
 
109
104
  var responseBody = msg.data.body;
110
105
  if (responseBody === null || responseBody === undefined) responseBody = '';
@@ -125,7 +120,6 @@
125
120
  failoverPending();
126
121
 
127
122
  if (!intentionalClose) {
128
- console.log('[app-ws] Disconnected, reconnecting in ' + reconnectDelay + 'ms');
129
123
  reconnectTimer = setTimeout(function () {
130
124
  reconnectDelay = Math.min(reconnectDelay * 2, RECONNECT_MAX);
131
125
  connect();
@@ -150,14 +144,12 @@
150
144
 
151
145
  // Fallback: WS not connected
152
146
  if (!connected || !ws || ws.readyState !== WebSocket.OPEN) {
153
- console.log('[app-ws] WS not connected, falling back to fetch: ' + method + ' ' + url);
154
147
  return originalFetch.apply(this, arguments);
155
148
  }
156
149
 
157
150
  // Fallback: non-serializable body (FormData, Blob, ArrayBuffer)
158
151
  var body = init && init.body;
159
152
  if (body && (body instanceof FormData || body instanceof Blob || body instanceof ArrayBuffer)) {
160
- console.log('[app-ws] Non-serializable body, falling back to fetch: ' + method + ' ' + url);
161
153
  return originalFetch.apply(this, arguments);
162
154
  }
163
155
 
@@ -189,7 +181,6 @@
189
181
  bodyStr = typeof body === 'string' ? body : JSON.stringify(body);
190
182
  }
191
183
 
192
- console.log('[app-ws] Proxying via WS: ' + method + ' ' + reqPath);
193
184
 
194
185
  ws.send(
195
186
  JSON.stringify({
@@ -204,7 +195,6 @@
204
195
  return new Promise(function (resolve, reject) {
205
196
  var timer = setTimeout(function () {
206
197
  delete pendingRequests[id];
207
- console.log('[app-ws] Request timed out, falling back to fetch: ' + method + ' ' + reqPath);
208
198
  resolve(originalFetch(savedInput, savedInit));
209
199
  }, REQUEST_TIMEOUT);
210
200
 
@@ -41,7 +41,6 @@ export function useBlobyChat(ws: WsClient | null, triggerReload?: number, enable
41
41
  function handleExtMessage(event: MessageEvent) {
42
42
  if (event.data?.type === 'bloby:page-context') {
43
43
  extensionPageContext.current = event.data.context;
44
- console.log('[blobyChat] Extension page context:', event.data.context?.url);
45
44
  }
46
45
  }
47
46
 
@@ -153,11 +152,9 @@ export function useBlobyChat(ws: WsClient | null, triggerReload?: number, enable
153
152
  useEffect(() => {
154
153
  if (!ws) return;
155
154
 
156
- console.log('[blobyChat] ──── WS HANDLERS REGISTERED ────');
157
155
 
158
156
  const unsubs = [
159
157
  ws.on('bot:typing', () => {
160
- console.log('[blobyChat] bot:typing → streaming=true');
161
158
  setStreaming(true);
162
159
  setTools([]);
163
160
  }),
@@ -175,7 +172,6 @@ export function useBlobyChat(ws: WsClient | null, triggerReload?: number, enable
175
172
  // This creates separate message bubbles and shows dots during work
176
173
  const content = streamBufferRef.current;
177
174
  if (content) {
178
- console.log(`[blobyChat] bot:tool (${data.name}) — committing ${content.length} chars as bubble`);
179
175
  committedTextLength.current += content.length;
180
176
  setMessages((msgs) => [
181
177
  ...msgs,
@@ -205,9 +201,6 @@ export function useBlobyChat(ws: WsClient | null, triggerReload?: number, enable
205
201
  if (committedTextLength.current > 0) {
206
202
  content = content.slice(committedTextLength.current).replace(/^\n+/, '');
207
203
  committedTextLength.current = 0;
208
- console.log('[blobyChat] bot:response — stripped partial, remaining:', content.slice(0, 60));
209
- } else {
210
- console.log('[blobyChat] bot:response — new bubble');
211
204
  }
212
205
 
213
206
  // Only add a bubble if there's new content
@@ -232,7 +225,6 @@ export function useBlobyChat(ws: WsClient | null, triggerReload?: number, enable
232
225
  ws.on('bot:task-created', () => {
233
226
  const content = streamBufferRef.current;
234
227
  if (content) {
235
- console.log('[blobyChat] bot:task-created — committing stream, showing dots');
236
228
  committedTextLength.current += content.length;
237
229
  setMessages((msgs) => [
238
230
  ...msgs,
@@ -250,11 +242,9 @@ export function useBlobyChat(ws: WsClient | null, triggerReload?: number, enable
250
242
  }),
251
243
  ws.on('bot:idle', () => {
252
244
  // Server confirmed agent is idle — safe to stop streaming
253
- console.log('[blobyChat] bot:idle → streaming=false');
254
245
  setStreaming(false);
255
246
  }),
256
247
  ws.on('bot:error', (data: { error: string }) => {
257
- console.log('[blobyChat] bot:error');
258
248
  setStreamBuffer('');
259
249
  streamBufferRef.current = '';
260
250
  setStreaming(false);
@@ -334,7 +324,6 @@ export function useBlobyChat(ws: WsClient | null, triggerReload?: number, enable
334
324
  // If bot is streaming, commit partial response first
335
325
  const partialContent = streamBufferRef.current;
336
326
  if (partialContent) {
337
- console.log('[blobyChat] Committing partial —', partialContent.length, 'chars');
338
327
  committedTextLength.current += partialContent.length;
339
328
  setMessages((msgs) => [
340
329
  ...msgs,
@@ -128,7 +128,6 @@ export function useChat(ws: WsClient | null) {
128
128
 
129
129
  const unsubs = [
130
130
  ws.on('bot:typing', () => {
131
- console.log('[useChat] bot:typing → streaming=true');
132
131
  setStreaming(true);
133
132
  setTools([]);
134
133
  }),
@@ -149,7 +148,6 @@ export function useChat(ws: WsClient | null) {
149
148
  });
150
149
  }),
151
150
  ws.on('bot:response', (data: { conversationId: string; messageId?: string; content: string }) => {
152
- console.log('[useChat] bot:response — adding message bubble');
153
151
  setConversationId(data.conversationId);
154
152
 
155
153
  // Always add as a new bubble
@@ -170,11 +168,9 @@ export function useChat(ws: WsClient | null) {
170
168
  }),
171
169
  ws.on('bot:idle', () => {
172
170
  // Server confirmed agent is idle — safe to stop streaming
173
- console.log('[useChat] bot:idle → streaming=false');
174
171
  setStreaming(false);
175
172
  }),
176
173
  ws.on('bot:error', (data: { error: string }) => {
177
- console.log('[useChat] bot:error');
178
174
  setStreamBuffer('');
179
175
  streamBufferRef.current = '';
180
176
  setStreaming(false);
@@ -218,7 +214,6 @@ export function useChat(ws: WsClient | null) {
218
214
  // so the user's new message appears BELOW the bot's in-progress text (chronological order)
219
215
  const partialContent = streamBufferRef.current;
220
216
  if (partialContent) {
221
- console.log('[useChat] Committing partial stream buffer as message before user send');
222
217
  setMessages((msgs) => [
223
218
  ...msgs,
224
219
  {
@@ -128,25 +128,22 @@ var HASHED_RE = new RegExp('/assets/.+-[a-zA-Z0-9]{6,}[.](js|css)$');
128
128
  // Without this, the first navigation isn't intercepted (SW wasn't
129
129
  // controlling yet), so refresh would find an empty cache → white screen.
130
130
  self.addEventListener('install', function(e) {
131
- console.log('[SW] installing, cache:', CACHE);
132
131
  e.waitUntil(
133
132
  caches.open(CACHE)
134
133
  .then(function(c) { return c.add('/'); })
135
- .then(function() { console.log('[SW] precached / — calling skipWaiting'); return self.skipWaiting(); })
134
+ .then(function() { return self.skipWaiting(); })
136
135
  .catch(function(err) { console.error('[SW] install failed:', err); throw err; })
137
136
  );
138
137
  });
139
138
 
140
139
  self.addEventListener('activate', function(e) {
141
- console.log('[SW] activating, cache:', CACHE);
142
140
  e.waitUntil(
143
141
  caches.keys()
144
142
  .then(function(keys) {
145
143
  var old = keys.filter(function(k) { return k !== CACHE; });
146
- if (old.length) console.log('[SW] deleting old caches:', old);
147
144
  return Promise.all(old.map(function(k) { return caches.delete(k); }));
148
145
  })
149
- .then(function() { console.log('[SW] claiming clients'); return self.clients.claim(); })
146
+ .then(function() { return self.clients.claim(); })
150
147
  );
151
148
  });
152
149
 
@@ -184,7 +181,6 @@ self.addEventListener('fetch', function(event) {
184
181
  // /bloby/* is a separate app — let it go to network to avoid
185
182
  // caching the wrong HTML under the / key.
186
183
  if (request.mode === 'navigate') {
187
- console.log('[SW] navigate →', url.pathname);
188
184
  // Workspace iframe documents (?__bloby_frame=1) share pathname '/' with the shell.
189
185
  // Network-only: never c.put() them (would overwrite the precached shell under the '/'
190
186
  // key) and never fall back to c.match('/') (would serve the shell INTO the iframe).
@@ -194,12 +190,11 @@ self.addEventListener('fetch', function(event) {
194
190
  event.respondWith(caches.open(CACHE).then(function(c) {
195
191
  return fetch(request)
196
192
  .then(function(r) { if (r.ok) c.put('/', r.clone()); return r; })
197
- .catch(function(err) { console.warn('[SW] / network failed, using cache:', err.message); return c.match('/'); });
193
+ .catch(function() { return c.match('/'); });
198
194
  }));
199
195
  return;
200
196
  }
201
197
  // Other navigations (/bloby/*, etc.) — network only
202
- console.log('[SW] navigate (network-only) →', url.pathname);
203
198
  return;
204
199
  }
205
200
 
@@ -426,7 +421,6 @@ export async function startSupervisor() {
426
421
  log.error(`Vite dev server failed to start — dashboard degraded, chat still available: ${err instanceof Error ? err.message : err}`);
427
422
  vitePorts = { dashboard: -1 }; // sentinel → dashboard proxy serves RECOVERING_HTML
428
423
  }
429
- console.log(`[supervisor] Upgrade listeners on server: ${server.listenerCount('upgrade')}`);
430
424
 
431
425
  // Ensure file storage dirs exist
432
426
  ensureFileDirs();
@@ -589,7 +583,6 @@ export async function startSupervisor() {
589
583
  server.on('request', async (req, res) => {
590
584
  // Bloby widget — served directly (not part of Vite build)
591
585
  if (req.url === '/bloby/widget.js') {
592
- console.log('[supervisor] Serving /bloby/widget.js directly');
593
586
  res.writeHead(200, { 'Content-Type': 'application/javascript', 'Cache-Control': 'no-cache' });
594
587
  res.end(fs.readFileSync(paths.widgetJs));
595
588
  return;
@@ -597,7 +590,6 @@ export async function startSupervisor() {
597
590
 
598
591
  // App WS client — served directly (proxies /app/api calls through WebSocket)
599
592
  if (req.url === '/bloby/app-ws.js') {
600
- console.log('[supervisor] Serving /bloby/app-ws.js directly');
601
593
  res.writeHead(200, { 'Content-Type': 'application/javascript', 'Cache-Control': 'no-cache' });
602
594
  res.end(fs.readFileSync(path.join(PKG_DIR, 'supervisor', 'app-ws.js')));
603
595
  return;
@@ -631,9 +623,7 @@ export async function startSupervisor() {
631
623
  // App API routes → proxy to user's backend server
632
624
  if (req.url?.startsWith('/app/api')) {
633
625
  const backendPath = req.url.replace(/^\/app/, '');
634
- console.log(`[supervisor] → backend :${backendPort} | ${req.method} ${backendPath}`);
635
626
  if (!isBackendAlive()) {
636
- console.log('[supervisor] Backend down — returning 503');
637
627
  res.writeHead(503, { 'Content-Type': 'application/json' });
638
628
  res.end(JSON.stringify({ error: 'Backend is starting...' }));
639
629
  return;
@@ -644,18 +634,10 @@ export async function startSupervisor() {
644
634
  (proxyRes) => {
645
635
  const ct = String(proxyRes.headers['content-type'] || '');
646
636
  const isSse = ct.includes('text/event-stream');
647
- if (isSse) {
648
- console.log(`[app-proxy] SSE upstream status=${proxyRes.statusCode} ct="${ct}" url=${backendPath}`);
649
- }
650
637
  res.writeHead(proxyRes.statusCode!, proxyRes.headers);
638
+ // SSE needs Nagle off so chat tokens flush immediately instead of in batched frames.
651
639
  if (isSse) {
652
640
  try { res.socket?.setNoDelay(true); } catch {}
653
- proxyRes.on('data', (chunk: Buffer) => {
654
- console.log(`[app-proxy] SSE chunk bytes=${chunk.length} preview=${JSON.stringify(chunk.toString('utf-8').slice(0, 80))}`);
655
- });
656
- proxyRes.on('end', () => console.log(`[app-proxy] SSE upstream END`));
657
- proxyRes.on('error', (e: any) => console.log(`[app-proxy] SSE upstream ERROR ${e.message}`));
658
- res.on('close', () => console.log(`[app-proxy] SSE res CLOSE`));
659
641
  }
660
642
  proxyRes.pipe(res);
661
643
  },
@@ -2139,7 +2121,6 @@ ${alreadyLinked ? '' : `
2139
2121
  if (req.method === 'GET' && agentPath === '/api/agent/chat/stream') {
2140
2122
  const clientId = urlObj.searchParams.get('clientId') || undefined;
2141
2123
  const subId = crypto.randomBytes(8).toString('hex');
2142
- console.log(`[sse-handler] OPEN sub=${subId} clientId=${clientId} remote=${req.socket.remoteAddress}`);
2143
2124
 
2144
2125
  res.setHeader('Content-Type', 'text/event-stream');
2145
2126
  res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
@@ -2147,19 +2128,14 @@ ${alreadyLinked ? '' : `
2147
2128
  res.setHeader('X-Accel-Buffering', 'no');
2148
2129
  res.writeHead(200);
2149
2130
  try { res.socket?.setNoDelay(true); } catch {}
2150
- const wrote = res.write(': connected\n\n');
2151
- console.log(`[sse-handler] wrote initial comment sub=${subId} writeOk=${wrote}`);
2131
+ res.write(': connected\n\n');
2152
2132
 
2153
2133
  const sub: ChatSubscriber = {
2154
2134
  id: subId,
2155
2135
  clientId,
2156
2136
  send: (type, data) => {
2157
- if (res.writableEnded) {
2158
- console.log(`[sse-handler] skip write (ended) sub=${subId} type=${type}`);
2159
- return;
2160
- }
2161
- const ok = res.write(`event: ${type}\ndata: ${JSON.stringify(data)}\n\n`);
2162
- console.log(`[sse-handler] write sub=${subId} type=${type} ok=${ok} bytes=${(type.length + JSON.stringify(data).length + 10)}`);
2137
+ if (res.writableEnded) return;
2138
+ res.write(`event: ${type}\ndata: ${JSON.stringify(data)}\n\n`);
2163
2139
  },
2164
2140
  close: () => { try { res.end(); } catch {} },
2165
2141
  };
@@ -2176,20 +2152,14 @@ ${alreadyLinked ? '' : `
2176
2152
  const keepAlive = setInterval(() => {
2177
2153
  if (res.writableEnded) return;
2178
2154
  res.write(': ping\n\n');
2179
- console.log(`[sse-handler] ping sub=${subId}`);
2180
2155
  }, 25_000);
2181
2156
 
2182
2157
  req.on('close', () => {
2183
- console.log(`[sse-handler] CLOSE sub=${subId} reason=req-close`);
2184
2158
  clearInterval(keepAlive);
2185
2159
  chatSubscribers.delete(sub);
2186
2160
  });
2187
- res.on('close', () => {
2188
- console.log(`[sse-handler] CLOSE sub=${subId} reason=res-close`);
2189
- });
2190
- res.on('error', (err: any) => {
2191
- console.log(`[sse-handler] ERROR sub=${subId} ${err?.message}`);
2192
- });
2161
+ // Keep an error listener so a client reset can't surface as an unhandled 'error'.
2162
+ res.on('error', () => {});
2193
2163
  return;
2194
2164
  }
2195
2165
 
@@ -2630,7 +2600,6 @@ ${alreadyLinked ? '' : `
2630
2600
  }
2631
2601
 
2632
2602
  // Everything else → proxy to dashboard Vite dev server
2633
- console.log(`[supervisor] → dashboard Vite :${vitePorts.dashboard} | ${req.method} ${(req.url || '').split('?')[0]}`);
2634
2603
  const GUARD_TAG = '<script defer src="/bloby/workspace-guard.js"></script>';
2635
2604
  const proxy = http.request(
2636
2605
  { host: '127.0.0.1', port: vitePorts.dashboard, path: req.url, method: req.method, headers: req.headers },
@@ -2679,10 +2648,9 @@ ${alreadyLinked ? '' : `
2679
2648
  const appWss = new WebSocketServer({ noServer: true });
2680
2649
 
2681
2650
  appWss.on('connection', (ws) => {
2682
- console.log('[supervisor] App API WS client connected');
2683
2651
  // An 'error' event with no listener is rethrown by Node as an uncaught exception,
2684
2652
  // which would crash the whole supervisor. ws still tears down + fires 'close'.
2685
- ws.on('error', (err: any) => console.warn(`[app-ws] socket error: ${err?.message || err}`));
2653
+ ws.on('error', () => {});
2686
2654
  // Liveness: a half-open socket (mobile/Wi-Fi drop behind the tunnel) never fires 'close', so
2687
2655
  // its chat subscription + maps would leak and broadcastBloby would keep writing to it. The
2688
2656
  // heartbeat below pings; a peer that misses a pong is terminated (which fires 'close' → cleanup).
@@ -2724,7 +2692,6 @@ ${alreadyLinked ? '' : `
2724
2692
  close: () => {},
2725
2693
  };
2726
2694
  chatSubscribers.add(chatSub);
2727
- console.log(`[app-ws-chat] subscribe sub=${subId} clientId=${clientId} total=${chatSubscribers.size}`);
2728
2695
 
2729
2696
  if (agentQueryActive && currentStreamConvId) {
2730
2697
  chatSub.send('chat:state', {
@@ -2742,7 +2709,6 @@ ${alreadyLinked ? '' : `
2742
2709
  if (msg.type === 'chat:unsubscribe') {
2743
2710
  if (chatSub) {
2744
2711
  chatSubscribers.delete(chatSub);
2745
- console.log(`[app-ws-chat] unsubscribe sub=${chatSub.id} total=${chatSubscribers.size}`);
2746
2712
  chatSub = null;
2747
2713
  }
2748
2714
  return;
@@ -2753,10 +2719,8 @@ ${alreadyLinked ? '' : `
2753
2719
  const { id, method, path: reqPath, headers: reqHeaders, body } = msg.data;
2754
2720
  const backendPath = (reqPath || '').replace(/^\/app/, '');
2755
2721
 
2756
- console.log(`[supervisor] App WS → backend :${backendPort} | ${method} ${backendPath} (${id})`);
2757
2722
 
2758
2723
  if (!isBackendAlive()) {
2759
- console.log('[supervisor] App WS: Backend down — returning 503');
2760
2724
  if (ws.readyState === WebSocket.OPEN) {
2761
2725
  ws.send(JSON.stringify({
2762
2726
  type: 'app:api:response',
@@ -2784,7 +2748,6 @@ ${alreadyLinked ? '' : `
2784
2748
  else if (Array.isArray(v)) resHeaders[k] = v.join(', ');
2785
2749
  }
2786
2750
 
2787
- console.log(`[supervisor] App WS ← backend: ${proxyRes.statusCode} (${id})`);
2788
2751
 
2789
2752
  if (ws.readyState === WebSocket.OPEN) {
2790
2753
  ws.send(JSON.stringify({
@@ -2813,10 +2776,8 @@ ${alreadyLinked ? '' : `
2813
2776
  ws.on('close', () => {
2814
2777
  if (chatSub) {
2815
2778
  chatSubscribers.delete(chatSub);
2816
- console.log(`[app-ws-chat] auto-unsubscribe on close sub=${chatSub.id} total=${chatSubscribers.size}`);
2817
2779
  chatSub = null;
2818
2780
  }
2819
- console.log('[supervisor] App API WS client disconnected');
2820
2781
  });
2821
2782
  });
2822
2783
 
@@ -2824,18 +2785,11 @@ ${alreadyLinked ? '' : `
2824
2785
  * subscribers. The workspace mirror sees the exact same event stream the widget does. */
2825
2786
  function broadcastBloby(type: string, data: any = {}) {
2826
2787
  const msg = JSON.stringify({ type, data });
2827
- let wsCount = 0;
2828
2788
  for (const client of blobyWss.clients) {
2829
- if (client.readyState === WebSocket.OPEN) { client.send(msg); wsCount++; }
2789
+ if (client.readyState === WebSocket.OPEN) client.send(msg);
2830
2790
  }
2831
- let sseCount = 0;
2832
2791
  for (const sub of chatSubscribers) {
2833
- try { sub.send(type, data); sseCount++; } catch (e: any) {
2834
- console.log(`[sse-broadcast] send failed sub=${sub.id}: ${e.message}`);
2835
- }
2836
- }
2837
- if (type.startsWith('bot:') || type.startsWith('chat:')) {
2838
- console.log(`[sse-broadcast] type=${type} ws=${wsCount} sse=${sseCount} subs=${chatSubscribers.size}`);
2792
+ try { sub.send(type, data); } catch {}
2839
2793
  }
2840
2794
  }
2841
2795
 
@@ -3406,17 +3360,14 @@ ${alreadyLinked ? '' : `
3406
3360
  // Bloby chat WebSocket — Vite HMR is handled automatically (hmr.server = this server)
3407
3361
  server.on('upgrade', async (req, socket: net.Socket, head) => {
3408
3362
  // Strip the query string: /bloby/ws?token=<7-day session token> must not be logged.
3409
- console.log(`[supervisor] WebSocket upgrade: ${(req.url || '').split('?')[0]} | protocol=${req.headers['sec-websocket-protocol'] || 'none'}`);
3410
3363
 
3411
3364
  // App API WebSocket — no auth (backend handles its own auth)
3412
3365
  if (req.url?.startsWith('/app/ws')) {
3413
- console.log('[supervisor] → App API WebSocket');
3414
3366
  appWss.handleUpgrade(req, socket, head, (ws) => appWss.emit('connection', ws, req));
3415
3367
  return;
3416
3368
  }
3417
3369
 
3418
3370
  if (!req.url?.startsWith('/bloby/ws')) {
3419
- console.log('[supervisor] → Letting Vite handle this upgrade');
3420
3371
  return;
3421
3372
  }
3422
3373
 
@@ -3433,7 +3384,6 @@ ${alreadyLinked ? '' : `
3433
3384
  }
3434
3385
  }
3435
3386
 
3436
- console.log('[supervisor] → Bloby chat WebSocket');
3437
3387
  blobyWss.handleUpgrade(req, socket, head, (ws) => blobyWss.emit('connection', ws, req));
3438
3388
  });
3439
3389
 
@@ -3859,7 +3809,6 @@ ${alreadyLinked ? '' : `
3859
3809
  closeDb();
3860
3810
  await stopBackend();
3861
3811
  stopTunnel();
3862
- console.log('[supervisor] Stopping Vite dev servers...');
3863
3812
  await stopViteDevServers();
3864
3813
  server.close();
3865
3814
  process.exit(0);
@@ -14,6 +14,7 @@ export const SHELL_HTML = `<!DOCTYPE html>
14
14
  <meta charset="UTF-8" />
15
15
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, interactive-widget=resizes-content" />
16
16
  <meta name="theme-color" content="#212121" />
17
+ <meta name="mobile-web-app-capable" content="yes" />
17
18
  <meta name="apple-mobile-web-app-capable" content="yes" />
18
19
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
19
20
  <meta name="apple-mobile-web-app-title" content="Bloby" />
@@ -31,7 +32,6 @@ export const SHELL_HTML = `<!DOCTYPE html>
31
32
  id="bloby-workspace"
32
33
  style="position:fixed;inset:0;width:100vw;height:100dvh;border:none;background:#0A0A0A"
33
34
  allow="camera; microphone; geolocation; clipboard-read; clipboard-write; fullscreen; autoplay; display-capture"
34
- allowfullscreen
35
35
  ></iframe>
36
36
 
37
37
  <script>
@@ -11,9 +11,6 @@ export async function startViteDevServers(supervisorPort: number, hmrServer: htt
11
11
  dashboard: supervisorPort + 2,
12
12
  };
13
13
 
14
- console.log(`[vite-dev] Starting dashboard Vite dev server on :${ports.dashboard}`);
15
- console.log(`[vite-dev] HMR attached to supervisor server`);
16
- console.log(`[vite-dev] PKG_DIR = ${PKG_DIR}`);
17
14
 
18
15
  try {
19
16
  dashboardVite = await createViteServer({
@@ -31,7 +28,6 @@ export async function startViteDevServers(supervisorPort: number, hmrServer: htt
31
28
  logLevel: 'info',
32
29
  });
33
30
  await dashboardVite.listen();
34
- console.log(`[vite-dev] ✓ Dashboard Vite ready on :${ports.dashboard}`);
35
31
  } catch (err) {
36
32
  console.error('[vite-dev] ✗ Dashboard Vite failed:', err);
37
33
  throw err;
@@ -58,15 +54,12 @@ export async function startViteDevServers(supervisorPort: number, hmrServer: htt
58
54
  /** Tell connected browsers to full-reload via Vite's HMR WebSocket */
59
55
  export function reloadDashboard(): void {
60
56
  if (!dashboardVite) return;
61
- console.log('[vite-dev] Sending full-reload to dashboard clients');
62
57
  dashboardVite.hot.send({ type: 'full-reload', path: '*' });
63
58
  }
64
59
 
65
60
  export async function stopViteDevServers(): Promise<void> {
66
- console.log('[vite-dev] Stopping Vite dev servers...');
67
61
  if (dashboardVite) {
68
62
  await dashboardVite.close();
69
63
  dashboardVite = null;
70
- console.log('[vite-dev] Dashboard Vite stopped');
71
64
  }
72
65
  }
@@ -684,7 +684,6 @@
684
684
  var toggleCooldown = false;
685
685
 
686
686
  function toggle() {
687
- console.log('[widget] toggle called', { canvasPhase: canvasPhase, isOpen: isOpen });
688
687
  if (canvasPhase === 'splash' || canvasPhase === 'transitioning') {
689
688
  skipToBubble();
690
689
  try { sessionStorage.setItem(SPLASH_KEY, '1'); } catch(e) {}
@@ -709,14 +708,12 @@
709
708
  // Hold detected via pointerdown timer. pointerup stops recording.
710
709
 
711
710
  bubble.addEventListener('pointerdown', function(e) {
712
- console.log('[widget] pointerdown', { canvasPhase: canvasPhase, hpState: hpState, type: e.pointerType });
713
711
  if (canvasPhase !== 'bubble') return;
714
712
  e.preventDefault(); // Prevent iOS long-press text selection
715
713
  hpPointerDown = true;
716
714
  hpWasHold = false;
717
715
 
718
716
  hpHoldTimer = setTimeout(function() {
719
- console.log('[widget] hold timer fired');
720
717
  hpWasHold = true;
721
718
  if (hpState === 'idle' || hpState === 'deactivating') {
722
719
  startHpRecording();
@@ -725,7 +722,6 @@
725
722
  });
726
723
 
727
724
  bubble.addEventListener('pointerup', function() {
728
- console.log('[widget] pointerup', { hpState: hpState, hpWasHold: hpWasHold });
729
725
  hpPointerDown = false;
730
726
  if (hpHoldTimer) { clearTimeout(hpHoldTimer); hpHoldTimer = null; }
731
727
  if (hpState === 'activating' || hpState === 'recording') {
@@ -743,7 +739,6 @@
743
739
  });
744
740
 
745
741
  bubble.addEventListener('pointercancel', function() {
746
- console.log('[widget] pointercancel');
747
742
  hpPointerDown = false;
748
743
  if (hpHoldTimer) { clearTimeout(hpHoldTimer); hpHoldTimer = null; }
749
744
  if (hpState === 'activating' || hpState === 'recording') stopHpRecording(true);
@@ -752,7 +747,6 @@
752
747
  // click still fires on desktop (pointerdown preventDefault does not suppress it there).
753
748
  // On mobile, pointerup already handled the tap, so this is a no-op guard.
754
749
  bubble.addEventListener('click', function(e) {
755
- console.log('[widget] click', { canvasPhase: canvasPhase, hpWasHold: hpWasHold, hpState: hpState, isOpen: isOpen });
756
750
  // Prevent double-toggle: pointerup already handled taps.
757
751
  e.stopPropagation();
758
752
  });
@@ -44,7 +44,6 @@
44
44
  function flog(event, extra) {
45
45
  var entry = { t: new Date().toISOString(), e: event };
46
46
  if (extra) entry.x = String(extra).slice(0, 600);
47
- console.log('[bloby-forensics]', entry.t, event, extra || '');
48
47
  try {
49
48
  var arr = JSON.parse(sessionStorage.getItem(FORENSICS_KEY) || '[]');
50
49
  arr.push(entry);
@@ -53,22 +52,15 @@
53
52
  } catch (e) {}
54
53
  }
55
54
 
56
- // On load: report how we got here + dump the trail the previous page session left behind.
55
+ // On load: record how we got here into the forensics ring buffer. The trail the previous
56
+ // page session left behind survives the reload in sessionStorage under FORENSICS_KEY and can
57
+ // be inspected there after a mystery refresh.
57
58
  (function () {
58
59
  var navType = '?';
59
60
  try {
60
61
  var nav = performance.getEntriesByType('navigation')[0];
61
62
  if (nav) navType = nav.type; // 'navigate' | 'reload' | 'back_forward' | 'prerender'
62
63
  } catch (e) {}
63
- var trail = null;
64
- try { trail = sessionStorage.getItem(FORENSICS_KEY); } catch (e) {}
65
- console.log(
66
- '%c[bloby-forensics] PAGE LOADED — navigation.type=' + navType +
67
- ' wasDiscarded=' + (document.wasDiscarded === true) +
68
- '\nTrail left by the previous page session (newest last):',
69
- 'color:#4AEEFF;font-weight:bold',
70
- trail ? JSON.parse(trail) : '(none — first load in this tab)'
71
- );
72
64
  flog('page-load', 'navType=' + navType + ' wasDiscarded=' + (document.wasDiscarded === true) + ' visible=' + document.visibilityState);
73
65
  })();
74
66
 
@@ -4,6 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, interactive-widget=resizes-content" />
6
6
  <meta name="theme-color" content="#212121" />
7
+ <meta name="mobile-web-app-capable" content="yes" />
7
8
  <meta name="apple-mobile-web-app-capable" content="yes" />
8
9
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
9
10
  <meta name="apple-mobile-web-app-title" content="Bloby" />
@@ -14,25 +14,22 @@ const CACHE = 'bloby-v24';
14
14
  // Without this, the first navigation isn't intercepted (SW wasn't
15
15
  // controlling yet), so refresh would find an empty cache → white screen.
16
16
  self.addEventListener('install', (e) => {
17
- console.log('[SW] installing, cache:', CACHE);
18
17
  e.waitUntil(
19
18
  caches.open(CACHE)
20
19
  .then(c => c.add('/'))
21
- .then(() => { console.log('[SW] precached / — calling skipWaiting'); return self.skipWaiting(); })
20
+ .then(() => self.skipWaiting())
22
21
  .catch(err => { console.error('[SW] install failed:', err); throw err; })
23
22
  );
24
23
  });
25
24
 
26
25
  self.addEventListener('activate', (e) => {
27
- console.log('[SW] activating, cache:', CACHE);
28
26
  e.waitUntil(
29
27
  caches.keys()
30
28
  .then(keys => {
31
29
  const old = keys.filter(k => k !== CACHE);
32
- if (old.length) console.log('[SW] deleting old caches:', old);
33
30
  return Promise.all(old.map(k => caches.delete(k)));
34
31
  })
35
- .then(() => { console.log('[SW] claiming clients'); return self.clients.claim(); })
32
+ .then(() => self.clients.claim())
36
33
  );
37
34
  });
38
35
 
@@ -70,18 +67,16 @@ self.addEventListener('fetch', (event) => {
70
67
  // /bloby/* is a separate app — let it go to network to avoid
71
68
  // caching the wrong HTML under the / key.
72
69
  if (request.mode === 'navigate') {
73
- console.log('[SW] navigate →', url.pathname);
74
70
  if (url.pathname === '/' || url.pathname === '/index.html') {
75
71
  // Network-first: always fetch the live dashboard; cache only as offline fallback.
76
72
  event.respondWith(caches.open(CACHE).then(c =>
77
73
  fetch(request)
78
74
  .then(r => { if (r.ok) c.put('/', r.clone()); return r; })
79
- .catch(err => { console.warn('[SW] / network failed, using cache:', err.message); return c.match('/'); })
75
+ .catch(() => c.match('/'))
80
76
  ));
81
77
  return;
82
78
  }
83
79
  // Other navigations (/bloby/*, etc.) — network only
84
- console.log('[SW] navigate (network-only) →', url.pathname);
85
80
  return;
86
81
  }
87
82
 
@@ -14,14 +14,11 @@ export default function DashboardLayout({ children, userName, botName = 'Bloby'
14
14
 
15
15
  useEffect(() => {
16
16
  const check = () => {
17
- console.log('[health] checking /app/api/health…');
18
17
  fetch('/app/api/health', { signal: AbortSignal.timeout(3000) })
19
18
  .then((r) => {
20
- console.log(`[health] response: ${r.status} ok=${r.ok}`);
21
19
  setStatus(r.ok ? 'healthy' : 'restarting');
22
20
  })
23
21
  .catch((err) => {
24
- console.warn('[health] fetch failed:', err.message ?? err);
25
22
  setStatus('restarting');
26
23
  });
27
24
  };