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