pi-sdk-web 0.4.5 → 0.4.7

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.
@@ -786,7 +786,9 @@ class PiWebClient {
786
786
  body.className = 'special-body body-text';
787
787
  div.appendChild(labelEl);
788
788
  div.appendChild(body);
789
- div.addEventListener('click', () => this.toggleSpecial(div));
789
+ // Click to expand/collapse, but NOT when the pointer was dragged (see
790
+ // bindExpandToggle - same drag-vs-click logic as tool blocks).
791
+ this.bindExpandToggle(div, (d) => this.toggleSpecial(d));
790
792
  this.contentEl.appendChild(div);
791
793
  return div;
792
794
  }
@@ -1151,6 +1153,25 @@ class PiWebClient {
1151
1153
  }
1152
1154
  }
1153
1155
 
1156
+ /**
1157
+ * Add expand/collapse-on-click to a block, while preserving text selection.
1158
+ *
1159
+ * A drag that selects text ends with mouseup -> click; toggling then would
1160
+ * re-render the block and wipe the selection. Record the mousedown
1161
+ * position and skip the toggle when the pointer moved more than 3px
1162
+ * (anything beyond that is a drag, not a click - 3px tolerates hand tremor
1163
+ * on a plain click).
1164
+ */
1165
+ bindExpandToggle(div, toggleFn) {
1166
+ let downX = 0, downY = 0;
1167
+ div.addEventListener('mousedown', (e) => { downX = e.clientX; downY = e.clientY; });
1168
+ div.addEventListener('click', (e) => {
1169
+ const dx = e.clientX - downX, dy = e.clientY - downY;
1170
+ if (dx * dx + dy * dy > 9) return; // dragged: preserve selection
1171
+ toggleFn(div);
1172
+ });
1173
+ }
1174
+
1154
1175
  createToolBlock(toolName, args, toolCallId) {
1155
1176
  const div = document.createElement('div');
1156
1177
  div.className = 'tool-block pending';
@@ -1203,8 +1224,9 @@ class PiWebClient {
1203
1224
  div.appendChild(content);
1204
1225
  div.appendChild(bottom);
1205
1226
 
1206
- // Click to expand/collapse
1207
- div.addEventListener('click', () => this.toggleToolExpand(div));
1227
+ // Click to expand/collapse, but NOT when the pointer was dragged (see
1228
+ // bindExpandToggle - a drag that selected text must preserve selection).
1229
+ this.bindExpandToggle(div, (d) => this.toggleToolExpand(d));
1208
1230
 
1209
1231
  this.contentEl.appendChild(div);
1210
1232
  return div;
@@ -1331,6 +1353,12 @@ class PiWebClient {
1331
1353
  if (!div) {
1332
1354
  div = this.createToolBlock('bash', { command: this.pendingBashCommand || '' }, key);
1333
1355
  div.className = 'tool-block pending';
1356
+ // Running indicator (spinner + label) - makes it obvious the stream
1357
+ // is still live even when output is quiet; removed on bash_result.
1358
+ const run = document.createElement('span');
1359
+ run.className = 'bash-running';
1360
+ run.textContent = '⟳ running';
1361
+ div.querySelector('.tool-title')?.appendChild(run);
1334
1362
  // Stop button in the title row (TUI Ctrl+C equivalent): abort_bash
1335
1363
  // kills only this running bash. Removed when the block finalizes.
1336
1364
  const stopBtn = document.createElement('button');
@@ -1351,7 +1379,14 @@ class PiWebClient {
1351
1379
  const bashPending = document.getElementById('bash-pending');
1352
1380
  if (bashPending) {
1353
1381
  bashPending.appendChild(div);
1382
+ // Area-level status line at the TAIL (always visible even when the
1383
+ // block's own title scrolled out of view while output streams).
1384
+ const statusLine = document.createElement('div');
1385
+ statusLine.className = 'bash-stream-status';
1386
+ statusLine.textContent = '⟳ running…';
1387
+ bashPending.appendChild(statusLine);
1354
1388
  bashPending.style.display = 'block';
1389
+ bashPending.scrollTop = bashPending.scrollHeight; // keep tail visible
1355
1390
  } else {
1356
1391
  this.contentEl.appendChild(div);
1357
1392
  }
@@ -1362,6 +1397,12 @@ class PiWebClient {
1362
1397
  const full = (div.dataset.fullOutput || '') + delta;
1363
1398
  div.dataset.fullOutput = full;
1364
1399
  this.applyToolPreview(div);
1400
+ // Keep the stream tail (and its running status line) visible inside the
1401
+ // pending area while output is coming in.
1402
+ const bashPending = document.getElementById('bash-pending');
1403
+ if (bashPending && bashPending.contains(div)) {
1404
+ bashPending.scrollTop = bashPending.scrollHeight;
1405
+ }
1365
1406
  if (this.wasAtBottom()) this.scrollToBottom();
1366
1407
  }
1367
1408
 
@@ -1384,12 +1425,16 @@ class PiWebClient {
1384
1425
  const bashPending = document.getElementById('bash-pending');
1385
1426
  if (bashPending && bashPending.contains(div)) {
1386
1427
  this.contentEl.appendChild(div);
1428
+ // Remove the area-level running status line, then hide the empty area.
1429
+ bashPending.querySelectorAll('.bash-stream-status').forEach((el) => el.remove());
1387
1430
  if (bashPending.children.length === 0) {
1388
1431
  bashPending.style.display = 'none';
1389
1432
  }
1390
1433
  this.scrollToBottom();
1391
1434
  }
1392
- // Bash done: the stop button is no longer relevant.
1435
+ // Bash done: the running indicator and stop button are no longer
1436
+ // relevant.
1437
+ div.querySelector('.bash-running')?.remove();
1393
1438
  div.querySelector('.bash-stop-btn')?.remove();
1394
1439
 
1395
1440
  const isError = result.exitCode !== undefined && result.exitCode !== 0;
@@ -63,6 +63,8 @@ body.theme-light {
63
63
  box-sizing: border-box;
64
64
  margin: 0;
65
65
  padding: 0;
66
+ user-select: text;
67
+ -webkit-user-select: text;
66
68
  }
67
69
 
68
70
  html, body {
@@ -162,8 +164,7 @@ body {
162
164
  border: 1px solid var(--border);
163
165
  border-radius: 6px;
164
166
  padding: 6px 10px;
165
- cursor: pointer;
166
- user-select: none;
167
+ cursor: text;
167
168
  font-size: 12px;
168
169
  }
169
170
 
@@ -268,8 +269,20 @@ body {
268
269
  background: var(--tool-pending-bg);
269
270
  margin: 4px 0;
270
271
  font-size: 13px;
271
- cursor: pointer;
272
+ }
273
+
274
+ .tool-block .tool-title,
275
+ .tool-block .tool-meta {
272
276
  user-select: none;
277
+ cursor: pointer;
278
+ }
279
+
280
+ /* Tool args (the JSON parameter summary) and output are informative
281
+ content: selectable/copyable with a text cursor. */
282
+ .tool-block .tool-args,
283
+ .tool-block .tool-output {
284
+ user-select: text;
285
+ cursor: text;
273
286
  }
274
287
 
275
288
  .tool-block.success {
@@ -337,8 +350,6 @@ body {
337
350
  margin: 4px 0;
338
351
  padding: 6px 12px;
339
352
  border-radius: 6px;
340
- cursor: pointer;
341
- user-select: none;
342
353
  }
343
354
 
344
355
  .special-block .special-label {
@@ -346,11 +357,15 @@ body {
346
357
  font-weight: bold;
347
358
  font-size: 12px;
348
359
  margin-bottom: 4px;
360
+ user-select: none;
361
+ cursor: pointer;
349
362
  }
350
363
 
351
364
  .special-block .special-body {
352
365
  color: var(--text);
353
366
  font-size: 13px;
367
+ user-select: text;
368
+ cursor: text;
354
369
  }
355
370
 
356
371
  .special-block .special-body > *:first-child {
@@ -549,8 +564,11 @@ body {
549
564
  pendingMessagesContainer). Moved into the message flow on completion. */
550
565
  #bash-pending {
551
566
  display: none;
552
- padding: 4px 12px;
553
- border-top: 1px solid var(--border);
567
+ padding: 8px 12px;
568
+ margin: 6px 8px 4px;
569
+ border: 1px solid var(--border);
570
+ border-radius: 6px;
571
+ background: var(--code-bg);
554
572
  font-size: 13px;
555
573
  /* ~10 lines of output visible; scrolling inside the block for more */
556
574
  max-height: calc(10 * 1.5em);
@@ -569,6 +587,38 @@ body {
569
587
  vertical-align: middle;
570
588
  }
571
589
 
590
+ .bash-running {
591
+ margin-left: 8px;
592
+ font-size: 11px;
593
+ color: var(--accent);
594
+ vertical-align: middle;
595
+ }
596
+
597
+ .bash-running::before {
598
+ content: '⟳';
599
+ display: inline-block;
600
+ margin-right: 4px;
601
+ animation: bash-spin 1s linear infinite;
602
+ }
603
+
604
+ @keyframes bash-spin {
605
+ from { transform: rotate(0deg); }
606
+ to { transform: rotate(360deg); }
607
+ }
608
+
609
+ .bash-stream-status {
610
+ margin-top: 2px;
611
+ font-size: 11px;
612
+ color: var(--accent);
613
+ }
614
+
615
+ .bash-stream-status::before {
616
+ content: '⟳';
617
+ display: inline-block;
618
+ margin-right: 4px;
619
+ animation: bash-spin 1s linear infinite;
620
+ }
621
+
572
622
  .bash-stop-btn:hover {
573
623
  background: color-mix(in srgb, var(--error) 15%, transparent);
574
624
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-sdk-web",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "description": "Browser Web access for Pi (AI coding agent) via the Pi SDK - standalone module, zero modification to Pi itself",
5
5
  "type": "module",
6
6
  "bin": {