@raquezha/notrace 0.1.2 → 0.2.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.
- package/CHANGELOG.md +10 -0
- package/dist/notrace/index.js +2 -1
- package/dist/notrace/report-app/__tests__/analytics.test.d.ts +1 -0
- package/dist/notrace/report-app/__tests__/analytics.test.js +35 -0
- package/dist/notrace/report-app/__tests__/card.test.d.ts +1 -0
- package/dist/notrace/report-app/__tests__/card.test.js +26 -0
- package/dist/notrace/report-app/__tests__/event.test.d.ts +1 -0
- package/dist/notrace/report-app/__tests__/event.test.js +20 -0
- package/dist/notrace/report-app/__tests__/format.test.d.ts +1 -0
- package/dist/notrace/report-app/__tests__/format.test.js +41 -0
- package/dist/notrace/report-app/__tests__/report.test.d.ts +1 -0
- package/dist/notrace/report-app/__tests__/report.test.js +31 -0
- package/dist/notrace/report-app/analytics.d.ts +3 -0
- package/dist/notrace/report-app/analytics.js +78 -0
- package/dist/notrace/report-app/client.d.ts +2 -0
- package/dist/notrace/report-app/client.js +105 -0
- package/dist/notrace/report-app/components/card.d.ts +4 -0
- package/dist/notrace/report-app/components/card.js +36 -0
- package/dist/notrace/report-app/components/dashboard.d.ts +1 -0
- package/dist/notrace/report-app/components/dashboard.js +16 -0
- package/dist/notrace/report-app/components/event.d.ts +5 -0
- package/dist/notrace/report-app/components/event.js +42 -0
- package/dist/notrace/report-app/components/message.d.ts +2 -0
- package/dist/notrace/report-app/components/message.js +43 -0
- package/dist/notrace/report-app/dashboard-report.d.ts +1 -0
- package/dist/notrace/report-app/dashboard-report.js +6 -0
- package/dist/notrace/report-app/escape.d.ts +1 -0
- package/dist/notrace/report-app/escape.js +10 -0
- package/dist/notrace/report-app/format.d.ts +13 -0
- package/dist/notrace/report-app/format.js +102 -0
- package/dist/notrace/report-app/report.d.ts +1 -0
- package/dist/notrace/report-app/report.js +29 -0
- package/dist/notrace/report-app/shell.d.ts +5 -0
- package/dist/notrace/report-app/shell.js +19 -0
- package/dist/notrace/report-app/styles.d.ts +1 -0
- package/dist/notrace/report-app/styles.js +431 -0
- package/dist/notrace/report-app/types.d.ts +28 -0
- package/dist/notrace/report-app/types.js +1 -0
- package/extensions/notrace/index.ts +2 -1
- package/extensions/notrace/report-app/__tests__/analytics.test.ts +41 -0
- package/extensions/notrace/report-app/__tests__/card.test.ts +29 -0
- package/extensions/notrace/report-app/__tests__/event.test.ts +23 -0
- package/extensions/notrace/report-app/__tests__/format.test.ts +46 -0
- package/extensions/notrace/report-app/__tests__/report.test.ts +33 -0
- package/extensions/notrace/report-app/analytics.ts +79 -0
- package/extensions/notrace/report-app/client.ts +106 -0
- package/extensions/notrace/report-app/components/card.ts +38 -0
- package/extensions/notrace/report-app/components/dashboard.ts +17 -0
- package/extensions/notrace/report-app/components/event.ts +39 -0
- package/extensions/notrace/report-app/components/message.ts +39 -0
- package/extensions/notrace/report-app/dashboard-report.ts +7 -0
- package/extensions/notrace/report-app/escape.ts +10 -0
- package/extensions/notrace/report-app/format.ts +107 -0
- package/extensions/notrace/report-app/report.ts +33 -0
- package/extensions/notrace/report-app/shell.ts +24 -0
- package/extensions/notrace/report-app/styles.ts +431 -0
- package/extensions/notrace/report-app/types.ts +35 -0
- package/package.json +1 -1
- package/templates/dashboard.sample.html +103 -63
- package/templates/dashboard.sample.json +73 -10
- package/templates/render-samples.mjs +119 -1
- package/templates/session.sample.html +125 -168
- package/templates/session.sample.json +66 -7
- package/templates/sessions/019ed2ee-1000-76ee-b353-000000000001/notrace.html +125 -163
- package/templates/sessions/019ed2ee-1000-76ee-b353-000000000001/notrace.json +50 -0
- package/templates/sessions/019ed2ee-1001-76ee-b353-000000000002/notrace.html +125 -162
- package/templates/sessions/019ed2ee-1001-76ee-b353-000000000002/notrace.json +50 -0
- package/templates/sessions/019ed2ee-1002-76ee-b353-000000000003/notrace.html +125 -163
- package/templates/sessions/019ed2ee-1002-76ee-b353-000000000003/notrace.json +50 -0
- package/templates/sessions/019ed2ee-massive/notrace.html +498 -0
- package/templates/sessions/019ed2ee-massive/notrace.json +14660 -0
- package/tsconfig.json +1 -1
- package/dist/notrace/renderer.d.ts +0 -4
- package/dist/notrace/renderer.js +0 -800
- package/extensions/notrace/renderer.ts +0 -810
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { DateValue, MaybeNumber, MaybeString, Repoish, Taskish } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export function parseDate(value: DateValue): Date | null {
|
|
4
|
+
const date = new Date(value);
|
|
5
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function formatDateDay(value: DateValue): string {
|
|
9
|
+
const date = parseDate(value);
|
|
10
|
+
return date ? date.toISOString().slice(0, 10) : String(value ?? "");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function formatTimeMinutes(value: DateValue): string {
|
|
14
|
+
const date = parseDate(value);
|
|
15
|
+
return date ? date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12: false }) : String(value ?? "");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function formatTimeSeconds(value: DateValue): string {
|
|
19
|
+
const date = parseDate(value);
|
|
20
|
+
return date ? date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false }) : String(value ?? "");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function workflowDisplayName(workflow: MaybeString): string {
|
|
24
|
+
switch (workflow) {
|
|
25
|
+
case "norpiv":
|
|
26
|
+
return "RPIV";
|
|
27
|
+
case "research":
|
|
28
|
+
return "Research";
|
|
29
|
+
case "generic":
|
|
30
|
+
default:
|
|
31
|
+
return "Generic";
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function workflowClassName(workflow: MaybeString): string {
|
|
36
|
+
switch (workflow) {
|
|
37
|
+
case "norpiv":
|
|
38
|
+
return "workflow-rpiv";
|
|
39
|
+
case "research":
|
|
40
|
+
return "workflow-research";
|
|
41
|
+
case "generic":
|
|
42
|
+
default:
|
|
43
|
+
return "workflow-generic";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function taskDisplay(taskish: Taskish): string {
|
|
48
|
+
const task = taskish?.task || taskish;
|
|
49
|
+
const workflow = task?.workflow || taskish?.workflow || "generic";
|
|
50
|
+
const taskId = (task as any)?.id ?? taskish?.taskId;
|
|
51
|
+
if (taskId) {
|
|
52
|
+
if (workflow === "research" && String(taskId).startsWith("branch:")) {
|
|
53
|
+
return `Branch ${String(taskId).slice(7)}`;
|
|
54
|
+
}
|
|
55
|
+
return String(taskId);
|
|
56
|
+
}
|
|
57
|
+
if (workflow === "research") return "Open research";
|
|
58
|
+
if (workflow === "generic") return "General session";
|
|
59
|
+
return "No active task";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function resolveRepoName(data: Repoish): string {
|
|
63
|
+
const name = data?.repository?.name || data?.repositoryName || data?.repoName || "Repository";
|
|
64
|
+
const branch = data?.repository?.branch;
|
|
65
|
+
return branch ? `${name} @ ${branch}` : name;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function formatUsd(value: MaybeNumber): string {
|
|
69
|
+
const num = Number(value || 0);
|
|
70
|
+
if (num === 0) return "$0";
|
|
71
|
+
return `$${num.toFixed(5)}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function formatTokens(value: MaybeNumber): string {
|
|
75
|
+
const num = Number(value || 0);
|
|
76
|
+
if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(2)}M`;
|
|
77
|
+
if (num >= 1_000) return `${(num / 1_000).toFixed(1)}k`;
|
|
78
|
+
return num.toString();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function formatMs(value: MaybeNumber): string {
|
|
82
|
+
const ms = Number(value || 0);
|
|
83
|
+
if (!ms) return "-";
|
|
84
|
+
const totalSeconds = Math.round(ms / 1000);
|
|
85
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
86
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
87
|
+
const seconds = totalSeconds % 60;
|
|
88
|
+
if (hours) return `${hours}h ${minutes}m`;
|
|
89
|
+
if (minutes) return `${minutes}m ${seconds}s`;
|
|
90
|
+
return `${seconds}s`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function formatTelemetryStatus(value: MaybeString): string {
|
|
94
|
+
switch (value) {
|
|
95
|
+
case "active":
|
|
96
|
+
return "Active";
|
|
97
|
+
case "loaded-disabled":
|
|
98
|
+
return "Loaded disabled";
|
|
99
|
+
case "loaded-inactive":
|
|
100
|
+
return "Loaded inactive";
|
|
101
|
+
case "absent":
|
|
102
|
+
return "Absent";
|
|
103
|
+
case "unknown":
|
|
104
|
+
default:
|
|
105
|
+
return "Unknown";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { buildModelSummary, buildModelSwitches, groupByModel } from "./analytics.js";
|
|
2
|
+
import { COPY_SCRIPT } from "./client.js";
|
|
3
|
+
import { renderCollapsibleSection } from "./components/card.js";
|
|
4
|
+
import { renderEventCard, renderJsonBlock } from "./components/event.js";
|
|
5
|
+
import { escapeHtml } from "./escape.js";
|
|
6
|
+
import { formatDateDay, formatMs, formatTelemetryStatus, formatTimeMinutes, formatTimeSeconds, formatTokens, formatUsd, parseDate, resolveRepoName, taskDisplay } from "./format.js";
|
|
7
|
+
import { copyButton, exportButton, shell, wordmarkSvg } from "./shell.js";
|
|
8
|
+
|
|
9
|
+
function formatDateLong(value: string | number): string {
|
|
10
|
+
const date = parseDate(value);
|
|
11
|
+
if (!date) return escapeHtml(value);
|
|
12
|
+
return `${escapeHtml(formatDateDay(value))} ${escapeHtml(formatTimeMinutes(value))}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function generateHtmlReport(data: any): string {
|
|
16
|
+
const visibleEvents = (data.events || []).filter((ev: any) => ev.type !== "session_start" && ev.type !== "turn_start");
|
|
17
|
+
const indexHref = data?.navigation?.indexHref || "../../index.html";
|
|
18
|
+
const task = data.task;
|
|
19
|
+
const modelStats = groupByModel(data.events || []);
|
|
20
|
+
const modelSwitches = buildModelSwitches(data.events || []);
|
|
21
|
+
const modelSummary = buildModelSummary(data.events || []);
|
|
22
|
+
const sortedModels = Object.entries(modelStats).sort((a: any, b: any) => Number(b[1]?.cost || 0) - Number(a[1]?.cost || 0));
|
|
23
|
+
const topSpender = sortedModels[0]?.[0] || null;
|
|
24
|
+
const mostUsed = [...sortedModels].sort((a: any, b: any) => Number(b[1]?.count || 0) - Number(a[1]?.count || 0))[0]?.[0] || null;
|
|
25
|
+
const biggestSwitch = [...modelSwitches].sort((a: any, b: any) => Number(b?.cost || 0) - Number(a?.cost || 0))[0] || null;
|
|
26
|
+
|
|
27
|
+
const modelHtml = sortedModels.length ? renderCollapsibleSection("Models", `<div style="padding: 16px; border-bottom: 1px solid var(--border); display: grid; gap: 8px;"><div style="color: var(--muted);">Top spender: <strong style="color: var(--text);">${escapeHtml(topSpender || "unknown")}</strong></div><div style="color: var(--muted);">Most used: <strong style="color: var(--text);">${escapeHtml(mostUsed || "unknown")}</strong></div></div><div style="overflow-x: auto;"><table><thead><tr><th>Model</th><th class="num-cell">Calls</th><th class="num-cell">Input</th><th class="num-cell">Output</th><th class="num-cell">Tokens</th><th class="num-cell">Cost</th><th class="num-cell">Avg Cost/Call</th><th class="num-cell">Avg Tokens/Call</th><th class="num-cell">Errors</th></tr></thead><tbody>${sortedModels.map(([name, stats]: [string, any]) => `<tr><td><strong>${escapeHtml(name)}</strong>${name === topSpender ? ` <span class="badge badge-llm">top spend</span>` : ""}${name === mostUsed ? ` <span class="badge badge-tool">most used</span>` : ""}</td><td class="num-cell">${stats.count}</td><td class="num-cell">${formatTokens(stats.inputTokens)}</td><td class="num-cell">${formatTokens(stats.outputTokens)}</td><td class="num-cell">${formatTokens(stats.totalTokens)}</td><td class="num-cell">${formatUsd(stats.cost)}</td><td class="num-cell">${formatUsd(stats.count ? stats.cost / stats.count : 0)}</td><td class="num-cell">${formatTokens(stats.count ? stats.totalTokens / stats.count : 0)}</td><td class="num-cell">${stats.errors}</td></tr>`).join("")}</tbody></table></div>`, false) : "";
|
|
28
|
+
|
|
29
|
+
const switchHtml = modelSwitches.length ? renderCollapsibleSection("Model Switches", `<div style="padding: 16px; border-bottom: 1px solid var(--border); color: var(--muted);">Biggest switch cost: <strong style="color: var(--text);">${biggestSwitch ? `${escapeHtml(biggestSwitch.from)} → ${escapeHtml(biggestSwitch.to)} (${formatUsd(biggestSwitch.cost)})` : "-"}</strong></div><div style="overflow-x: auto;"><table><thead><tr><th>Switch</th><th class="num-cell">Cost Impact</th><th class="num-cell">New Context Tokens</th><th class="num-cell">Provider Changed</th><th>Details</th></tr></thead><tbody>${modelSwitches.map((sw: any) => `<tr><td><span class="badge badge-system">${escapeHtml(sw.from)}</span> → <span class="badge badge-llm">${escapeHtml(sw.to)}</span></td><td class="num-cell">${formatUsd(sw.cost)}</td><td class="num-cell">${formatTokens(sw.tokens)}</td><td class="num-cell">${sw.providerChanged ? "Yes" : "No"}</td><td><span class="session-sub">#${sw.index} • ${escapeHtml(formatTimeSeconds(sw.timestamp))} • ${Math.round(sw.timeDelta / 1000)}s since previous completion</span></td></tr>`).join("")}</tbody></table></div>`, false) : "";
|
|
30
|
+
|
|
31
|
+
const body = `<div class="container" id="top"><section class="hero"><div class="hero-top"><div><div class="brand"><a class="brand-link" href="${escapeHtml(indexHref)}" onclick="if (window.history.length > 1) { window.history.back(); return false; }">${wordmarkSvg()}</a><p class="subtitle session-subtitle"><span>Session retrospective</span><span class="session-id-chip"><span>${escapeHtml(data.traceId)}</span>${copyButton(String(data.traceId || ""), "session ID")}</span></p></div></div><div class="meta"><span class="pill">${escapeHtml(resolveRepoName(data))}</span><span class="pill">Started ${formatDateLong(data.session?.startedAt)}</span><span class="pill">Mode: ${escapeHtml(data.captureMode || "full")}</span>${exportButton(data)}</div></div><div class="metrics"><div class="metric-card"><small>Total Cost</small><strong>${formatUsd(data.activity?.totals?.totalCostUsd)}</strong></div><div class="metric-card"><small>Total Tokens</small><strong>${Number(data.activity?.totals?.totalTokens || 0).toLocaleString()}</strong></div><div class="metric-card"><small>Duration</small><strong>${formatMs(data.session?.durationMs || data.activity?.durationMs)}</strong></div></div><div class="tiny-breakdown"><span>Input <strong>${formatTokens(data.activity?.totals?.inputTokens || 0)}</strong></span><span>Output <strong>${formatTokens(data.activity?.totals?.outputTokens || 0)}</strong></span><span>Cache Read <strong>${formatTokens(data.activity?.totals?.cacheReadTokens || 0)}</strong></span><span>Cache Write <strong>${formatTokens(data.activity?.totals?.cacheWriteTokens || 0)}</strong></span><span>Switches <strong>${modelSummary ? modelSummary.switchCount : 0}</strong></span><span>Unique Models <strong>${modelSummary ? modelSummary.uniqueModels : 0}</strong></span><span>Tool Calls <strong>${Number(data.activity?.toolCallCount || 0)}</strong></span><span>Events <strong>${visibleEvents.length}</strong></span></div></section>${renderCollapsibleSection("Run Summary", `<div style="padding: 18px 20px; display: grid; gap: 14px;"><div style="color: var(--muted); line-height: 1.7;"><strong style="color: var(--text);">${escapeHtml(taskDisplay(task || { workflow: "generic", id: null }))}</strong> ran for <strong style="color: var(--text);">${formatMs(data.session?.durationMs || data.activity?.durationMs)}</strong>, used <strong style="color: var(--text);">${formatTokens(data.activity?.totals?.totalTokens || 0)}</strong> tokens, cost <strong style="color: var(--text);">${formatUsd(data.activity?.totals?.totalCostUsd)}</strong>.</div><div class="tiny-breakdown" style="margin: 0;"><span>Session <strong>${escapeHtml(data.session?.id || data.traceId || "-")}</strong></span><span>Started <strong>${formatDateLong(data.session?.startedAt)}</strong></span><span>Workflow <strong>${escapeHtml(task?.workflow || "generic")}</strong></span><span>LLM Calls <strong>${Number(data.activity?.llmCallCount || 0)}</strong></span><span>Tool Errors <strong>${Number(data.activity?.toolErrorCount || 0)}</strong></span><span>Providers <strong>${escapeHtml((data.conditions?.providers || []).join(", ") || "-")}</strong></span></div></div>`, false)}${renderCollapsibleSection("Timeline", `<div style="padding: 16px;"><div class="timeline">${visibleEvents.map(renderEventCard).join("") || `<div class="empty">No visible events captured.</div>`}</div></div>`, false)}${modelHtml}${switchHtml}${renderCollapsibleSection("Dynamic Extension Telemetry", `<div style="padding: 16px;" class="stack">${Object.entries(data.telemetry?.extensions || {}).length ? Object.entries(data.telemetry.extensions).map(([name, ext]: [string, any]) => renderJsonBlock(`${name} (${formatTelemetryStatus(ext?.status)})`, { summary: ext?.summary || null, ...ext?.details })).join("") : `<div class="empty">No extension telemetry captured for this run.</div>`}</div>`, false)}<footer class="footer-note stack"><div class="footer-brand">notrace</div><div class="footer-tagline">Local-first retrospective engine</div><div class="footer-meta"><a href="https://opensource.org/licenses/MIT">MIT</a></div></footer></div>`;
|
|
32
|
+
return shell(`notrace - ${data.traceId}`, body, COPY_SCRIPT);
|
|
33
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { escapeHtml } from "./escape.js";
|
|
2
|
+
import { STYLES } from "./styles.js";
|
|
3
|
+
|
|
4
|
+
export function wordmarkSvg(className = "wordmark"): string {
|
|
5
|
+
return `<svg class="${escapeHtml(className)}" viewBox="0 0 420 138" aria-label="notrace" role="img" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="fadeGrad" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stop-color="#E2754A"/><stop offset="100%" stop-color="#EDE2D2"/></linearGradient></defs><g id="trace-icon" transform="translate(1 -18) scale(0.93)"><path d="M6,50 C16,18 26,18 36,50 C46,82 54,82 60,50 C64,30 68,30 71,50" fill="none" stroke="url(#fadeGrad)" stroke-width="4" stroke-linecap="round"/><line x1="74" y1="50" x2="79" y2="50" stroke="#D9C9B5" stroke-width="4" stroke-linecap="round" stroke-opacity="0.6"/><circle cx="85" cy="50" r="2.2" fill="#D9C9B5" opacity="0.5"/><circle cx="90" cy="50" r="1.4" fill="#EDE2D2" opacity="0.32"/><circle cx="94" cy="50" r="0.9" fill="#EDE2D2" opacity="0.15"/></g><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><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></svg>`;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function faviconHref(): string {
|
|
9
|
+
const svg = `<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" role="img"><title>notrace logo mark</title><desc>A wave that smooths into a flat line, then fades into dots — color shifts from trace orange to no cream as it dissolves.</desc><defs><linearGradient id="fadeGrad" x1="0%" y1="0%" x2="100%" y2="0%"><stop offset="0%" stop-color="#E2754A"/><stop offset="100%" stop-color="#EDE2D2"/></linearGradient></defs><g id="trace-icon"><path d="M6,50 C16,18 26,18 36,50 C46,82 54,82 60,50 C64,30 68,30 71,50" fill="none" stroke="url(#fadeGrad)" stroke-width="4" stroke-linecap="round"/><line x1="74" y1="50" x2="79" y2="50" stroke="#D9C9B5" stroke-width="4" stroke-linecap="round" stroke-opacity="0.6"/><circle cx="85" cy="50" r="2.2" fill="#D9C9B5" opacity="0.5"/><circle cx="90" cy="50" r="1.4" fill="#EDE2D2" opacity="0.32"/><circle cx="94" cy="50" r="0.9" fill="#EDE2D2" opacity="0.15"/></g></svg>`;
|
|
10
|
+
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function copyButton(value: string, label: string, className = "copy-btn"): string {
|
|
14
|
+
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>`;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function exportButton(data: any): string {
|
|
18
|
+
const payload = JSON.stringify({ kind: "notrace-export", traceId: data.traceId, repository: data.repository?.name, branch: data.repository?.branch, task: data.task, metrics: data.activity?.totals, summary: data.telemetry?.extensions?.noheadroom?.summary, events: (data.events || []).filter((e: any) => e.type === "llm_completion").map((e: any) => ({ model: e.model, input: e.inputPayload?.messages, output: e.outputContent })) });
|
|
19
|
+
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>`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function shell(title: string, body: string, script = ""): string {
|
|
23
|
+
return `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${escapeHtml(title)}</title><link rel="icon" href="${faviconHref()}"><style>${STYLES}</style></head><body>${body}${script ? `<script>${script}</script>` : ""}</body></html>`;
|
|
24
|
+
}
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
export const STYLES = `:root {
|
|
2
|
+
--bg: #0c0b0a;
|
|
3
|
+
--panel: rgba(255,255,255,0.04);
|
|
4
|
+
--panel-strong: rgba(255,255,255,0.06);
|
|
5
|
+
--text: #ece3da;
|
|
6
|
+
--muted: rgba(236,227,218,0.68);
|
|
7
|
+
--accent: #d88462;
|
|
8
|
+
--accent-soft: rgba(216,132,98,0.12);
|
|
9
|
+
--border: rgba(255,255,255,0.08);
|
|
10
|
+
--shadow: 0 20px 50px rgba(0,0,0,0.45);
|
|
11
|
+
--code: #090807;
|
|
12
|
+
--err: #ef7f7f;
|
|
13
|
+
--rpiv-fg: #f3be8a;
|
|
14
|
+
--rpiv-bg: rgba(243,190,138,0.12);
|
|
15
|
+
--rpiv-border: rgba(243,190,138,0.26);
|
|
16
|
+
--research-fg: #8ec5ff;
|
|
17
|
+
--research-bg: rgba(142,197,255,0.12);
|
|
18
|
+
--research-border: rgba(142,197,255,0.24);
|
|
19
|
+
--generic-fg: #b9b4ae;
|
|
20
|
+
--generic-bg: rgba(185,180,174,0.12);
|
|
21
|
+
--generic-border: rgba(185,180,174,0.2);
|
|
22
|
+
}
|
|
23
|
+
* { box-sizing: border-box; }
|
|
24
|
+
html { color-scheme: dark; }
|
|
25
|
+
body {
|
|
26
|
+
margin: 0;
|
|
27
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
28
|
+
background: var(--bg);
|
|
29
|
+
color: var(--text);
|
|
30
|
+
line-height: 1.5;
|
|
31
|
+
background-image: radial-gradient(circle at 50% -10%, rgba(216,132,98,0.14), transparent 45%);
|
|
32
|
+
background-attachment: fixed;
|
|
33
|
+
}
|
|
34
|
+
a { color: inherit; }
|
|
35
|
+
.container { max-width: 1120px; margin: 0 auto; padding: 32px 20px 64px; }
|
|
36
|
+
.hero, .panel {
|
|
37
|
+
background: var(--panel);
|
|
38
|
+
border: 1px solid var(--border);
|
|
39
|
+
border-radius: 24px;
|
|
40
|
+
box-shadow: var(--shadow);
|
|
41
|
+
backdrop-filter: blur(10px);
|
|
42
|
+
}
|
|
43
|
+
.hero { padding: 28px; margin-bottom: 24px; }
|
|
44
|
+
.hero-top {
|
|
45
|
+
display: grid;
|
|
46
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
47
|
+
gap: 16px;
|
|
48
|
+
align-items: start;
|
|
49
|
+
}
|
|
50
|
+
.hero-split {
|
|
51
|
+
display: grid;
|
|
52
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
53
|
+
gap: 16px;
|
|
54
|
+
align-items: start;
|
|
55
|
+
}
|
|
56
|
+
.hero-right {
|
|
57
|
+
display: grid;
|
|
58
|
+
gap: 12px;
|
|
59
|
+
justify-items: end;
|
|
60
|
+
min-width: 0;
|
|
61
|
+
}
|
|
62
|
+
.hero-session {
|
|
63
|
+
display: grid;
|
|
64
|
+
gap: 4px;
|
|
65
|
+
text-align: right;
|
|
66
|
+
min-width: 0;
|
|
67
|
+
}
|
|
68
|
+
.hero-meta {
|
|
69
|
+
display: flex;
|
|
70
|
+
flex-wrap: wrap;
|
|
71
|
+
gap: 8px;
|
|
72
|
+
justify-content: flex-end;
|
|
73
|
+
}
|
|
74
|
+
.brand { margin-bottom: 10px; }
|
|
75
|
+
.brand-link {
|
|
76
|
+
display: inline-flex;
|
|
77
|
+
align-items: flex-start;
|
|
78
|
+
text-decoration: none;
|
|
79
|
+
}
|
|
80
|
+
.wordmark {
|
|
81
|
+
width: 340px;
|
|
82
|
+
height: 112px;
|
|
83
|
+
display: block;
|
|
84
|
+
overflow: visible;
|
|
85
|
+
}
|
|
86
|
+
.subtitle { margin: 10px 0 0; color: var(--muted); }
|
|
87
|
+
.session-subtitle {
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
gap: 10px;
|
|
91
|
+
flex-wrap: wrap;
|
|
92
|
+
}
|
|
93
|
+
.session-id-chip {
|
|
94
|
+
display: inline-flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
gap: 8px;
|
|
97
|
+
max-width: 100%;
|
|
98
|
+
padding: 6px 8px 6px 10px;
|
|
99
|
+
border: 1px solid var(--border);
|
|
100
|
+
border-radius: 999px;
|
|
101
|
+
background: rgba(0,0,0,0.18);
|
|
102
|
+
color: var(--text);
|
|
103
|
+
font-family: "SFMono-Regular", ui-monospace, Menlo, Monaco, Consolas, monospace;
|
|
104
|
+
font-size: 0.78rem;
|
|
105
|
+
word-break: break-all;
|
|
106
|
+
}
|
|
107
|
+
.copy-btn {
|
|
108
|
+
display: inline-flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
justify-content: center;
|
|
111
|
+
width: 26px;
|
|
112
|
+
height: 26px;
|
|
113
|
+
border: 1px solid rgba(255,255,255,0.12);
|
|
114
|
+
border-radius: 999px;
|
|
115
|
+
background: rgba(255,255,255,0.04);
|
|
116
|
+
color: var(--muted);
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
|
|
119
|
+
}
|
|
120
|
+
.copy-btn:hover, .copy-btn.copied {
|
|
121
|
+
color: var(--text);
|
|
122
|
+
border-color: rgba(216,132,98,0.45);
|
|
123
|
+
background: var(--accent-soft);
|
|
124
|
+
}
|
|
125
|
+
.meta {
|
|
126
|
+
display: flex;
|
|
127
|
+
gap: 8px;
|
|
128
|
+
flex-wrap: wrap;
|
|
129
|
+
justify-content: flex-end;
|
|
130
|
+
align-items: center;
|
|
131
|
+
margin-top: 16px;
|
|
132
|
+
}
|
|
133
|
+
.pill, .workflow-pill, .sort-btn, .export-btn {
|
|
134
|
+
display: inline-flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
gap: 6px;
|
|
137
|
+
text-decoration: none;
|
|
138
|
+
padding: 8px 12px;
|
|
139
|
+
border: 1px solid var(--border);
|
|
140
|
+
border-radius: 999px;
|
|
141
|
+
background: rgba(255,255,255,0.03);
|
|
142
|
+
color: var(--muted);
|
|
143
|
+
font-size: 0.86rem;
|
|
144
|
+
font-weight: 600;
|
|
145
|
+
}
|
|
146
|
+
.metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(135px, 1fr)); gap: 16px; margin: 24px 0; }
|
|
147
|
+
.metric-card {
|
|
148
|
+
background: var(--panel-strong);
|
|
149
|
+
border: 1px solid var(--border);
|
|
150
|
+
border-radius: 18px;
|
|
151
|
+
padding: 18px;
|
|
152
|
+
min-width: 0;
|
|
153
|
+
}
|
|
154
|
+
.metric-card small { display: block; color: var(--accent); text-transform: uppercase; letter-spacing: 0.08em; font-size: 0.72rem; font-weight: 700; }
|
|
155
|
+
.metric-card strong { display: block; margin-top: 8px; font-size: clamp(1rem, 2vw, 1.55rem); overflow-wrap: anywhere; }
|
|
156
|
+
.panel { padding: 0; overflow: hidden; }
|
|
157
|
+
.section-title { margin: 0; padding: 20px 22px; border-bottom: 1px solid var(--border); font-size: 1rem; }
|
|
158
|
+
.summary-pills { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
|
|
159
|
+
.kv-grid {
|
|
160
|
+
display: grid;
|
|
161
|
+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
162
|
+
gap: 16px;
|
|
163
|
+
}
|
|
164
|
+
.kv-card {
|
|
165
|
+
background: rgba(0,0,0,0.18);
|
|
166
|
+
border: 1px solid var(--border);
|
|
167
|
+
border-radius: 16px;
|
|
168
|
+
padding: 16px;
|
|
169
|
+
}
|
|
170
|
+
.kv-title {
|
|
171
|
+
font-size: 0.72rem;
|
|
172
|
+
text-transform: uppercase;
|
|
173
|
+
color: var(--accent);
|
|
174
|
+
font-weight: 700;
|
|
175
|
+
letter-spacing: 0.08em;
|
|
176
|
+
margin-bottom: 10px;
|
|
177
|
+
}
|
|
178
|
+
.kv-list { display: grid; gap: 8px; }
|
|
179
|
+
.kv-row { display: flex; justify-content: space-between; gap: 12px; align-items: start; }
|
|
180
|
+
.kv-key { color: var(--muted); }
|
|
181
|
+
.kv-value { color: var(--text); text-align: right; word-break: break-word; }
|
|
182
|
+
.tiny-breakdown {
|
|
183
|
+
margin-top: -10px;
|
|
184
|
+
margin-bottom: 8px;
|
|
185
|
+
color: var(--muted);
|
|
186
|
+
font-size: 0.82rem;
|
|
187
|
+
display: flex;
|
|
188
|
+
flex-wrap: wrap;
|
|
189
|
+
gap: 8px 14px;
|
|
190
|
+
}
|
|
191
|
+
.tiny-breakdown strong { color: var(--text); font-size: inherit; font-weight: 600; }
|
|
192
|
+
.collapsible { margin-top: 24px; }
|
|
193
|
+
.collapsible > summary { list-style: none; cursor: pointer; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 18px 22px; font-size: 1rem; font-weight: 700; }
|
|
194
|
+
.collapsible > summary::-webkit-details-marker { display: none; }
|
|
195
|
+
.collapsible > summary:hover { background: rgba(255,255,255,0.02); }
|
|
196
|
+
.collapsible > summary::after { content: "▾"; color: var(--muted); font-size: 0.9rem; }
|
|
197
|
+
.collapsible:not([open]) > summary::after { content: "▸"; }
|
|
198
|
+
.empty { padding: 32px 22px; color: var(--muted); }
|
|
199
|
+
table { width: 100%; border-collapse: collapse; }
|
|
200
|
+
th, td { padding: 14px 18px; text-align: left; border-bottom: 1px solid var(--border); vertical-align: top; }
|
|
201
|
+
th { color: var(--muted); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.08em; }
|
|
202
|
+
.num-cell { text-align: right; }
|
|
203
|
+
tr:last-child td { border-bottom: 0; }
|
|
204
|
+
.col-index { width: 64px; }
|
|
205
|
+
.sortable-head { padding: 10px 18px; }
|
|
206
|
+
.sort-btn {
|
|
207
|
+
padding: 0;
|
|
208
|
+
border: 0;
|
|
209
|
+
border-radius: 0;
|
|
210
|
+
background: transparent;
|
|
211
|
+
font: inherit;
|
|
212
|
+
text-transform: inherit;
|
|
213
|
+
letter-spacing: inherit;
|
|
214
|
+
cursor: pointer;
|
|
215
|
+
}
|
|
216
|
+
.sort-label { color: inherit; }
|
|
217
|
+
.sort-state { color: var(--accent); font-size: 0.9rem; min-width: 16px; text-align: left; line-height: 1; }
|
|
218
|
+
.index-cell { color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
219
|
+
.session-link { text-decoration: none; }
|
|
220
|
+
.session-link strong { display: block; }
|
|
221
|
+
.session-sub { display: block; margin-top: 2px; color: var(--muted); font-size: 0.8rem; }
|
|
222
|
+
.workflow-pill { padding: 6px 10px; font-size: 0.78rem; }
|
|
223
|
+
.workflow-rpiv { color: var(--rpiv-fg); background: var(--rpiv-bg); border-color: var(--rpiv-border); }
|
|
224
|
+
.workflow-research { color: var(--research-fg); background: var(--research-bg); border-color: var(--research-border); }
|
|
225
|
+
.workflow-generic { color: var(--generic-fg); background: var(--generic-bg); border-color: var(--generic-border); }
|
|
226
|
+
.date-cell { display: grid; gap: 2px; }
|
|
227
|
+
.date-cell strong { font-size: 0.92rem; }
|
|
228
|
+
.date-cell span { color: var(--muted); font-size: 0.82rem; }
|
|
229
|
+
.timeline { display: grid; gap: 14px; }
|
|
230
|
+
.event {
|
|
231
|
+
background: var(--panel);
|
|
232
|
+
border: 1px solid var(--border);
|
|
233
|
+
border-radius: 18px;
|
|
234
|
+
overflow: hidden;
|
|
235
|
+
}
|
|
236
|
+
.event summary {
|
|
237
|
+
list-style: none;
|
|
238
|
+
cursor: pointer;
|
|
239
|
+
display: flex;
|
|
240
|
+
justify-content: space-between;
|
|
241
|
+
gap: 14px;
|
|
242
|
+
align-items: center;
|
|
243
|
+
padding: 16px 18px;
|
|
244
|
+
}
|
|
245
|
+
.event summary::-webkit-details-marker { display: none; }
|
|
246
|
+
.event summary:hover { background: rgba(255,255,255,0.02); }
|
|
247
|
+
.event-main { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
|
|
248
|
+
.badge {
|
|
249
|
+
display: inline-flex;
|
|
250
|
+
align-items: center;
|
|
251
|
+
padding: 4px 8px;
|
|
252
|
+
border-radius: 999px;
|
|
253
|
+
font-size: 0.72rem;
|
|
254
|
+
font-weight: 800;
|
|
255
|
+
text-transform: uppercase;
|
|
256
|
+
letter-spacing: 0.06em;
|
|
257
|
+
border: 1px solid var(--border);
|
|
258
|
+
background: rgba(255,255,255,0.03);
|
|
259
|
+
}
|
|
260
|
+
.badge-llm { color: var(--accent); background: var(--accent-soft); border-color: rgba(216,132,98,0.24); }
|
|
261
|
+
.badge-tool { color: #8ec5ff; background: rgba(142,197,255,0.1); border-color: rgba(142,197,255,0.22); }
|
|
262
|
+
.badge-system { color: var(--muted); }
|
|
263
|
+
.badge-error { color: var(--err); background: rgba(239,127,127,0.12); border-color: rgba(239,127,127,0.24); }
|
|
264
|
+
.event-title { font-weight: 700; }
|
|
265
|
+
.event-time { color: var(--muted); font-size: 0.9rem; white-space: nowrap; }
|
|
266
|
+
.event-body { padding: 0 18px 18px; }
|
|
267
|
+
.stack { display: grid; gap: 12px; }
|
|
268
|
+
.block {
|
|
269
|
+
background: rgba(0,0,0,0.18);
|
|
270
|
+
border: 1px solid var(--border);
|
|
271
|
+
border-radius: 14px;
|
|
272
|
+
overflow: hidden;
|
|
273
|
+
}
|
|
274
|
+
.block h4 {
|
|
275
|
+
margin: 0;
|
|
276
|
+
padding: 10px 12px;
|
|
277
|
+
border-bottom: 1px solid var(--border);
|
|
278
|
+
color: var(--muted);
|
|
279
|
+
font-size: 0.8rem;
|
|
280
|
+
text-transform: uppercase;
|
|
281
|
+
letter-spacing: 0.08em;
|
|
282
|
+
}
|
|
283
|
+
pre {
|
|
284
|
+
margin: 0;
|
|
285
|
+
padding: 14px;
|
|
286
|
+
overflow-x: auto;
|
|
287
|
+
white-space: pre-wrap;
|
|
288
|
+
word-break: break-word;
|
|
289
|
+
font-family: "SFMono-Regular", ui-monospace, Menlo, Monaco, Consolas, monospace;
|
|
290
|
+
font-size: 0.84rem;
|
|
291
|
+
background: var(--code);
|
|
292
|
+
}
|
|
293
|
+
.msg { border-bottom: 1px solid var(--border); }
|
|
294
|
+
.msg:last-child { border-bottom: 0; }
|
|
295
|
+
.msg-head {
|
|
296
|
+
display: flex;
|
|
297
|
+
justify-content: space-between;
|
|
298
|
+
gap: 12px;
|
|
299
|
+
padding: 10px 12px;
|
|
300
|
+
border-bottom: 1px solid var(--border);
|
|
301
|
+
background: rgba(255,255,255,0.02);
|
|
302
|
+
}
|
|
303
|
+
.msg-role { font-size: 0.78rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; }
|
|
304
|
+
.msg.user .msg-role { color: #8ec5ff; }
|
|
305
|
+
.msg.assistant .msg-role { color: var(--accent); }
|
|
306
|
+
.msg-content { padding: 14px; }
|
|
307
|
+
.chat-text {
|
|
308
|
+
white-space: pre-wrap;
|
|
309
|
+
word-break: break-word;
|
|
310
|
+
font-size: 0.95rem;
|
|
311
|
+
line-height: 1.6;
|
|
312
|
+
margin-bottom: 12px;
|
|
313
|
+
}
|
|
314
|
+
.chat-text:last-child { margin-bottom: 0; }
|
|
315
|
+
.chat-tool-use {
|
|
316
|
+
background: rgba(0,0,0,0.3);
|
|
317
|
+
border: 1px solid var(--border);
|
|
318
|
+
border-radius: 8px;
|
|
319
|
+
overflow: hidden;
|
|
320
|
+
margin-bottom: 12px;
|
|
321
|
+
}
|
|
322
|
+
.chat-tool-use:last-child { margin-bottom: 0; }
|
|
323
|
+
.chat-tool-header {
|
|
324
|
+
background: rgba(255,255,255,0.04);
|
|
325
|
+
padding: 8px 12px;
|
|
326
|
+
font-size: 0.8rem;
|
|
327
|
+
font-family: "SFMono-Regular", ui-monospace, Menlo, Monaco, Consolas, monospace;
|
|
328
|
+
color: #8ec5ff;
|
|
329
|
+
border-bottom: 1px solid var(--border);
|
|
330
|
+
display: flex;
|
|
331
|
+
align-items: center;
|
|
332
|
+
gap: 8px;
|
|
333
|
+
}
|
|
334
|
+
.chat-tool-body {
|
|
335
|
+
padding: 12px;
|
|
336
|
+
margin: 0;
|
|
337
|
+
background: transparent;
|
|
338
|
+
border: none;
|
|
339
|
+
max-height: 400px;
|
|
340
|
+
overflow-y: auto;
|
|
341
|
+
}
|
|
342
|
+
.footer-note {
|
|
343
|
+
margin-top: 22px;
|
|
344
|
+
color: var(--muted);
|
|
345
|
+
text-align: center;
|
|
346
|
+
padding: 10px 0 0;
|
|
347
|
+
font-family: inherit;
|
|
348
|
+
}
|
|
349
|
+
.footer-note.minimal {
|
|
350
|
+
font-size: 0.84rem;
|
|
351
|
+
font-variant-caps: all-small-caps;
|
|
352
|
+
letter-spacing: 0.14em;
|
|
353
|
+
line-height: 1;
|
|
354
|
+
}
|
|
355
|
+
.footer-note.stack {
|
|
356
|
+
display: grid;
|
|
357
|
+
gap: 6px;
|
|
358
|
+
line-height: 1.2;
|
|
359
|
+
}
|
|
360
|
+
.footer-brand {
|
|
361
|
+
color: var(--text);
|
|
362
|
+
font-size: 0.88rem;
|
|
363
|
+
font-weight: 700;
|
|
364
|
+
font-variant-caps: all-small-caps;
|
|
365
|
+
letter-spacing: 0.16em;
|
|
366
|
+
}
|
|
367
|
+
.footer-tagline {
|
|
368
|
+
font-size: 0.78rem;
|
|
369
|
+
letter-spacing: 0.08em;
|
|
370
|
+
font-variant-caps: all-small-caps;
|
|
371
|
+
}
|
|
372
|
+
.footer-meta {
|
|
373
|
+
font-size: 0.76rem;
|
|
374
|
+
font-variant-caps: all-small-caps;
|
|
375
|
+
letter-spacing: 0.14em;
|
|
376
|
+
}
|
|
377
|
+
.footer-meta a {
|
|
378
|
+
color: inherit;
|
|
379
|
+
text-decoration: none;
|
|
380
|
+
border-bottom: 1px solid rgba(236,227,218,0.22);
|
|
381
|
+
}
|
|
382
|
+
.footer-meta a:hover {
|
|
383
|
+
color: var(--text);
|
|
384
|
+
border-bottom-color: rgba(236,227,218,0.45);
|
|
385
|
+
}
|
|
386
|
+
.export-btn {
|
|
387
|
+
cursor: pointer;
|
|
388
|
+
transition: color 120ms ease, border-color 120ms ease, background 120ms ease;
|
|
389
|
+
}
|
|
390
|
+
.export-btn:hover, .export-btn.copied {
|
|
391
|
+
color: var(--text);
|
|
392
|
+
border-color: rgba(216,132,98,0.45);
|
|
393
|
+
background: var(--accent-soft);
|
|
394
|
+
}
|
|
395
|
+
.back-to-top {
|
|
396
|
+
position: fixed;
|
|
397
|
+
right: 20px;
|
|
398
|
+
bottom: 20px;
|
|
399
|
+
z-index: 20;
|
|
400
|
+
display: inline-flex;
|
|
401
|
+
align-items: center;
|
|
402
|
+
gap: 8px;
|
|
403
|
+
padding: 10px 14px;
|
|
404
|
+
border: 1px solid var(--border);
|
|
405
|
+
border-radius: 999px;
|
|
406
|
+
background: rgba(12,11,10,0.88);
|
|
407
|
+
color: var(--text);
|
|
408
|
+
text-decoration: none;
|
|
409
|
+
box-shadow: var(--shadow);
|
|
410
|
+
backdrop-filter: blur(10px);
|
|
411
|
+
opacity: 0;
|
|
412
|
+
pointer-events: none;
|
|
413
|
+
transform: translateY(8px);
|
|
414
|
+
transition: opacity 160ms ease, transform 160ms ease, border-color 120ms ease, background 120ms ease;
|
|
415
|
+
}
|
|
416
|
+
.back-to-top.visible {
|
|
417
|
+
opacity: 1;
|
|
418
|
+
pointer-events: auto;
|
|
419
|
+
transform: translateY(0);
|
|
420
|
+
}
|
|
421
|
+
.back-to-top:hover { border-color: rgba(216,132,98,0.45); background: rgba(216,132,98,0.12); }
|
|
422
|
+
.container { padding: 20px 14px 48px; }
|
|
423
|
+
.hero { padding: 20px; }
|
|
424
|
+
.hero-top, .hero-split { grid-template-columns: 1fr; }
|
|
425
|
+
.meta, .hero-meta { justify-content: flex-start; margin-top: 8px; }
|
|
426
|
+
.hero-right, .hero-session { justify-items: start; text-align: left; }
|
|
427
|
+
.wordmark { width: min(280px, 100%); height: auto; }
|
|
428
|
+
.metrics { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); }
|
|
429
|
+
th:nth-child(5), td:nth-child(5) { display: none; }
|
|
430
|
+
.event summary { align-items: flex-start; }
|
|
431
|
+
}`;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type WorkflowName = "norpiv" | "research" | "generic" | string;
|
|
2
|
+
|
|
3
|
+
export type TaskLike = {
|
|
4
|
+
workflow?: WorkflowName | null;
|
|
5
|
+
id?: string | null;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type Taskish = {
|
|
9
|
+
task?: TaskLike | null;
|
|
10
|
+
workflow?: WorkflowName | null;
|
|
11
|
+
taskId?: string | null;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type RepositoryLike = {
|
|
15
|
+
name?: string | null;
|
|
16
|
+
branch?: string | null;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type Repoish = {
|
|
20
|
+
repository?: RepositoryLike | null;
|
|
21
|
+
repositoryName?: string | null;
|
|
22
|
+
repoName?: string | null;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type StatusLike = {
|
|
26
|
+
status?: string | null;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type EventLike = {
|
|
30
|
+
type?: string | null;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type DateValue = string | number;
|
|
34
|
+
export type MaybeNumber = number | null | undefined;
|
|
35
|
+
export type MaybeString = string | null | undefined;
|
package/package.json
CHANGED