Add error ID display and copy shortcuts to prod-errors TUI

Show error ID after status label in list view. Add 'c' shortcut to copy error ID from list and full stacktrace from detail view. Replace Enter-to-copy-line with 'c' copy stacktrace in detail. Show error reason in detail header instead of kind.
This commit is contained in:
Claudio Ortolina
2026-05-08 22:43:02 +01:00
parent b7b2324d99
commit 5162b992cd
+41 -10
View File
@@ -760,6 +760,16 @@ class ErrorBrowser {
// ── List mode keys ────────────────────────────────────────────────
if (this.mode === "list" || this.mode === "loading") {
if (matchesKey(data, "c")) {
if (this.errors.length > 0 && this.cursorIndex >= 0) {
const error = this.errors[this.cursorIndex];
if (error) {
this.onCopy?.(String(error.id));
}
}
return;
}
if (matchesKey(data, "r")) {
this.toggleResolved();
return;
@@ -868,13 +878,11 @@ class ErrorBrowser {
return;
}
if (matchesKey(data, Key.enter)) {
// Copy current line to editor (prod-logs pattern)
if (matchesKey(data, "c")) {
if (this.selectedError) {
const detailLines = this.buildDetailLines();
const idx = this.detailScrollOffset;
if (idx < detailLines.length) {
this.onCopy?.(detailLines[idx]!);
const text = this.buildStacktraceText();
if (text) {
this.onCopy?.(text);
}
}
return;
@@ -1035,7 +1043,7 @@ class ErrorBrowser {
error.reason,
Math.max(30, width - 25),
);
const line1 = `${cursor} ${statusBadge}${mutedLabel} ${error.kind}: ${reasonText}`;
const line1 = `${cursor} ${statusBadge} #${error.id}${mutedLabel} ${error.kind}: ${reasonText}`;
result.push(
truncateToWidth(
isCursor ? theme.fg("accent", line1) : line1,
@@ -1075,7 +1083,7 @@ class ErrorBrowser {
truncateToWidth(
theme.fg(
"dim",
` ↑↓/jk navigate ↵ details r ${resolvedLabel} resolved m ${mutedLabel} muted M toggle mute R toggle resolve`,
` ↑↓/jk navigate c copy id ↵ details r ${resolvedLabel} resolved m ${mutedLabel} muted M toggle mute R toggle resolve`,
),
width,
"",
@@ -1104,6 +1112,29 @@ class ErrorBrowser {
return formatDetailLines(error);
}
private buildStacktraceText(): string {
const error = this.selectedError;
if (!error) return "";
const lines: string[] = [];
const occurrences = error.occurrences ?? [];
for (const occ of occurrences) {
if (occ.stacktrace?.lines?.length) {
lines.push(`# Occurrence ${occ.inserted_at}`);
for (const sl of occ.stacktrace.lines) {
const app = sl.application || "\u2014";
const loc = sl.file
? `${sl.file}${sl.line != null ? `:${sl.line}` : ""}`
: "(nofile)";
lines.push(
` ${app} / ${sl.module}.${sl.function}/${sl.arity} ${loc}`,
);
}
lines.push("");
}
}
return lines.join("\n").trimEnd();
}
private renderDetail(width: number, theme: Theme): string[] {
const border = theme.fg("accent", "─".repeat(width));
const result: string[] = [];
@@ -1114,7 +1145,7 @@ class ErrorBrowser {
// Header
const error = this.selectedError;
const headerText = error
? ` Error #${error.id}: ${error.kind}`
? ` Error #${error.id}: ${error.reason}`
: " Error Detail";
result.push(
truncateToWidth(theme.fg("accent", theme.bold(headerText)), width, ""),
@@ -1154,7 +1185,7 @@ class ErrorBrowser {
truncateToWidth(
theme.fg(
"dim",
` Lines ${fromLine}-${toLine} of ${detailLines.length} (${pct}%) ↑↓/jk scroll Enter copy M mute R resolve Escape back`,
` Lines ${fromLine}-${toLine} of ${detailLines.length} (${pct}%) ↑↓/jk scroll c copy stacktrace M mute R resolve Escape back`,
),
width,
"",