@kodaris/krubble-app-components 1.0.116 → 1.0.117

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.
@@ -5854,11 +5854,26 @@
5854
5854
  */
5855
5855
  this._drawerOpen = false;
5856
5856
  /**
5857
- * Which view the drawer shows — settings or debug. One drawer holds both; the
5858
- * header buttons switch this and the content/width swap inside. Never null, so
5859
- * the content stays visible while the drawer slides shut.
5857
+ * Which view the drawer shows — settings, debug, or a single tool call's full
5858
+ * detail. One drawer holds all three; the header buttons switch settings/debug
5859
+ * and clicking a tool line switches to `toolDetail`. The content/width swap
5860
+ * inside. Never null, so the content stays visible while the drawer slides shut.
5860
5861
  */
5861
5862
  this._drawerView = 'settings';
5863
+ /**
5864
+ * The tool call whose full input/output the `toolDetail` drawer view shows —
5865
+ * the clicked `tool_use` message and its paired `tool_result` message, stored
5866
+ * whole. The render reads `.data.*` off them (and formats for display) so we
5867
+ * don't duplicate the message shape here. Transient — set on click, never
5868
+ * persisted (its bytes can be large and the view is a peek). Null when none.
5869
+ */
5870
+ this._toolDetail = null;
5871
+ /** Which tool-detail block ('input' | 'output') just flashed "Copied" (null = none). */
5872
+ this._toolDetailCopied = null;
5873
+ /** Whether the tool-detail Input section is expanded. Both open on open. */
5874
+ this._toolDetailInputOpen = true;
5875
+ /** Whether the tool-detail Output section is expanded. */
5876
+ this._toolDetailOutputOpen = true;
5862
5877
  /** Current width of the drawer in px (user-resizable, persisted). */
5863
5878
  this._drawerWidth = 430;
5864
5879
  /** True while the user is dragging the drawer's resize handle. */
@@ -6377,6 +6392,12 @@
6377
6392
  }
