@raquezha/notrace 0.0.7 → 0.1.0

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.
@@ -259,7 +259,7 @@ function shell(title: string, body: string, script = ""): string {
259
259
  align-items: center;
260
260
  margin-top: 16px;
261
261
  }
262
- .pill, .workflow-pill, .sort-btn {
262
+ .pill, .workflow-pill, .sort-btn, .export-btn {
263
263
  display: inline-flex;
264
264
  align-items: center;
265
265
  gap: 6px;
@@ -272,7 +272,7 @@ function shell(title: string, body: string, script = ""): string {
272
272
  font-size: 0.86rem;
273
273
  font-weight: 600;
274
274
  }
275
- .metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 16px; margin: 24px 0; }
275
+ .metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(135px, 1fr)); gap: 16px; margin: 24px 0; }
276
276
  .metric-card {
277
277
  background: var(--panel-strong);
278
278
  border: 1px solid var(--border);
@@ -391,7 +391,42 @@ function shell(title: string, body: string, script = ""): string {
391
391
  .msg-role { font-size: 0.78rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; }
392
392
  .msg.user .msg-role { color: #8ec5ff; }
393
393
  .msg.assistant .msg-role { color: var(--accent); }
394
- .msg-content { padding: 14px; white-space: pre-wrap; word-break: break-word; }
394
+ .msg-content { padding: 14px; }
395
+ .chat-text {
396
+ white-space: pre-wrap;
397
+ word-break: break-word;
398
+ font-size: 0.95rem;
399
+ line-height: 1.6;
400
+ margin-bottom: 12px;
401
+ }
402
+ .chat-text:last-child { margin-bottom: 0; }
403
+ .chat-tool-use {
404
+ background: rgba(0,0,0,0.3);
405
+ border: 1px solid var(--border);
406
+ border-radius: 8px;
407
+ overflow: hidden;
408
+ margin-bottom: 12px;
409
+ }
410
+ .chat-tool-use:last-child { margin-bottom: 0; }
411
+ .chat-tool-header {
412
+ background: rgba(255,255,255,0.04);
413
+ padding: 8px 12px;
414
+ font-size: 0.8rem;
415
+ font-family: "SFMono-Regular", ui-monospace, Menlo, Monaco, Consolas, monospace;
416
+ color: #8ec5ff;
417
+ border-bottom: 1px solid var(--border);
418
+ display: flex;
419
+ align-items: center;
420
+ gap: 8px;
421
+ }
422
+ .chat-tool-body {
423
+ padding: 12px;
424
+ margin: 0;
425
+ background: transparent;
426
+ border: none;
427
+ max-height: 400px;
428
+ overflow-y: auto;
429
+ }
395
430
  .footer-note {
396
431
  margin-top: 22px;
397
432
  color: var(--muted);
@@ -436,7 +471,15 @@ function shell(title: string, body: string, script = ""): string {
436
471
  color: var(--text);
437
472
  border-bottom-color: rgba(236,227,218,0.45);
438
473
  }
439
- @media (max-width: 760px) {
474
+ .export-btn {
475
+ cursor: pointer;
476
+ transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
477
+ }
478
+ .export-btn:hover, .export-btn.copied {
479
+ color: var(--text);
480
+ border-color: rgba(216,132,98,0.45);
481
+ background: var(--accent-soft);
482
+ }
440
483
  .container { padding: 20px 14px 48px; }
441
484
  .hero { padding: 20px; }
442
485
  .hero-top { grid-template-columns: 1fr; }
@@ -451,8 +494,26 @@ function shell(title: string, body: string, script = ""): string {
451
494
  </html>`;
452
495
  }
453
496
 
454
- function copyButton(value: string, label: string): string {
455
- return `<button class="copy-btn" type="button" data-copy-value="${escapeHtml(value)}" aria-label="Copy ${escapeHtml(label)}" title="Copy ${escapeHtml(label)}"><svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></button>`;
497
+ function copyButton(value: string, label: string, className = "copy-btn"): string {
498
+ return `<button class="${escapeHtml(className)}" type="button" data-copy-value="${escapeHtml(value)}" aria-label="Copy ${escapeHtml(label)}" title="Copy ${escapeHtml(label)}"><svg width="14" height="14" viewBox="0 0 24 24" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg></button>`;
499
+ }
500
+
501
+ function exportButton(data: any): string {
502
+ const payload = JSON.stringify({
503
+ kind: "notrace-export",
504
+ traceId: data.traceId,
505
+ repository: data.repository?.name,
506
+ branch: data.repository?.branch,
507
+ task: data.task,
508
+ metrics: data.activity?.totals,
509
+ summary: data.telemetry?.extensions?.noheadroom?.summary,
510
+ events: (data.events || []).filter((e: any) => e.type === "llm_completion").map((e: any) => ({
511
+ model: e.model,
512
+ input: e.inputPayload?.messages,
513
+ output: e.outputContent
514
+ }))
515
+ });
516
+ return `<button class="export-btn" type="button" data-copy-value="${escapeHtml(payload)}" title="Copy session data for LLM/Agent context"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg><span>Export</span></button>`;
456
517
  }
457
518
 
458
519
  function copyScript(): string {
@@ -493,9 +554,61 @@ function renderJsonBlock(title: string, value: unknown): string {
493
554
  return `<section class="block"><h4>${escapeHtml(title)}</h4><pre>${escapeHtml(typeof value === "string" ? value : JSON.stringify(value, null, 2))}</pre></section>`;
494
555
  }
495
556
 
557
+ function renderToolUseHtml(name: string, input: any): string {
558
+ const parsedInput = typeof input === "string" ? (() => { try { return JSON.parse(input); } catch { return input; } })() : input;
559
+ return `<div class="chat-tool-use"><div class="chat-tool-header"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path></svg> ${escapeHtml(name)}</div><pre class="chat-tool-body">${escapeHtml(typeof parsedInput === 'string' ? parsedInput : JSON.stringify(parsedInput, null, 2))}</pre></div>`;
560
+ }
561
+
562
+ function renderToolResultHtml(id: string, content: any): string {
563
+ return `<div class="chat-tool-use"><div class="chat-tool-header" style="color: var(--muted);"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 10 4 15 9 20"></polyline><path d="M20 4v7a4 4 0 0 1-4 4H4"></path></svg> Tool Result: ${escapeHtml(id)}</div><pre class="chat-tool-body">${escapeHtml(typeof content === 'string' ? content : JSON.stringify(content, null, 2))}</pre></div>`;
564
+ }
565
+
566
+ function renderUniversalMessageContent(m: any): string {
567
+ if (!m) return "";
568
+ let html = "";
569
+
570
+ // 1. Handle string content or Anthropic/Pi blocks
571
+ if (typeof m.content === "string" && m.content.trim()) {
572
+ html += `<div class="chat-text">${escapeHtml(m.content)}</div>`;
573
+ } else if (Array.isArray(m.content)) {
574
+ html += m.content.map((block: any) => {
575
+ if (!block) return "";
576
+ if (block.type === "text") return `<div class="chat-text">${escapeHtml(block.text)}</div>`;
577
+ if (block.type === "tool_use") return renderToolUseHtml(block.name, block.input);
578
+ if (block.type === "tool_result") return renderToolResultHtml(block.tool_use_id || "unknown", block.content);
579
+ return `<pre class="chat-tool-body">${escapeHtml(JSON.stringify(block, null, 2))}</pre>`;
580
+ }).join("");
581
+ } else if (m.content && typeof m.content === "object") {
582
+ html += `<pre class="chat-tool-body">${escapeHtml(JSON.stringify(m.content, null, 2))}</pre>`;
583
+ }
584
+
585
+ // 2. Handle OpenAI/Codex tool_calls (attached to message, not in content)
586
+ if (Array.isArray(m.tool_calls)) {
587
+ html += m.tool_calls.map((tc: any) => {
588
+ if (tc.type === "function" && tc.function) {
589
+ return renderToolUseHtml(tc.function.name, tc.function.arguments);
590
+ }
591
+ return "";
592
+ }).join("");
593
+ }
594
+
595
+ // 3. Handle OpenAI legacy function_call
596
+ if (m.function_call) {
597
+ html += renderToolUseHtml(m.function_call.name, m.function_call.arguments);
598
+ }
599
+
600
+ // 4. Handle OpenAI tool result (message role is "tool")
601
+ if (m.role === "tool" && !html.includes("chat-tool-result")) {
602
+ // If it was just a string, it rendered above. Wrap it in a tool result block instead.
603
+ html = renderToolResultHtml(m.tool_call_id || m.name || "unknown", m.content);
604
+ }
605
+
606
+ return html || `<div class="empty">Empty message</div>`;
607
+ }
608
+
496
609
  function renderMessages(messages: any[] | undefined): string {
497
610
  if (!messages?.length) return "";
498
- return `<section class="block"><h4>Input Messages</h4>${messages.map(m => `<div class="msg ${escapeHtml(m?.role || "unknown")}"><div class="msg-head"><span class="msg-role">${escapeHtml(m?.role || "unknown")}</span></div><div class="msg-content">${escapeHtml(m?.content ?? "")}</div></div>`).join("")}</section>`;
611
+ return `<section class="block"><h4>Input Messages</h4>${messages.map(m => `<div class="msg ${escapeHtml(m?.role || "unknown")}"><div class="msg-head"><span class="msg-role">${escapeHtml(m?.role || "unknown")}</span></div><div class="msg-content">${renderUniversalMessageContent(m)}</div></div>`).join("")}</section>`;
499
612
  }
500
613
 
501
614
  function eventBadgeClass(ev: any): string {
@@ -518,12 +631,12 @@ function renderEventCard(ev: any): string {
518
631
  if (ev.errorMessage) {
519
632
  sections.push(renderJsonBlock("Error Message", ev.errorMessage));
520
633
  }
521
- sections.push(renderJsonBlock("Output", ev.outputContent));
634
+ sections.push(`<section class="block"><h4>Output</h4><div class="msg-content">${renderUniversalMessageContent({ content: ev.outputContent })}</div></section>`);
522
635
  if (ev.usage) sections.push(renderJsonBlock("Usage", ev.usage));
523
636
  } else if (ev.type === "tool_start") {
524
- sections.push(renderJsonBlock("Arguments", ev.args));
637
+ sections.push(`<section class="block"><h4>Arguments</h4><div class="msg-content"><div class="chat-tool-use"><div class="chat-tool-header"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path></svg> Execution Input</div><pre class="chat-tool-body">${escapeHtml(typeof ev.args === 'string' ? ev.args : JSON.stringify(ev.args, null, 2))}</pre></div></div></section>`);
525
638
  } else if (ev.type === "tool_end") {
526
- sections.push(renderJsonBlock(ev.isError ? "Error Result" : "Result", ev.result));
639
+ sections.push(`<section class="block"><h4>${ev.isError ? "Error Result" : "Result"}</h4><div class="msg-content"><div class="chat-tool-use" style="${ev.isError ? 'border-color: rgba(239,127,127,0.3);' : ''}"><div class="chat-tool-header" style="${ev.isError ? 'color: var(--err);' : 'color: var(--muted);'}"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 10 4 15 9 20"></polyline><path d="M20 4v7a4 4 0 0 1-4 4H4"></path></svg> Execution Output</div><pre class="chat-tool-body">${escapeHtml(typeof ev.result === 'string' ? ev.result : JSON.stringify(ev.result, null, 2))}</pre></div></div></section>`);
527
640
  } else {
528
641
  sections.push(renderJsonBlock("Event", ev));
529
642
  }
@@ -600,17 +713,19 @@ export function generateDashboardHtml(sessions: any[], options: any = {}): strin
600
713
  const reversed = sessions.slice().reverse();
601
714
  const totalCost = sessions.reduce((sum, s) => sum + Number(s.activity?.totals?.totalCostUsd || 0), 0);
602
715
  const totalTokens = sessions.reduce((sum, s) => sum + Number(s.activity?.totals?.totalTokens || 0), 0);
603
- const repositoryName = resolveRepoName(options);
604
716
  const homeHref = options?.indexHref || "index.html";
605
717
  const body = `<div class="container">
606
718
  <section class="hero">
607
- <div class="hero-top">
608
- <div>
609
- <div class="brand"><a class="brand-link" href="${escapeHtml(homeHref)}">${wordmarkSvg()}</a><p class="subtitle">Retrospective index. Repo-local session evidence.</p></div>
610
- </div>
611
- <div class="meta">
612
- <span class="pill">${escapeHtml(repositoryName)}</span>
613
- <span class="pill">${sessions.length} sessions</span>
719
+ <div class="hero-split">
720
+ <a class="brand-link" href="${escapeHtml(homeHref)}">${wordmarkSvg()}</a>
721
+ <div class="hero-right">
722
+ <div class="hero-session">
723
+ <strong style="color: var(--text); font-weight: 500;">Global Index</strong>
724
+ <span style="color: var(--muted);">Machine-wide session evidence.</span>
725
+ </div>
726
+ <div class="hero-meta">
727
+ <span class="hero-pill">${sessions.length} sessions</span>
728
+ </div>
614
729
  </div>
615
730
  </div>
616
731
  <div class="metrics">
@@ -621,14 +736,14 @@ export function generateDashboardHtml(sessions: any[], options: any = {}): strin
621
736
  </section>
622
737
  <section class="panel">
623
738
  <h2 class="section-title">Session Reports</h2>
624
- ${reversed.length ? `<table data-dashboard-table><thead><tr><th class="col-index sortable-head"><button class="sort-btn" data-sort-key="index"><span class="sort-label">#</span><span class="sort-state">↓</span></button></th><th>Session</th><th class="sortable-head"><button class="sort-btn" data-sort-key="workflow"><span class="sort-label">Workflow</span><span class="sort-state"></span></button></th><th class="sortable-head"><button class="sort-btn" data-sort-key="started"><span class="sort-label">Started</span><span class="sort-state"></span></button></th><th>Task</th><th class="sortable-head num-cell"><button class="sort-btn" data-sort-key="tokens"><span class="sort-label">Tokens</span><span class="sort-state"></span></button></th><th class="sortable-head num-cell"><button class="sort-btn" data-sort-key="cost"><span class="sort-label">Cost</span><span class="sort-state"></span></button></th></tr></thead><tbody>
739
+ ${reversed.length ? `<table data-dashboard-table><thead><tr><th class="col-index sortable-head"><button class="sort-btn" data-sort-key="index"><span class="sort-label">#</span><span class="sort-state">↓</span></button></th><th>Session</th><th>Project</th><th class="sortable-head"><button class="sort-btn" data-sort-key="workflow"><span class="sort-label">Workflow</span><span class="sort-state"></span></button></th><th class="sortable-head"><button class="sort-btn" data-sort-key="started"><span class="sort-label">Started</span><span class="sort-state"></span></button></th><th>Task</th><th class="sortable-head num-cell"><button class="sort-btn" data-sort-key="tokens"><span class="sort-label">Tokens</span><span class="sort-state"></span></button></th><th class="sortable-head num-cell"><button class="sort-btn" data-sort-key="cost"><span class="sort-label">Cost</span><span class="sort-state"></span></button></th></tr></thead><tbody>
625
740
  ${reversed.map((s, index) => {
626
- const link = s.artifacts.html.startsWith(".notrace/") ? s.artifacts.html.substring(9) : s.artifacts.html;
741
+ const link = s.artifacts?.html ? (s.artifacts.html.startsWith(".notrace/") ? s.artifacts.html.substring(9) : s.artifacts.html) : "#";
627
742
  const workflow = s.task?.workflow || "generic";
628
743
  const workflowLabel = workflowDisplayName(workflow);
629
744
  const tokens = Number(s.activity?.totals?.totalTokens || 0);
630
745
  const cost = Number(s.activity?.totals?.totalCostUsd || 0);
631
- return `<tr data-index="${reversed.length - index}" data-workflow="${escapeHtml(workflowLabel)}" data-started="${parseDate(s.startedAt)?.getTime() || 0}" data-tokens="${tokens}" data-cost="${cost}"><td class="index-cell">${reversed.length - index}</td><td><a class="session-link" href="${escapeHtml(link)}"><strong>${escapeHtml(String(s.sessionId).slice(0, 8))}</strong><span class="session-sub">${escapeHtml(String(s.sessionId))}</span></a></td><td><span class="workflow-pill ${workflowClassName(workflow)}">${escapeHtml(workflowLabel)}</span></td><td>${formatDateCell(s.startedAt)}</td><td>${escapeHtml(taskDisplay(s))}</td><td class="num-cell">${tokens.toLocaleString()}</td><td class="num-cell">${formatUsd(cost)}</td></tr>`;
746
+ return `<tr data-index="${reversed.length - index}" data-workflow="${escapeHtml(workflowLabel)}" data-started="${parseDate(s.startedAt)?.getTime() || 0}" data-tokens="${tokens}" data-cost="${cost}"><td class="index-cell">${reversed.length - index}</td><td><a class="session-link" href="${escapeHtml(link)}"><strong>${escapeHtml(String(s.sessionId).slice(0, 8))}</strong><span class="session-sub">${escapeHtml(String(s.sessionId))}</span></a></td><td><span class="hero-pill">${escapeHtml(s.repositoryName || "Unknown")}</span></td><td><span class="workflow-pill ${workflowClassName(workflow)}">${escapeHtml(workflowLabel)}</span></td><td>${formatDateCell(s.startedAt)}</td><td>${escapeHtml(taskDisplay(s))}</td><td class="num-cell">${tokens.toLocaleString()}</td><td class="num-cell">${formatUsd(cost)}</td></tr>`;
632
747
  }).join("")}
633
748
  </tbody></table>` : `<div class="empty">No sessions yet. Run Pi with notrace enabled. New reports appear here.</div>`}
634
749
  </section>
@@ -652,6 +767,7 @@ export function generateHtmlReport(data: any): string {
652
767
  <span class="pill">${escapeHtml(resolveRepoName(data))}</span>
653
768
  <span class="pill">Started ${formatDateLong(data.session?.startedAt)}</span>
654
769
  <span class="pill">Mode: ${escapeHtml(data.captureMode || "full")}</span>
770
+ ${exportButton(data)}
655
771
  </div>
656
772
  </div>
657
773
  <div class="metrics">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raquezha/notrace",
3
- "version": "0.0.7",
3
+ "version": "0.1.0",
4
4
  "description": "Zero-dependency, local-first interactive HTML Trace Viewer for the Pi Coding Agent",
5
5
  "main": "dist/notrace/index.js",
6
6
  "types": "dist/notrace/index.d.ts",
@@ -114,7 +114,7 @@
114
114
  align-items: center;
115
115
  margin-top: 16px;
116
116
  }
117
- .pill, .workflow-pill, .sort-btn {
117
+ .pill, .workflow-pill, .sort-btn, .export-btn {
118
118
  display: inline-flex;
119
119
  align-items: center;
120
120
  gap: 6px;
@@ -127,7 +127,7 @@
127
127
  font-size: 0.86rem;
128
128
  font-weight: 600;
129
129
  }
130
- .metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 16px; margin: 24px 0; }
130
+ .metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(135px, 1fr)); gap: 16px; margin: 24px 0; }
131
131
  .metric-card {
132
132
  background: var(--panel-strong);
133
133
  border: 1px solid var(--border);
@@ -246,7 +246,40 @@
246
246
  .msg-role { font-size: 0.78rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; }
247
247
  .msg.user .msg-role { color: #8ec5ff; }
248
248
  .msg.assistant .msg-role { color: var(--accent); }
249
- .msg-content { padding: 14px; white-space: pre-wrap; word-break: break-word; }
249
+ .msg-content { padding: 14px; }
250
+ .chat-text {
251
+ white-space: pre-wrap;
252
+ word-break: break-word;
253
+ font-size: 0.95rem;
254
+ line-height: 1.6;
255
+ margin-bottom: 12px;
256
+ }
257
+ .chat-text:last-child { margin-bottom: 0; }
258
+ .chat-tool-use {
259
+ background: rgba(0,0,0,0.3);
260
+ border: 1px solid var(--border);
261
+ border-radius: 8px;
262
+ overflow: hidden;
263
+ margin-bottom: 12px;
264
+ }
265
+ .chat-tool-use:last-child { margin-bottom: 0; }
266
+ .chat-tool-header {
267
+ background: rgba(255,255,255,0.04);
268
+ padding: 8px 12px;
269
+ font-size: 0.8rem;
270
+ font-family: "SFMono-Regular", ui-monospace, Menlo, Monaco, Consolas, monospace;
271
+ color: #8ec5ff;
272
+ border-bottom: 1px solid var(--border);
273
+ display: flex;
274
+ align-items: center;
275
+ gap: 8px;
276
+ }
277
+ .chat-tool-body {
278
+ padding: 12px;
279
+ margin: 0;
280
+ background: transparent;
281
+ border: none;
282
+ }
250
283
  .footer-note {
251
284
  margin-top: 22px;
252
285
  color: var(--muted);
@@ -291,7 +324,15 @@
291
324
  color: var(--text);
292
325
  border-bottom-color: rgba(236,227,218,0.45);
293
326
  }
294
- @media (max-width: 760px) {
327
+ .export-btn {
328
+ cursor: pointer;
329
+ transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
330
+ }
331
+ .export-btn:hover, .export-btn.copied {
332
+ color: var(--text);
333
+ border-color: rgba(216,132,98,0.45);
334
+ background: var(--accent-soft);
335
+ }
295
336
  .container { padding: 20px 14px 48px; }
296
337
  .hero { padding: 20px; }
297
338
  .hero-top { grid-template-columns: 1fr; }
@@ -304,9 +345,8 @@
304
345
  </head>
305
346
  <body><div class="container">
306
347
  <section class="hero">
307
- <div class="hero-top">
308
- <div>
309
- <div class="brand"><a class="brand-link" href="index.html"><svg class="wordmark" viewBox="0 0 420 138" aria-label="notrace" role="img" xmlns="http://www.w3.org/2000/svg">
348
+ <div class="hero-split">
349
+ <a class="brand-link" href="index.html"><svg class="wordmark" viewBox="0 0 420 138" aria-label="notrace" role="img" xmlns="http://www.w3.org/2000/svg">
310
350
  <defs>
311
351
  <linearGradient id="fadeGrad" x1="0%" y1="0%" x2="100%" y2="0%">
312
352
  <stop offset="0%" stop-color="#E2754A"/>
@@ -323,11 +363,15 @@
323
363
  </g>
324
364
  <text x="0" y="114" fill="#ECE3DA" style="fill:#ECE3DA" font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif" font-size="96" font-weight="900" letter-spacing="-7">no</text>
325
365
  <text x="82" y="114" fill="#d88462" font-family="Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif" font-size="96" font-weight="900" letter-spacing="-7">trace</text>
326
- </svg></a><p class="subtitle">Retrospective index. Repo-local session evidence.</p></div>
327
- </div>
328
- <div class="meta">
329
- <span class="pill">nothing</span>
330
- <span class="pill">3 sessions</span>
366
+ </svg></a>
367
+ <div class="hero-right">
368
+ <div class="hero-session">
369
+ <strong style="color: var(--text); font-weight: 500;">Global Index</strong>
370
+ <span style="color: var(--muted);">Machine-wide session evidence.</span>
371
+ </div>
372
+ <div class="hero-meta">
373
+ <span class="hero-pill">3 sessions</span>
374
+ </div>
331
375
  </div>
332
376
  </div>
333
377
  <div class="metrics">
@@ -338,8 +382,8 @@
338
382
  </section>
339
383
  <section class="panel">
340
384
  <h2 class="section-title">Session Reports</h2>
341
- <table data-dashboard-table><thead><tr><th class="col-index sortable-head"><button class="sort-btn" data-sort-key="index"><span class="sort-label">#</span><span class="sort-state">↓</span></button></th><th>Session</th><th class="sortable-head"><button class="sort-btn" data-sort-key="workflow"><span class="sort-label">Workflow</span><span class="sort-state"></span></button></th><th class="sortable-head"><button class="sort-btn" data-sort-key="started"><span class="sort-label">Started</span><span class="sort-state"></span></button></th><th>Task</th><th class="sortable-head num-cell"><button class="sort-btn" data-sort-key="tokens"><span class="sort-label">Tokens</span><span class="sort-state"></span></button></th><th class="sortable-head num-cell"><button class="sort-btn" data-sort-key="cost"><span class="sort-label">Cost</span><span class="sort-state"></span></button></th></tr></thead><tbody>
342
- <tr data-index="3" data-workflow="Generic" data-started="1781691240000" data-tokens="5910" data-cost="0.0334"><td class="index-cell">3</td><td><a class="session-link" href="sessions/019ed2ee-1002-76ee-b353-000000000003/notrace.html"><strong>019ed2ee</strong><span class="session-sub">019ed2ee-1002-76ee-b353-000000000003</span></a></td><td><span class="workflow-pill workflow-generic">Generic</span></td><td><div class="date-cell"><strong>2026-06-17</strong><span>18:14</span></div></td><td>General session</td><td class="num-cell">5,910</td><td class="num-cell">$0.03340</td></tr><tr data-index="2" data-workflow="Research" data-started="1781689020000" data-tokens="5754" data-cost="0.0267"><td class="index-cell">2</td><td><a class="session-link" href="sessions/019ed2ee-1001-76ee-b353-000000000002/notrace.html"><strong>019ed2ee</strong><span class="session-sub">019ed2ee-1001-76ee-b353-000000000002</span></a></td><td><span class="workflow-pill workflow-research">Research</span></td><td><div class="date-cell"><strong>2026-06-17</strong><span>17:37</span></div></td><td>Branch topic-02</td><td class="num-cell">5,754</td><td class="num-cell">$0.02670</td></tr><tr data-index="1" data-workflow="RPIV" data-started="1781686800000" data-tokens="3510" data-cost="0.0204"><td class="index-cell">1</td><td><a class="session-link" href="sessions/019ed2ee-1000-76ee-b353-000000000001/notrace.html"><strong>019ed2ee</strong><span class="session-sub">019ed2ee-1000-76ee-b353-000000000001</span></a></td><td><span class="workflow-pill workflow-rpiv">RPIV</span></td><td><div class="date-cell"><strong>2026-06-17</strong><span>17:00</span></div></td><td>NR-101</td><td class="num-cell">3,510</td><td class="num-cell">$0.02040</td></tr>
385
+ <table data-dashboard-table><thead><tr><th class="col-index sortable-head"><button class="sort-btn" data-sort-key="index"><span class="sort-label">#</span><span class="sort-state">↓</span></button></th><th>Session</th><th>Project</th><th class="sortable-head"><button class="sort-btn" data-sort-key="workflow"><span class="sort-label">Workflow</span><span class="sort-state"></span></button></th><th class="sortable-head"><button class="sort-btn" data-sort-key="started"><span class="sort-label">Started</span><span class="sort-state"></span></button></th><th>Task</th><th class="sortable-head num-cell"><button class="sort-btn" data-sort-key="tokens"><span class="sort-label">Tokens</span><span class="sort-state"></span></button></th><th class="sortable-head num-cell"><button class="sort-btn" data-sort-key="cost"><span class="sort-label">Cost</span><span class="sort-state"></span></button></th></tr></thead><tbody>
386
+ <tr data-index="3" data-workflow="Generic" data-started="1781691240000" data-tokens="5910" data-cost="0.0334"><td class="index-cell">3</td><td><a class="session-link" href="sessions/019ed2ee-1002-76ee-b353-000000000003/notrace.html"><strong>019ed2ee</strong><span class="session-sub">019ed2ee-1002-76ee-b353-000000000003</span></a></td><td><span class="hero-pill">Unknown</span></td><td><span class="workflow-pill workflow-generic">Generic</span></td><td><div class="date-cell"><strong>2026-06-17</strong><span>18:14</span></div></td><td>General session</td><td class="num-cell">5,910</td><td class="num-cell">$0.03340</td></tr><tr data-index="2" data-workflow="Research" data-started="1781689020000" data-tokens="5754" data-cost="0.0267"><td class="index-cell">2</td><td><a class="session-link" href="sessions/019ed2ee-1001-76ee-b353-000000000002/notrace.html"><strong>019ed2ee</strong><span class="session-sub">019ed2ee-1001-76ee-b353-000000000002</span></a></td><td><span class="hero-pill">Unknown</span></td><td><span class="workflow-pill workflow-research">Research</span></td><td><div class="date-cell"><strong>2026-06-17</strong><span>17:37</span></div></td><td>Branch topic-02</td><td class="num-cell">5,754</td><td class="num-cell">$0.02670</td></tr><tr data-index="1" data-workflow="RPIV" data-started="1781686800000" data-tokens="3510" data-cost="0.0204"><td class="index-cell">1</td><td><a class="session-link" href="sessions/019ed2ee-1000-76ee-b353-000000000001/notrace.html"><strong>019ed2ee</strong><span class="session-sub">019ed2ee-1000-76ee-b353-000000000001</span></a></td><td><span class="hero-pill">Unknown</span></td><td><span class="workflow-pill workflow-rpiv">RPIV</span></td><td><div class="date-cell"><strong>2026-06-17</strong><span>17:00</span></div></td><td>NR-101</td><td class="num-cell">3,510</td><td class="num-cell">$0.02040</td></tr>
343
387
  </tbody></table>
344
388
  </section>
345
389
  <footer class="footer-note minimal">notrace • raquezha 2026</footer>
@@ -114,7 +114,7 @@
114
114
  align-items: center;
115
115
  margin-top: 16px;
116
116
  }
117
- .pill, .workflow-pill, .sort-btn {
117
+ .pill, .workflow-pill, .sort-btn, .export-btn {
118
118
  display: inline-flex;
119
119
  align-items: center;
120
120
  gap: 6px;
@@ -127,7 +127,7 @@
127
127
  font-size: 0.86rem;
128
128
  font-weight: 600;
129
129
  }
130
- .metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr)); gap: 16px; margin: 24px 0; }
130
+ .metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(135px, 1fr)); gap: 16px; margin: 24px 0; }
131
131
  .metric-card {
132
132
  background: var(--panel-strong);
133
133
  border: 1px solid var(--border);
@@ -246,7 +246,40 @@
246
246
  .msg-role { font-size: 0.78rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; }
247
247
  .msg.user .msg-role { color: #8ec5ff; }
248
248
  .msg.assistant .msg-role { color: var(--accent); }
249
- .msg-content { padding: 14px; white-space: pre-wrap; word-break: break-word; }
249
+ .msg-content { padding: 14px; }
250
+ .chat-text {
251
+ white-space: pre-wrap;
252
+ word-break: break-word;
253
+ font-size: 0.95rem;
254
+ line-height: 1.6;
255
+ margin-bottom: 12px;
256
+ }
257
+ .chat-text:last-child { margin-bottom: 0; }
258
+ .chat-tool-use {
259
+ background: rgba(0,0,0,0.3);
260
+ border: 1px solid var(--border);
261
+ border-radius: 8px;
262
+ overflow: hidden;
263
+ margin-bottom: 12px;
264
+ }
265
+ .chat-tool-use:last-child { margin-bottom: 0; }
266
+ .chat-tool-header {
267
+ background: rgba(255,255,255,0.04);
268
+ padding: 8px 12px;
269
+ font-size: 0.8rem;
270
+ font-family: "SFMono-Regular", ui-monospace, Menlo, Monaco, Consolas, monospace;
271
+ color: #8ec5ff;
272
+ border-bottom: 1px solid var(--border);
273
+ display: flex;
274
+ align-items: center;
275
+ gap: 8px;
276
+ }
277
+ .chat-tool-body {
278
+ padding: 12px;
279
+ margin: 0;
280
+ background: transparent;
281
+ border: none;
282
+ }
250
283
  .footer-note {
251
284
  margin-top: 22px;
252
285
  color: var(--muted);
@@ -291,7 +324,15 @@
291
324
  color: var(--text);
292
325
  border-bottom-color: rgba(236,227,218,0.45);
293
326
  }
294
- @media (max-width: 760px) {
327
+ .export-btn {
328
+ cursor: pointer;
329
+ transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
330
+ }
331
+ .export-btn:hover, .export-btn.copied {
332
+ color: var(--text);
333
+ border-color: rgba(216,132,98,0.45);
334
+ background: var(--accent-soft);
335
+ }
295
336
  .container { padding: 20px 14px 48px; }
296
337
  .hero { padding: 20px; }
297
338
  .hero-top { grid-template-columns: 1fr; }
@@ -329,6 +370,7 @@
329
370
  <span class="pill">nothing</span>
330
371
  <span class="pill">Started 2026-06-17 22:05</span>
331
372
  <span class="pill">Mode: full</span>
373
+ <button class="export-btn" type="button" data-copy-value="{&quot;kind&quot;:&quot;notrace-export&quot;,&quot;traceId&quot;:&quot;019ed2ee-5252-76ee-b353-ad925a6bad31&quot;,&quot;repository&quot;:&quot;nothing&quot;,&quot;task&quot;:{&quot;workflow&quot;:&quot;research&quot;,&quot;id&quot;:&quot;branch:main&quot;,&quot;path&quot;:null,&quot;dir&quot;:null},&quot;metrics&quot;:{&quot;inputTokens&quot;:3120,&quot;outputTokens&quot;:179,&quot;cacheReadTokens&quot;:0,&quot;cacheWriteTokens&quot;:0,&quot;totalTokens&quot;:3299,&quot;totalCostUsd&quot;:0.02064},&quot;summary&quot;:&quot;compressed 24,110 to 18,332 tokens; saved 5,778 tokens across 3 tool results&quot;,&quot;events&quot;:[{&quot;model&quot;:&quot;claude-sonnet-4&quot;,&quot;input&quot;:[{&quot;role&quot;:&quot;user&quot;,&quot;content&quot;:&quot;Polish notrace HTML output and unify dashboard/session design.&quot;}],&quot;output&quot;:&quot;Found renderer split. Will move both pages to one shared shell and standardize styles.&quot;}]}" title="Copy session data for LLM/Agent context"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg><span>Export</span></button>
332
374
  </div>
333
375
  </div>
334
376
  <div class="metrics">
@@ -423,9 +465,9 @@
423
465
  </div>
424
466
  <span class="event-time">22:05:04</span>
425
467
  </summary>
426
- <div class="event-body"><div class="stack"><section class="block"><h4>Arguments</h4><pre>{
468
+ <div class="event-body"><div class="stack"><section class="block"><h4>Arguments</h4><div class="msg-content"><div class="chat-tool-use"><div class="chat-tool-header"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"></path></svg> Execution Input</div><pre class="chat-tool-body">{
427
469
  &quot;command&quot;: &quot;find packages/notrace -maxdepth 3 -type f&quot;
428
- }</pre></section></div></div>
470
+ }</pre></div></div></section></div></div>
429
471
  </details><details class="event">
430
472
  <summary>
431
473
  <div class="event-main">
@@ -434,11 +476,11 @@
434
476
  </div>
435
477
  <span class="event-time">22:05:05</span>
436
478
  </summary>
437
- <div class="event-body"><div class="stack"><section class="block"><h4>Result</h4><pre>{
479
+ <div class="event-body"><div class="stack"><section class="block"><h4>Result</h4><div class="msg-content"><div class="chat-tool-use" style=""><div class="chat-tool-header" style="color: var(--muted);"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 10 4 15 9 20"></polyline><path d="M20 4v7a4 4 0 0 1-4 4H4"></path></svg> Execution Output</div><pre class="chat-tool-body">{
438
480
  &quot;stdout&quot;: &quot;packages/notrace/extensions/notrace/index.ts\npackages/notrace/extensions/notrace/renderer.ts\npackages/notrace/package.json&quot;,
439
481
  &quot;stderr&quot;: &quot;&quot;,
440
482
  &quot;exitCode&quot;: 0
441
- }</pre></section></div></div>
483
+ }</pre></div></div></section></div></div>
442
484
  </details><details class="event">
443
485
  <summary>
444
486
  <div class="event-main">
@@ -447,7 +489,7 @@
447
489
  </div>
448
490
  <span class="event-time">22:05:11</span>
449
491
  </summary>
450
- <div class="event-body"><div class="stack"><section class="block"><h4>Input Messages</h4><div class="msg user"><div class="msg-head"><span class="msg-role">user</span></div><div class="msg-content">Polish notrace HTML output and unify dashboard/session design.</div></div></section><section class="block"><h4>Output</h4><pre>Found renderer split. Will move both pages to one shared shell and standardize styles.</pre></section><section class="block"><h4>Usage</h4><pre>{
492
+ <div class="event-body"><div class="stack"><section class="block"><h4>Input Messages</h4><div class="msg user"><div class="msg-head"><span class="msg-role">user</span></div><div class="msg-content"><div class="chat-text">Polish notrace HTML output and unify dashboard/session design.</div></div></div></section><section class="block"><h4>Output</h4><div class="msg-content"><div class="chat-text">Found renderer split. Will move both pages to one shared shell and standardize styles.</div></div></section><section class="block"><h4>Usage</h4><pre>{
451
493
  &quot;input&quot;: 2140,
452
494
  &quot;output&quot;: 116,
453
495
  &quot;cacheRead&quot;: 0,