Skip to content

Commit 85606af

Browse files
wesmclaude
andauthored
UX improvements: message viewer, analytics, and sync performance (#10)
## Summary - Overhaul session message viewer for readability: system font stack, role icon circles, proportional body text at 14px, darker code blocks, refined tool/thinking blocks - Sync header project filter to analytics dashboard so selecting a project in the top bar filters the dashboard - Highlight selected activity timeline bar instead of re-rendering the chart with a single bar - Fix excessive CPU usage by making the file watcher sync only changed files instead of running full SyncAll with SHA-256 hashing of every file - Fix duplicate fetchAll on analytics page mount and activity timeline staleness ## Test plan - [x] All 304 frontend vitest tests pass (including new analytics tests) - [x] All Go tests pass across all packages (including 5 new SyncPaths tests) - [ ] Manual: verify message viewer readability in light/dark themes - [ ] Manual: verify selecting a project in header updates the analytics dashboard - [ ] Manual: verify clicking a timeline bar highlights it and dims others - [ ] Manual: verify CPU usage is reduced during active file watching 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent babb70f commit 85606af

File tree

16 files changed

+795
-140
lines changed

16 files changed

+795
-140
lines changed

cmd/agentsview/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,8 @@ func printSyncProgress(p sync.Progress) {
204204
func startFileWatcher(
205205
cfg config.Config, engine *sync.Engine,
206206
) func() {
207-
onChange := func(_ []string) {
208-
engine.SyncAll(nil)
207+
onChange := func(paths []string) {
208+
engine.SyncPaths(paths)
209209
}
210210
watcher, err := sync.NewWatcher(watcherDebounce, onChange)
211211
if err != nil {

frontend/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7+
<link rel="preconnect" href="https://fonts.googleapis.com">
8+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9+
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
710
<title>AgentsView</title>
811
</head>
912
<body>

frontend/package-lock.json

Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/app.css

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,60 +7,64 @@
77
padding: 0;
88
}
99

10-
/* Light theme (default) — warm cream professional */
10+
/* Light theme (default) — cool, high-contrast, readable */
1111
:root {
12-
--bg-primary: #faf8f5;
12+
--bg-primary: #f7f7fa;
1313
--bg-surface: #ffffff;
14-
--bg-surface-hover: #f5f2ee;
15-
--bg-inset: #f0ede8;
16-
--border-default: #e5e0db;
17-
--border-muted: #ece8e3;
18-
--text-primary: #2c2825;
19-
--text-secondary: #5c5650;
20-
--text-muted: #8c8580;
14+
--bg-surface-hover: #f0f1f5;
15+
--bg-inset: #edeef3;
16+
--border-default: #dfe1e8;
17+
--border-muted: #e8eaf0;
18+
--text-primary: #1a1d26;
19+
--text-secondary: #5a6070;
20+
--text-muted: #8b92a0;
2121
--accent-blue: #2563eb;
22-
--accent-purple: #b08d24;
22+
--accent-purple: #7c3aed;
2323
--accent-amber: #d97706;
2424
--accent-green: #059669;
2525
--accent-red: #dc2626;
26-
--user-bg: #f0f4ff;
27-
--assistant-bg: #fdf8ee;
28-
--thinking-bg: #fdf6e8;
29-
--tool-bg: #fff8f0;
26+
--user-bg: #eef2ff;
27+
--assistant-bg: #faf9ff;
28+
--thinking-bg: #f5f3ff;
29+
--tool-bg: #fffbf0;
30+
--code-bg: #1e1e2e;
31+
--code-text: #cdd6f4;
3032
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
3133
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.06);
3234
--radius-sm: 4px;
3335
--radius-md: 6px;
3436
--radius-lg: 8px;
35-
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
36-
"Helvetica Neue", sans-serif;
37-
--font-mono: "SF Mono", "Fira Code", "Fira Mono", Menlo, Consolas,
38-
monospace;
37+
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI",
38+
"Noto Sans", Helvetica, Arial, sans-serif;
39+
--font-mono: "JetBrains Mono", "SF Mono", "Fira Code",
40+
"Fira Mono", Menlo, Consolas, monospace;
3941
--viewport-indicator: rgba(0, 0, 0, 0.08);
4042
--overlay-bg: rgba(0, 0, 0, 0.3);
4143
color-scheme: light;
4244
}
4345

4446
/* Dark theme */
4547
:root.dark {
46-
--bg-primary: #0a0a0a;
47-
--bg-surface: #141414;
48-
--bg-surface-hover: #1a1a1a;
49-
--bg-inset: #0e0e0e;
50-
--border-default: #262626;
51-
--border-muted: #1e1e1e;
52-
--text-primary: #e0e0e0;
53-
--text-secondary: #a0a0a0;
54-
--text-muted: #666666;
48+
--bg-primary: #0c0c10;
49+
--bg-surface: #15151b;
50+
--bg-surface-hover: #1e1e28;
51+
--bg-inset: #101015;
52+
--border-default: #2a2a35;
53+
--border-muted: #222230;
54+
--text-primary: #e2e4e9;
55+
--text-secondary: #9ca3af;
56+
--text-muted: #6b7280;
5557
--accent-blue: #60a5fa;
56-
--accent-purple: #c9a84c;
58+
--accent-purple: #a78bfa;
5759
--accent-amber: #fbbf24;
5860
--accent-green: #34d399;
5961
--accent-red: #f87171;
60-
--user-bg: #0f1724;
61-
--assistant-bg: #1a1708;
62-
--thinking-bg: #1e1a0c;
62+
--user-bg: #111827;
63+
--assistant-bg: #141220;
64+
--thinking-bg: #1a1530;
6365
--tool-bg: #1a1508;
66+
--code-bg: #0d0d14;
67+
--code-text: #cdd6f4;
6468
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.2);
6569
--shadow-md: 0 2px 8px rgba(0, 0, 0, 0.3);
6670
--viewport-indicator: rgba(255, 255, 255, 0.08);
@@ -73,7 +77,7 @@ body {
7377
height: 100%;
7478
overflow: hidden;
7579
font-family: var(--font-sans);
76-
font-size: 13px;
80+
font-size: 14px;
7781
line-height: 1.5;
7882
color: var(--text-primary);
7983
background: var(--bg-primary);
@@ -143,7 +147,7 @@ select {
143147
code,
144148
pre {
145149
font-family: var(--font-mono);
146-
font-size: 12px;
150+
font-size: 13px;
147151
}
148152

149153
pre {

frontend/src/lib/components/analytics/ActivityTimeline.svelte

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,8 @@
247247
rx="1"
248248
class="bar"
249249
class:empty={bar.value === 0}
250+
class:selected={analytics.selectedDate === bar.date}
251+
class:dimmed={analytics.selectedDate !== null && analytics.selectedDate !== bar.date}
250252
role="button"
251253
tabindex="0"
252254
onclick={() => handleBarClick(bar)}
@@ -352,12 +354,25 @@
352354
fill: var(--accent-blue);
353355
opacity: 0.8;
354356
cursor: pointer;
357+
transition: opacity 0.15s;
355358
}
356359
357360
.bar:hover {
358361
opacity: 1;
359362
}
360363
364+
.bar.selected {
365+
opacity: 1;
366+
}
367+
368+
.bar.dimmed {
369+
opacity: 0.2;
370+
}
371+
372+
.bar.dimmed:hover {
373+
opacity: 0.5;
374+
}
375+
361376
.bar.empty {
362377
opacity: 0.2;
363378
cursor: default;

frontend/src/lib/components/analytics/AnalyticsPage.svelte

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script lang="ts">
2-
import { onMount, onDestroy } from "svelte";
2+
import { onMount, onDestroy, untrack } from "svelte";
33
import DateRangePicker from "./DateRangePicker.svelte";
44
import SummaryCards from "./SummaryCards.svelte";
55
import Heatmap from "./Heatmap.svelte";
@@ -13,6 +13,7 @@
1313
import TopSessions from "./TopSessions.svelte";
1414
import ActiveFilters from "./ActiveFilters.svelte";
1515
import { analytics } from "../../stores/analytics.svelte.js";
16+
import { sessions } from "../../stores/sessions.svelte.js";
1617
import { exportAnalyticsCSV } from "../../utils/csv-export.js";
1718
1819
function shortTz(tz: string): string {
@@ -39,13 +40,26 @@
3940
let refreshTimer: ReturnType<typeof setInterval> | undefined;
4041
4142
onMount(() => {
42-
analytics.fetchAll();
4343
refreshTimer = setInterval(
4444
() => analytics.fetchAll(),
4545
REFRESH_INTERVAL_MS,
4646
);
4747
});
4848
49+
// Sync header project filter to analytics dashboard and
50+
// handle the initial fetch. Runs on mount (setting the
51+
// initial project) and whenever the header project changes.
52+
// Uses untrack on analytics.project so that local
53+
// drill-downs (clicking a project bar) don't re-trigger.
54+
$effect(() => {
55+
const headerProject = sessions.filters.project;
56+
const current = untrack(() => analytics.project);
57+
if (current !== headerProject) {
58+
analytics.project = headerProject;
59+
}
60+
untrack(() => analytics.fetchAll());
61+
});
62+
4963
onDestroy(() => {
5064
if (refreshTimer !== undefined) {
5165
clearInterval(refreshTimer);

frontend/src/lib/components/content/CodeBlock.svelte

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,28 @@
1616

1717
<style>
1818
.code-block {
19-
background: var(--bg-inset);
20-
border: 1px solid var(--border-muted);
21-
border-radius: var(--radius-sm);
19+
background: var(--code-bg);
20+
border-radius: var(--radius-md);
2221
margin: 4px 0;
2322
overflow: hidden;
2423
}
2524
2625
.code-lang {
27-
padding: 2px 8px;
28-
font-size: 10px;
26+
padding: 4px 12px;
27+
font-family: var(--font-mono);
28+
font-size: 11px;
2929
font-weight: 500;
30-
color: var(--text-muted);
31-
border-bottom: 1px solid var(--border-muted);
30+
color: var(--code-text);
31+
opacity: 0.5;
32+
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
3233
}
3334
3435
.code-content {
35-
padding: 8px;
36+
padding: 12px 16px;
3637
font-family: var(--font-mono);
37-
font-size: 11px;
38-
line-height: 1.5;
39-
color: var(--text-primary);
38+
font-size: 13px;
39+
line-height: 1.55;
40+
color: var(--code-text);
4041
overflow-x: auto;
4142
}
4243

0 commit comments

Comments
 (0)