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:
@@ -760,6 +760,16 @@ class ErrorBrowser {
|
|||||||
// ── List mode keys ────────────────────────────────────────────────
|
// ── List mode keys ────────────────────────────────────────────────
|
||||||
|
|
||||||
if (this.mode === "list" || this.mode === "loading") {
|
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")) {
|
if (matchesKey(data, "r")) {
|
||||||
this.toggleResolved();
|
this.toggleResolved();
|
||||||
return;
|
return;
|
||||||
@@ -868,13 +878,11 @@ class ErrorBrowser {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchesKey(data, Key.enter)) {
|
if (matchesKey(data, "c")) {
|
||||||
// Copy current line to editor (prod-logs pattern)
|
|
||||||
if (this.selectedError) {
|
if (this.selectedError) {
|
||||||
const detailLines = this.buildDetailLines();
|
const text = this.buildStacktraceText();
|
||||||
const idx = this.detailScrollOffset;
|
if (text) {
|
||||||
if (idx < detailLines.length) {
|
this.onCopy?.(text);
|
||||||
this.onCopy?.(detailLines[idx]!);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -1035,7 +1043,7 @@ class ErrorBrowser {
|
|||||||
error.reason,
|
error.reason,
|
||||||
Math.max(30, width - 25),
|
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(
|
result.push(
|
||||||
truncateToWidth(
|
truncateToWidth(
|
||||||
isCursor ? theme.fg("accent", line1) : line1,
|
isCursor ? theme.fg("accent", line1) : line1,
|
||||||
@@ -1075,7 +1083,7 @@ class ErrorBrowser {
|
|||||||
truncateToWidth(
|
truncateToWidth(
|
||||||
theme.fg(
|
theme.fg(
|
||||||
"dim",
|
"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,
|
width,
|
||||||
"",
|
"",
|
||||||
@@ -1104,6 +1112,29 @@ class ErrorBrowser {
|
|||||||
return formatDetailLines(error);
|
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[] {
|
private renderDetail(width: number, theme: Theme): string[] {
|
||||||
const border = theme.fg("accent", "─".repeat(width));
|
const border = theme.fg("accent", "─".repeat(width));
|
||||||
const result: string[] = [];
|
const result: string[] = [];
|
||||||
@@ -1114,7 +1145,7 @@ class ErrorBrowser {
|
|||||||
// Header
|
// Header
|
||||||
const error = this.selectedError;
|
const error = this.selectedError;
|
||||||
const headerText = error
|
const headerText = error
|
||||||
? ` Error #${error.id}: ${error.kind}`
|
? ` Error #${error.id}: ${error.reason}`
|
||||||
: " Error Detail";
|
: " Error Detail";
|
||||||
result.push(
|
result.push(
|
||||||
truncateToWidth(theme.fg("accent", theme.bold(headerText)), width, ""),
|
truncateToWidth(theme.fg("accent", theme.bold(headerText)), width, ""),
|
||||||
@@ -1154,7 +1185,7 @@ class ErrorBrowser {
|
|||||||
truncateToWidth(
|
truncateToWidth(
|
||||||
theme.fg(
|
theme.fg(
|
||||||
"dim",
|
"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,
|
width,
|
||||||
"",
|
"",
|
||||||
|
|||||||
Reference in New Issue
Block a user