Skip to content

Commit 9fa5662

Browse files
phurynclaude
andcommitted
feat(dashboard): label column "Title", 160px min-width; extension retry + 20s timeout (#147)
Addressing test feedback: - Rename the user-facing column header (and CSV) from "Topic" to "Title" to match Claude Code's own custom-title / ai-title records. The internal sessions.topic column and s.topic data key are unchanged. - Give the column a 160px min-width (border-box, padding included) so short titles don't collapse it; long titles still wrap and cap at 260px. - VS Code extension: bump the dashboard readiness timeout 10s -> 20s (the one-time topic backfill can slow the first cold start) and add a Retry button — both as an action on the failure notification and a persistent button on the panel (command: URI; enableCommandUris enabled). Tests: +1 sidebar test for the retry link. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9201c6c commit 9fa5662

5 files changed

Lines changed: 32 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44

55
### Dashboard
66

7-
- Added a **Topic** column to the Recent Sessions table (placed after Project) and its CSV export, derived from Claude Code's own session titles: a user-set `custom-title` takes priority over an AI-generated `ai-title`. Long topics wrap within the column. Sessions without a title record show an empty Topic — there is no fallback to the first user message, so prompt text never leaks into the table (#147, thanks @arojunior).
7+
- Added a **Title** column to the Recent Sessions table (after Project) and its CSV export, showing Claude Code's own session title: a user-set `custom-title` takes priority over an AI-generated `ai-title`. Long titles wrap within the column (min width 160px). Sessions without a title record show an empty cell — there is no fallback to the first user message, so prompt text never leaks into the table (#147, thanks @arojunior).
8+
9+
### VS Code extension
10+
11+
- The embedded dashboard now waits up to **20s** (was 10s) for the server to become ready on cold start — the one-time topic backfill can slow the first launch — and a failed start now offers a **Retry** button (both in the error notification and on the panel) instead of only pointing at the command palette (#147).
812

913
### Scanner
1014

dashboard.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ def get_dashboard_data(db_path=DB_PATH):
359359
.cost-na { color: var(--muted); font-family: monospace; font-size: 11px; }
360360
.num { font-family: monospace; }
361361
.muted { color: var(--muted); }
362-
.topic-cell { max-width: 260px; overflow-wrap: anywhere; font-size: 12px; color: var(--text); }
362+
.topic-cell { box-sizing: border-box; min-width: 160px; max-width: 260px; overflow-wrap: anywhere; font-size: 12px; color: var(--text); }
363363
.section-title { font-size: 13px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 12px; }
364364
.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
365365
.section-header .section-title { margin-bottom: 0; }
@@ -570,7 +570,7 @@ def get_dashboard_data(db_path=DB_PATH):
570570
<thead><tr>
571571
<th>Session</th>
572572
<th>Project</th>
573-
<th>Topic</th>
573+
<th>Title</th>
574574
<th class="sortable" onclick="setSessionSort('last')">Last Active <span class="sort-icon" id="sort-icon-last"></span></th>
575575
<th class="sortable" onclick="setSessionSort('duration_min')">Duration <span class="sort-icon" id="sort-icon-duration_min"></span></th>
576576
<th>Model</th>
@@ -1841,7 +1841,7 @@ def get_dashboard_data(db_path=DB_PATH):
18411841
}
18421842
18431843
function exportSessionsCSV() {
1844-
const header = ['Session', 'Project', 'Topic', 'Last Active', 'Duration (min)', 'Model', 'Turns', 'Input', 'Output', 'Cache Read', 'Cache Creation', 'Est. Cost'];
1844+
const header = ['Session', 'Project', 'Title', 'Last Active', 'Duration (min)', 'Model', 'Turns', 'Input', 'Output', 'Cache Read', 'Cache Creation', 'Est. Cost'];
18451845
const rows = lastFilteredSessions.map(s => {
18461846
const cost = calcCost(s.model, s.input, s.output, s.cache_read, s.cache_creation);
18471847
return [s.session_id, s.project, s.topic, s.last, s.duration_min, s.model, s.turns, s.input, s.output, s.cache_read, s.cache_creation, cost.toFixed(4)];

vscode-extension/src/extension.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ class Extension {
146146
args: spawnArgs.args,
147147
url: probeUrl,
148148
output: this.toSink(),
149+
// Cold start can be slow the first time: spawning Python, opening the DB,
150+
// and (once, after upgrade) backfilling session topics across the whole
151+
// history before /api/data answers. Give it 20s before giving up.
152+
readinessTimeoutMs: 20_000,
149153
});
150154
this.server = manager;
151155
try {
@@ -155,9 +159,14 @@ class Extension {
155159
const msg = `Failed to start dashboard: ${(err as Error).message}`;
156160
this.output.appendLine(msg);
157161
this.sidebar.setStatus(msg);
158-
vscode.window.showErrorMessage(msg);
159162
manager.dispose();
160163
if (this.server === manager) this.server = undefined;
164+
// Offer a one-click retry (and log access) rather than making the user
165+
// hunt for the command palette. The sidebar also shows a Retry button.
166+
void vscode.window.showErrorMessage(msg, "Retry", "Show Logs").then((choice) => {
167+
if (choice === "Retry") void this.openDashboard();
168+
else if (choice === "Show Logs") this.output.show();
169+
});
161170
}
162171
}
163172

vscode-extension/src/sidebar.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,15 @@ export function renderHtml(
6363
p { color: #BFBFBF; font-size: 13px; margin: 0 0 8px; }
6464
p.hint { color: #6F6F70; }
6565
code { background: #1E1F20; border: 1px solid #2C2D2E; border-radius: 4px; padding: 1px 5px; font-size: 12px; }
66+
a.retry { display: inline-block; margin: 6px 0 14px; padding: 6px 14px; font-size: 13px; font-weight: 600; color: #161617; background: #BFBFBF; border-radius: 6px; text-decoration: none; }
67+
a.retry:hover { background: #D8D8D8; }
6668
</style>
6769
</head>
6870
<body>
6971
<div class="brand">${logo}<h1>Claude Code Usage</h1></div>
7072
<p>${escapeHtml(statusText) || "The dashboard server is not running yet."}</p>
71-
<p class="hint">Run <code>Claude Usage: Open Dashboard</code> from the command palette to start it.</p>
73+
<p><a class="retry" href="command:claudeUsage.open">&#8635; Retry</a></p>
74+
<p class="hint">Or run <code>Claude Usage: Open Dashboard</code> from the command palette.</p>
7275
</body>
7376
</html>`;
7477
}
@@ -113,7 +116,10 @@ export class DashboardSidebar implements vscode.WebviewViewProvider {
113116

114117
resolveWebviewView(view: vscode.WebviewView): void {
115118
this.view = view;
116-
view.webview.options = { enableScripts: true };
119+
// enableCommandUris lets the status pane's "Retry" link invoke
120+
// claudeUsage.open via a command: URI. The pane renders only our own
121+
// trusted HTML (statusText is escaped), so allowing command URIs is safe.
122+
view.webview.options = { enableScripts: true, enableCommandUris: true };
117123
// Resolve a webview-safe URI for the bundled icon so the status pane shows
118124
// the same logo as the dashboard header. Guarded so node-only tests (whose
119125
// fake view has no asWebviewUri / no vscode.Uri) don't blow up.

vscode-extension/test/sidebar.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ describe("renderHtml with null URL (status pane)", () => {
117117
expect(html).not.toContain("frame-src");
118118
});
119119

120+
it("offers a Retry button that invokes the open command", () => {
121+
const html = renderHtml(null, "Failed to start dashboard: timed out", NONCE);
122+
expect(html).toContain('href="command:claudeUsage.open"');
123+
expect(html).toContain("Retry");
124+
});
125+
120126
it("renders the logo and an img-src CSP when an icon URI is provided", () => {
121127
const html = renderHtml(null, "", NONCE, "https://host/icon.svg", "vscode-webview://abc");
122128
expect(html).toContain('class="logo"');

0 commit comments

Comments
 (0)