6378
6393
  _saveState() {
6379
6394
  try {
6395
+ // `toolDetail` is a transient peek, not a saved view — persist only the two
6396
+ // real preference views so a reload never reopens a stale tool.
6397
+ let persistedDrawerView = this._drawerView;
6398
+ if (persistedDrawerView === 'toolDetail') {
6399
+ persistedDrawerView = 'settings';
6400
+ }
6380
6401
  const state = {
6381
6402
  opened: String(this.opened),
6382
6403
  message: this._inputValue,
@@ -6385,7 +6406,7 @@
6385
6406
  showToolInputs: String(this._showToolInputs),
6386
6407
  showConfirmationInput: String(this._showConfirmationInput),
6387
6408
  drawerOpen: String(this._drawerOpen),
6388
- drawerView: this._drawerView,
6409
+ drawerView: persistedDrawerView,
6389
6410
  drawerWidth: String(this._drawerWidth),
6390
6411
  };
6391
6412
  if (this._panelEl) {
@@ -7528,6 +7549,162 @@
7528
7549
  this._drawerOpen = false;
7529
7550
  this._saveState();
7530
7551
  }
7552
+ /**
7553
+ * Open the drawer to a single tool call's full input/output. Called when the
7554
+ * user clicks a tool-activity line in the transcript. Independent of the
7555
+ * `_showToolInputs` setting — this is the on-demand "show me everything for
7556
+ * THIS call" peek, so it works even when inline inputs are hidden. `result` is
7557
+ * the paired `tool_result` message data (may be absent if the tool hasn't
7558
+ * returned yet).
7559
+ */
7560
+ _openToolDetail(use) {
7561
+ // Pair the tool_use to the tool_result that immediately follows it (matched by
7562
+ // toolUseId) — the same pairing the transcript render uses for the ⎿ preview.
7563
+ const next = this.messages[this.messages.indexOf(use) + 1];
7564
+ this._toolDetail = {
7565
+ use,
7566
+ result: null,
7567
+ };
7568
+ if (next?.type === 'tool_result' && next.data?.toolUseId === use.data?.toolUseId) {
7569
+ this._toolDetail.result = next;
7570
+ }
7571
+ this._toolDetailCopied = null;
7572
+ this._toolDetailInputOpen = true;
7573
+ this._toolDetailOutputOpen = true;
7574
+ this._drawerView = 'toolDetail';
7575
+ this._drawerOpen = true;
7576
+ this._saveState();
7577
+ // Reset the panel scroll so a newly-opened tool starts at the top, rather
7578
+ // than inheriting the previous tool's scroll position. Wait for the render.
7579
+ this.updateComplete.then(() => {
7580
+ if (this._drawerBodyEl) {
7581
+ this._drawerBodyEl.scrollTop = 0;
7582
+ }
7583
+ });
7584
+ }
7585
+ /** Toggle the tool-detail Input section open/closed. */
7586
+ _handleToggleToolDetailInput() {
7587
+ this._toolDetailInputOpen = !this._toolDetailInputOpen;
7588
+ }
7589
+ /** Toggle the tool-detail Output section open/closed. */
7590
+ _handleToggleToolDetailOutput() {
7591
+ this._toolDetailOutputOpen = !this._toolDetailOutputOpen;
7592
+ }
7593
+ /**
7594
+ * Pretty-print a tool input/result for the detail view. The values arrive as
7595
+ * strings that usually hold JSON; reindent when they do, otherwise show as-is.
7596
+ * Never throws — falls back to the original text.
7597
+ *
7598
+ * A large/overflowing result is stashed server-side and returned as a preview
7599
+ * wrapped in `<<<uuid_BEGIN>>> … <<<uuid_END>>>` markers, followed by a paging
7600
+ * note. Two things make `JSON.parse` unusable here: the marker wrapper isn't
7601
+ * JSON, and the payload between the markers is a truncated SLICE of a bigger
7602
+ * blob (cut off mid-string), so it never parses on its own. So we reindent the
7603
+ * payload STRUCTURALLY (see `_reindentJson`) rather than parse it, and keep the
7604
+ * markers + note exactly where they were so it still reads as a paged response.
7605
+ */
7606
+ _formatToolJson(value) {
7607
+ if (value === undefined || value === null) {
7608
+ return '';
7609
+ }
7610
+ const raw = String(value);
7611
+ const marker = raw.match(/(<<<[0-9a-f-]+_BEGIN>>>\n?)([\s\S]*?)(\n?<<<[0-9a-f-]+_END>>>)/);
7612
+ if (marker && marker.index != null) {
7613
+ // Splice by index rather than String.replace — the reindented JSON can
7614
+ // contain `$` sequences (e.g. "$dtoRef") that replace() treats as special.
7615
+ const start = marker.index + marker[1].length;
7616
+ const end = start + marker[2].length;
7617
+ return raw.slice(0, start) + this._reindentJson(marker[2]) + raw.slice(end);
7618
+ }
7619
+ return this._reindentJson(raw);
7620
+ }
7621
+ /**
7622
+ * Reindent JSON-ish text for display. Tries a strict parse+stringify first (the
7623
+ * clean path for complete, valid JSON). If that fails — which it does for the
7624
+ * truncated slices returned by the stashed-response pager — it falls back to a
7625
+ * structural walk that inserts newlines/indentation around `{}[],:` while
7626
+ * tracking string state (so braces and commas INSIDE string values, and escaped
7627
+ * quotes, are left alone). This formats even incomplete JSON. Non-JSON text
7628
+ * (no leading `{`/`[`) is returned untouched.
7629
+ */
7630
+ _reindentJson(text) {
7631
+ const trimmed = text.trim();
7632
+ if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) {
7633
+ return text;
7634
+ }
7635
+ try {
7636
+ return JSON.stringify(JSON.parse(trimmed), null, 2);
7637
+ }
7638
+ catch {
7639
+ // Fall through to the tolerant structural reindent below.
7640
+ }
7641
+ let out = '';
7642
+ let depth = 0;
7643
+ let inString = false;
7644
+ let escaped = false;
7645
+ const pad = (level) => '\n' + ' '.repeat(Math.max(0, level));
7646
+ for (let i = 0; i < trimmed.length; i++) {
7647
+ const ch = trimmed[i];
7648
+ if (inString) {
7649
+ out += ch;
7650
+ if (escaped) {
7651
+ escaped = false;
7652
+ }
7653
+ else if (ch === '\\') {
7654
+ escaped = true;
7655
+ }
7656
+ else if (ch === '"') {
7657
+ inString = false;
7658
+ }
7659
+ continue;
7660
+ }
7661
+ if (ch === '"') {
7662
+ inString = true;
7663
+ out += ch;
7664
+ }
7665
+ else if (ch === '{' || ch === '[') {
7666
+ depth++;
7667
+ out += ch + pad(depth);
7668
+ }
7669
+ else if (ch === '}' || ch === ']') {
7670
+ depth--;
7671
+ out += pad(depth) + ch;
7672
+ }
7673
+ else if (ch === ',') {
7674
+ out += ch + pad(depth);
7675
+ }
7676
+ else if (ch === ':') {
7677
+ out += ': ';
7678
+ }
7679
+ else if (ch === ' ' || ch === '\n' || ch === '\t' || ch === '\r') ;
7680
+ else {
7681
+ out += ch;
7682
+ }
7683
+ }
7684
+ return out;
7685
+ }
7686
+ /** Copy the tool-detail Input to the clipboard. Stops propagation so the click
7687
+ * doesn't also toggle the (parent) section header. */
7688
+ _handleCopyToolDetailInput(e) {
7689
+ e.stopPropagation();
7690
+ this._copyToolDetail('input', this._formatToolJson(this._toolDetail?.use.data?.toolInput));
7691
+ }
7692
+ /** Copy the tool-detail Output to the clipboard (see input handler). */
7693
+ _handleCopyToolDetailOutput(e) {
7694
+ e.stopPropagation();
7695
+ this._copyToolDetail('output', this._formatToolJson(this._toolDetail?.result?.data?.toolResultString));
7696
+ }
7697
+ /** Write one tool-detail block to the clipboard and flash "Copied" on it. */
7698
+ _copyToolDetail(block, content) {
7699
+ navigator.clipboard.writeText(content).then(() => {
7700
+ this._toolDetailCopied = block;
7701
+ setTimeout(() => {
7702
+ if (this._toolDetailCopied === block) {
7703
+ this._toolDetailCopied = null;
7704
+ }
7705
+ }, 2000);
7706
+ }).catch(() => { });
7707
+ }
7531
7708
  _handleDrawerResizeMouseDown(e) {
7532
7709
  this._isDrawerResizing = true;
7533
7710
  this._drawerResizeStartX = e.clientX;
@@ -8134,6 +8311,16 @@
8134
8311
  const isDebug = this._drawerView === 'debug'
8135
8312
  && this.variant === 'inline'
8136
8313
  && this._aiFrameworkConfig.debugTelemetryEnabled === true;
8314
+ let title = 'Settings';
8315
+ let body = this._renderSettingsContent();
8316
+ if (isDebug) {
8317
+ title = 'Debug';
8318
+ body = this._renderDebugContent();
8319
+ }
8320
+ else if (this._drawerView === 'toolDetail' && this._toolDetail) {
8321
+ title = this._toolDetail.use.data?.displayName || this._toolDetail.use.data?.toolName || 'Tool';
8322
+ body = this._renderToolDetailContent();
8323
+ }
8137
8324
  return b `
8138
8325
  <div
8139
8326
  class=${e({
@@ -8148,7 +8335,7 @@
8148
8335
  @mousedown=${this._handleDrawerResizeMouseDown}
8149
8336
  ></div>
8150
8337
  <div class="drawer__header">
8151
- <span class="drawer__title">${isDebug ? 'Debug' : 'Settings'}</span>
8338
+ <span class="drawer__title">${title}</span>
8152
8339
  <button
8153
8340
  class="header-action"
8154
8341
  @click=${this._handleCloseDrawer}
@@ -8171,7 +8358,7 @@
8171
8358
  </button>
8172
8359
  </div>
8173
8360
  <div class="drawer__body">
8174
- ${isDebug ? this._renderDebugContent() : this._renderSettingsContent()}
8361
+ ${body}
8175
8362
  </div>
8176
8363
  </div>
8177
8364
  `;
@@ -8203,6 +8390,129 @@
8203
8390
  ></kr-checkbox>
8204
8391
  </div>
8205
8392
  </div>
8393
+ `;
8394
+ }
8395
+ /**
8396
+ * The Tool detail view's body — the full input and output for one tool call,
8397
+ * pretty-printed in scrollable code blocks with a per-block copy button. This
8398
+ * is the "occasionally see everything" path: the transcript stays terse, and
8399
+ * clicking a tool line drops its complete input/output here, uncapped (the
8400
+ * blocks scroll rather than truncating).
8401
+ */
8402
+ _renderToolDetailContent() {
8403
+ return b `
8404
+ <div class="tool-detail">
8405
+ <div class="tool-detail__section">
8406
+ <button
8407
+ type="button"
8408
+ class="tool-detail__section-head"
8409
+ aria-expanded=${this._toolDetailInputOpen}
8410
+ @click=${this._handleToggleToolDetailInput}
8411
+ >
8412
+ <span class=${e({
8413
+ 'tool-detail__caret': true,
8414
+ 'tool-detail__caret--open': this._toolDetailInputOpen,
8415
+ })}>›</span>
8416
+ <span class="tool-detail__section-title">Input</span>
8417
+ <span
8418
+ class="tool-detail__copy"
8419
+ role="button"
8420
+ @click=${this._handleCopyToolDetailInput}
8421
+ >
8422
+ ${this._toolDetailCopied === 'input'
8423
+ ? b `
8424
+ <svg
8425
+ viewBox="0 0 24 24"
8426
+ fill="none"
8427
+ stroke="currentColor"
8428
+ stroke-width="1.5"
8429
+ stroke-linecap="round"
8430
+ stroke-linejoin="round"
8431
+ >
8432
+ <path d="M20 6 9 17l-5-5" />
8433
+ </svg>
8434
+ `
8435
+ : b `
8436
+ <svg
8437
+ viewBox="0 0 24 24"
8438
+ fill="none"
8439
+ stroke="currentColor"
8440
+ stroke-width="1.5"
8441
+ stroke-linecap="round"
8442
+ stroke-linejoin="round"
8443
+ >
8444
+ <rect
8445
+ x="9"
8446
+ y="9"
8447
+ width="13"
8448
+ height="13"
8449
+ rx="2"
8450
+ />
8451
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
8452
+ </svg>
8453
+ `}
8454
+ </span>
8455
+ </button>
8456
+ ${this._toolDetailInputOpen
8457
+ ? b `<pre class="tool-detail__code">${this._formatToolJson(this._toolDetail.use.data?.toolInput) || '(no input)'}</pre>`
8458
+ : A}
8459
+ </div>
8460
+ <div class="tool-detail__section">
8461
+ <button
8462
+ type="button"
8463
+ class="tool-detail__section-head"
8464
+ aria-expanded=${this._toolDetailOutputOpen}
8465
+ @click=${this._handleToggleToolDetailOutput}
8466
+ >
8467
+ <span class=${e({
8468
+ 'tool-detail__caret': true,
8469
+ 'tool-detail__caret--open': this._toolDetailOutputOpen,
8470
+ })}>›</span>
8471
+ <span class="tool-detail__section-title">Output</span>
8472
+ <span
8473
+ class="tool-detail__copy"
8474
+ role="button"
8475
+ @click=${this._handleCopyToolDetailOutput}
8476
+ >
8477
+ ${this._toolDetailCopied === 'output'
8478
+ ? b `
8479
+ <svg
8480
+ viewBox="0 0 24 24"
8481
+ fill="none"
8482
+ stroke="currentColor"
8483
+ stroke-width="1.5"
8484
+ stroke-linecap="round"
8485
+ stroke-linejoin="round"
8486
+ >
8487
+ <path d="M20 6 9 17l-5-5" />
8488
+ </svg>
8489
+ `
8490
+ : b `
8491
+ <svg
8492
+ viewBox="0 0 24 24"
8493
+ fill="none"
8494
+ stroke="currentColor"
8495
+ stroke-width="1.5"
8496
+ stroke-linecap="round"
8497
+ stroke-linejoin="round"
8498
+ >
8499
+ <rect
8500
+ x="9"
8501
+ y="9"
8502
+ width="13"
8503
+ height="13"
8504
+ rx="2"
8505
+ />
8506
+ <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
8507
+ </svg>
8508
+ `}
8509
+ </span>
8510
+ </button>
8511
+ ${this._toolDetailOutputOpen
8512
+ ? b `<pre class="tool-detail__code">${this._formatToolJson(this._toolDetail.result?.data?.toolResultString) || '(no output yet)'}</pre>`
8513
+ : A}
8514
+ </div>
8515
+ </div>
8206
8516
  `;
8207
8517
  }
8208
8518
  /** The Debug view's body — telemetry scopes and the per-iteration table. */
@@ -8566,8 +8876,17 @@
8566
8876
  catch {
8567
8877
  formatted = String(result.toolResultString);
8568
8878
  }
8879
+ // Cap the inline preview by BOTH lines and characters. Line-only
8880
+ // truncation blows up on results whose first line is huge (e.g. a
8881
+ // minified-JSON blob or a stashed-response marker with a 30k-char
8882
+ // opening line) — 3 lines then still dumps tens of thousands of
8883
+ // chars into the transcript. Char-cap first (content-blind, like
8884
+ // the backend chokepoint), then keep at most 3 lines of that.
8569
8885
  const lines = formatted.split('\n');
8570
8886
  resultPreview = lines.slice(0, 3).join('\n');
8887
+ if (resultPreview.length > 200) {
8888
+ resultPreview = `${resultPreview.slice(0, 200)}…`;
8889
+ }
8571
8890
  resultRemainder = Math.max(0, lines.length - 3);
8572
8891
  }
8573
8892
  }
@@ -8596,24 +8915,32 @@
8596
8915
  })}>
8597
8916
  ${msg.type === 'tool_use' ? b `
8598
8917
  <div class="tool-activity-block">
8599
- <div class=${e({
8918
+ <button
8919
+ type="button"
8920
+ class=${e({
8600
8921
  'tool-activity': true,
8601
8922
  'tool-activity--error': result?.toolResultStatus === 'error',
8602
- })}>
8923
+ })}
8924
+ @click=${() => this._openToolDetail(msg)}
8925
+ >
8603
8926
  <span class="tool-activity__chevron">›</span>
8604
8927
  <span class="tool-activity__label">${msg.data?.displayName || msg.data?.toolName || 'Tool'}</span>
8605
8928
  ${inputSummary ? b `<span class="tool-activity__input">(${inputSummary})</span>` : A}
8606
- </div>
8929
+ </button>
8607
8930
  ${this._showToolInputs && result?.toolResultString ? b `
8608
- <div class=${e({
8931
+ <button
8932
+ type="button"
8933
+ class=${e({
8609
8934
  'tool-activity__result': true,
8610
8935
  'tool-activity__result--error': result.toolResultStatus === 'error',
8611
- })}>
8936
+ })}
8937
+ @click=${() => this._openToolDetail(msg)}
8938
+ >
8612
8939
  <span class="tool-activity__connector">⎿</span>
8613
8940
  <pre class="tool-activity__result-body">${resultPreview}${resultRemainder > 0
8614
8941
  ? b `\n<span class="tool-activity__more">… +${resultRemainder} lines</span>`
8615
8942
  : A}</pre>
8616
- </div>
8943
+ </button>
8617
8944
  ` : A}
8618
8945
  </div>
8619
8946
  ` : A}
@@ -9218,6 +9545,102 @@
9218
9545
  padding: 6px 0;
9219
9546
  }
9220
9547
 
9548
+ /* Tool detail view — the full input/output for one tool call. */
9549
+ .tool-detail {
9550
+ display: flex;
9551
+ flex-direction: column;
9552
+ gap: 24px;
9553
+ padding: 16px 20px;
9554
+ }
9555
+
9556
+ .tool-detail__section {
9557
+ display: flex;
9558
+ flex-direction: column;
9559
+ gap: 8px;
9560
+ min-width: 0;
9561
+ }
9562
+
9563
+ /* Clickable section header — toggles the code block below. Button chrome is
9564
+ stripped so it reads as a heading row. */
9565
+ .tool-detail__section-head {
9566
+ display: flex;
9567
+ align-items: center;
9568
+ gap: 8px;
9569
+ appearance: none;
9570
+ background: none;
9571
+ border: none;
9572
+ width: 100%;
9573
+ font-family: inherit;
9574
+ text-align: left;
9575
+ cursor: pointer;
9576
+ border-radius: 4px;
9577
+ }
9578
+
9579
+ .tool-detail__section-head:focus-visible {
9580
+ outline: 2px solid var(--kr-chatbot-primary);
9581
+ outline-offset: 2px;
9582
+ }
9583
+
9584
+ /* Disclosure caret — the same "›" used on tool lines; rotates down when open. */
9585
+ .tool-detail__caret {
9586
+ flex-shrink: 0;
9587
+ font-size: 16px;
9588
+ color: rgba(0, 0, 0, 0.55);
9589
+ transition: transform 0.15s ease;
9590
+ }
9591
+
9592
+ .tool-detail__caret--open {
9593
+ transform: rotate(90deg);
9594
+ }
9595
+
9596
+ .tool-detail__section-title {
9597
+ font-size: 12px;
9598
+ font-weight: 700;
9599
+ text-transform: uppercase;
9600
+ letter-spacing: 0.06em;
9601
+ color: #000000;
9602
+ }
9603
+
9604
+ /* Copy icon-button — same treatment as the per-message copy button. */
9605
+ .tool-detail__copy {
9606
+ margin-left: auto;
9607
+ display: inline-flex;
9608
+ align-items: center;
9609
+ justify-content: center;
9610
+ width: 26px;
9611
+ height: 26px;
9612
+ padding: 0;
9613
+ background: none;
9614
+ border: none;
9615
+ border-radius: 6px;
9616
+ color: #000000;
9617
+ cursor: pointer;
9618
+ transition: background-color 0.15s, color 0.15s;
9619
+ }
9620
+
9621
+ .tool-detail__copy:hover {
9622
+ background: rgba(0, 0, 0, 0.06);
9623
+ }
9624
+
9625
+ .tool-detail__copy svg {
9626
+ width: 14px;
9627
+ height: 14px;
9628
+ }
9629
+
9630
+ /* The code block — pretty-printed JSON, shown in full (no inner scroll). The
9631
+ drawer body is the only scroll container, so long inputs/outputs extend the
9632
+ page and you scroll the panel, not a nested box. */
9633
+ .tool-detail__code {
9634
+ margin: 0;
9635
+ padding: 0 18px;
9636
+ font-family: 'SF Mono', 'Fira Code', Monaco, monospace;
9637
+ font-size: 12px;
9638
+ line-height: 1.5;
9639
+ color: #000000;
9640
+ white-space: pre-wrap;
9641
+ word-break: break-word;
9642
+ }
9643
+
9221
9644
  /* Messages */
9222
9645
  /* Full-width scroll container — keeps the scrollbar at the panel edge. */
9223
9646
  .messages {
@@ -9314,6 +9737,9 @@
9314
9737
  /* Tool-activity line — "the assistant ran X" row in the transcript. Same text
9315
9738
  style as any other message (black, 14px); a leading › chevron marks it as a
9316
9739
  step (in the same text style, not an icon). */
9740
+ /* The tool-activity line and its result preview are <button>s (they open the
9741
+ tool-detail drawer on click) — strip the native button chrome so they read
9742
+ as plain transcript text, then re-add the layout. */
9317
9743
  .tool-activity {
9318
9744
  align-self: flex-start;
9319
9745
  display: flex;
@@ -9323,6 +9749,22 @@
9323
9749
  color: #000000;
9324
9750
  /* Keep the chevron + name + input on a single line; input truncates if long. */
9325
9751
  white-space: nowrap;
9752
+ /* Button reset */
9753
+ appearance: none;
9754
+ background: none;
9755
+ border: none;
9756
+ margin: 0;
9757
+ padding: 2px 4px;
9758
+ font-family: inherit;
9759
+ text-align: left;
9760
+ cursor: pointer;
9761
+ border-radius: 4px;
9762
+ max-width: 100%;
9763
+ }
9764
+
9765
+ .tool-activity:focus-visible {
9766
+ outline: 2px solid var(--kr-chatbot-primary);
9767
+ outline-offset: 1px;
9326
9768
  }
9327
9769
 
9328
9770
  .tool-activity--error {
@@ -9358,6 +9800,16 @@
9358
9800
  gap: 4px;
9359
9801
  }
9360
9802
 
9803
+ /* One hover for the whole tool block: hovering anywhere over the header or its
9804
+ result underlines the tool name, input, and result together as a single
9805
+ link, rather than lighting up header and result separately. */
9806
+ .tool-activity-block:hover .tool-activity__label,
9807
+ .tool-activity-block:hover .tool-activity__input,
9808
+ .tool-activity-block:hover .tool-activity__result-body {
9809
+ text-decoration: underline;
9810
+ text-decoration-color: rgba(0, 0, 0, 0.25);
9811
+ }
9812
+
9361
9813
  /* The result block indented under the activity line with a ⎿ tree connector,
9362
9814
  like Claude CLI's tool output preview. Uses the normal message text style
9363
9815
  (14px, black) — only the ⎿ connector is muted. */
@@ -9373,6 +9825,20 @@
9373
9825
  margin-left: 6px;
9374
9826
  font-size: 14px;
9375
9827
  color: #000000;
9828
+ /* Button reset — this preview row is also a click target for the detail view. */
9829
+ appearance: none;
9830
+ background: none;
9831
+ border: none;
9832
+ padding: 0;
9833
+ font-family: inherit;
9834
+ text-align: left;
9835
+ cursor: pointer;
9836
+ }
9837
+
9838
+ .tool-activity__result:focus-visible {
9839
+ outline: 2px solid var(--kr-chatbot-primary);
9840
+ outline-offset: 1px;
9841
+ border-radius: 4px;
9376
9842
  }
9377
9843
 
9378
9844
  .tool-activity__result--error {
@@ -10838,6 +11304,18 @@
10838
11304
  __decorate([
10839
11305
  r()
10840
11306
  ], exports.Chatbot.prototype, "_drawerView", void 0);
11307
+ __decorate([
11308
+ r()
11309
+ ], exports.Chatbot.prototype, "_toolDetail", void 0);
11310
+ __decorate([
11311
+ r()
11312
+ ], exports.Chatbot.prototype, "_toolDetailCopied", void 0);
11313
+ __decorate([
11314
+ r()
11315
+ ], exports.Chatbot.prototype, "_toolDetailInputOpen", void 0);
11316
+ __decorate([
11317
+ r()
11318
+ ], exports.Chatbot.prototype, "_toolDetailOutputOpen", void 0);
10841
11319
  __decorate([
10842
11320
  r()
10843
11321
  ], exports.Chatbot.prototype, "_drawerWidth", void 0);
@@ -10919,6 +11397,9 @@
10919
11397
  __decorate([
10920
11398
  e$3('.skill-menu')
10921
11399
  ], exports.Chatbot.prototype, "_skillMenuEl", void 0);
11400
+ __decorate([
11401
+ e$3('.drawer__body')
11402
+ ], exports.Chatbot.prototype, "_drawerBodyEl", void 0);
10922
11403
  exports.Chatbot = __decorate([
10923
11404
  t$2('kr-chatbot')
10924
11405
  ], exports.Chatbot);