Skip to content

UX improvements: message viewer, analytics, and sync performance - #10

Merged
wesm merged 9 commits into
mainfrom
ux-improvements
Feb 22, 2026
Merged

UX improvements: message viewer, analytics, and sync performance#10
wesm merged 9 commits into
mainfrom
ux-improvements

Conversation

@wesm

@wesm wesm commented Feb 22, 2026

Copy link
Copy Markdown
Member

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

  • All 304 frontend vitest tests pass (including new analytics tests)
  • 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

wesm and others added 7 commits February 22, 2026 13:31
Switch to Manrope (body) and JetBrains Mono (code) from Google Fonts
for proper typographic hierarchy. Body text uses proportional font at
14px instead of monospace at 13px. Add role icon circles for instant
user/assistant identification. Update color palette to cooler tones
with real purple (#7c3aed) for assistant accent. Code blocks use
dedicated dark background (--code-bg) for strong visual contrast.
Messages constrained to 880px max-width rail for comfortable reading.
Increase spacing throughout: message padding, segment gaps, header
margins. Refine tool blocks and thinking blocks with subtler borders
and cleaner hover states.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the user selects a project in the top bar, the dashboard now
updates to show only that project's analytics data. Previously the
sessions store and analytics store had independent project filters
with no synchronization.

The sync is one-directional: header → dashboard. Local drill-downs
(clicking a project bar in ProjectBreakdown) still work independently
without being overridden by the header filter. Uses untrack() to
avoid the $effect re-triggering on analytics-internal project changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace Manrope with the same system font stack GitHub uses for
prose (-apple-system, BlinkMacSystemFont, Segoe UI, Noto Sans,
Helvetica, Arial). Keep JetBrains Mono for code blocks.

Remove the 880px max-width centering rail from MessageList that
was causing excessive whitespace on the sides. Messages now fill
the available content area with 12px horizontal padding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When clicking a bar in the activity timeline (day granularity), the
chart now highlights the selected bar and dims the others, matching
the behavior of the project breakdown horizontal bar chart. Previously,
clicking a bar re-fetched activity data narrowed to that single date,
causing the chart to re-render with only one bar visible.

The selectDate and clearDate methods no longer call fetchActivity,
keeping the full timeline visible as context. Other panels (summary,
projects, tools, etc.) still update with filtered data for the
selected date.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The file watcher's onChange callback was running SyncAll on every
change, which discovers all session files and computes SHA-256
hashes for each. This caused excessive CPU usage with many sessions.

SyncPaths classifies changed paths by agent type, then processes
only those files. Paths that don't match session file patterns
(directories, non-JSONL files, agent-* files) are filtered out.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ness

Remove fetchAll from onMount and let the $effect handle both initial
load and header project sync, eliminating a double-fetch on page load.

Change fetchActivity to use baseParams() instead of filterParams() so
the timeline always shows the full date range when a date is selected
(matching fetchHeatmap/fetchHourOfWeek). Prevents stale narrow data
when other filters trigger fetchAll while a date is active.

Add clearDate and activity-uses-full-range tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use filepath.Clean + filepath.Rel for path containment checks
  instead of strings.HasPrefix, fixing trailing-slash dir configs
- Tighten Codex classification to require year/month/day structure
  matching DiscoverCodexSessions rules
- Tighten Gemini classification to require tmp/<hash>/chats
  structure matching DiscoverGeminiSessions rules
- Update lastSyncStats in SyncPaths (was only setting lastSync)
- Add tests for trailing-slash dirs, Gemini SyncPaths, flat Codex
  rejection, wrong Gemini structure rejection, and stats update

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@roborev-ci

roborev-ci Bot commented Feb 22, 2026

Copy link
Copy Markdown

roborev: Combined Review (7511cb1)

Verdict: Changes introduce 1 high-severity regression and 2 medium-severity backend correctness issues that should be fixed before merge.

High

  • frontend/src/lib/components/analytics/AnalyticsPage.svelte:54, frontend/src/lib/components/analytics/AnalyticsPage.svelte:60 (also reported around lines 56–65)
    The $effect calls analytics.fetchAll() in a way that can track analytics.project and react to local drill-down changes, causing project selection to be reset to header filters and triggering duplicate/unintended fetches.
    Recommended fix: isolate effect tracking to header filter state (e.g., untrack around fetch/sync logic, or split initial fetch from header-sync branch).

Medium

  • internal/sync/engine.go:89
    SyncPaths updates lastSync but not lastSyncStats, so /sync/status can return stale stats after watcher-triggered syncs.
    Recommended fix: update e.lastSyncStats = stats under the same lock where lastSync is set.

  • internal/sync/engine.go:125, internal/sync/engine.go:135
    Path classification relies on raw strings.HasPrefix(...) and string slicing. With non-normalized configured roots (e.g., trailing slash/non-clean paths), valid watcher updates can be missed.
    Recommended fix: normalize paths (filepath.Clean) and use filepath.Rel-style containment checks instead of prefix/slice math.


Synthesized from 4 reviews (agents: codex, gemini | types: default, security)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@roborev-ci

roborev-ci Bot commented Feb 22, 2026

Copy link
Copy Markdown

roborev: Combined Review (17e63a8)

Verdict: Changes are not clean yet; 4 medium+ issues remain (1 high, 3 medium).

High

  1. Reactive fetch loop / duplicate fetch risk
    • Files: frontend/src/lib/components/analytics/AnalyticsPage.svelte:49, frontend/src/lib/components/analytics/AnalyticsPage.svelte:54-62
    • Issue: analytics.fetchAll() is invoked inside a tracked $effect, so reactive reads inside fetchAll can become dependencies. This can trigger duplicate fetches on unrelated analytics state changes and may reset local drill-down selection.
    • Suggested fix: Call fetch in untrack (or split effects so only sessions.filters.project is tracked).

Medium

  1. Symlink escape in path containment checks

    • Files: internal/sync/engine.go:108, internal/sync/engine.go:126, cmd/agentsview/main.go:209
    • Issue: SyncPaths trusts watcher paths and uses lexical containment checks (Clean/Rel) without resolving symlinks. A symlink inside a watched tree can point outside and still pass classification, enabling unintended file ingestion and TOCTOU exposure.
    • Suggested fix: Canonicalize with filepath.EvalSymlinks (base + candidate), reject symlinked targets (Lstat), and/or enforce no-follow open + FD-level containment verification.
  2. SyncPaths classification too permissive for Claude paths

    • File: internal/sync/engine.go (in classifyOnePath, Claude branch)
    • Issue: Current split logic accepts nested paths like <project>/subdir/file.jsonl, despite expected shape <project>/<session>.jsonl, which can ingest unrelated .jsonl files and diverge from SyncAll behavior.
    • Suggested fix: Require exactly two path segments for Claude session files.
  3. No fallback sync when changed-path batch yields zero classified files

    • File: cmd/agentsview/main.go:207
    • Issue: Watcher callback moved from SyncAll(nil) to SyncPaths(paths) without fallback; temp/rename/dir-only event batches can classify nothing, leaving UI stale until periodic sync.
    • Suggested fix: If paths is non-empty and SyncPaths classifies zero files, trigger fallback full sync (or improve event normalization for atomic-save flows).

Synthesized from 4 reviews (agents: codex, gemini | types: default, security)

- Wrap analytics.fetchAll() in untrack() inside the $effect so that
  reactive reads in filterParams/baseParams don't become tracked
  dependencies (which caused duplicate fetches on unrelated state
  changes like date selection or granularity)
- Change SplitN to Split for Claude path classification so that
  nested paths like proj/subdir/file.jsonl are rejected (discovery
  only walks one level deep)
- Add test for nested Claude path rejection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@wesm
wesm merged commit 85606af into main Feb 22, 2026
5 of 6 checks passed
@roborev-ci

roborev-ci Bot commented Feb 22, 2026

Copy link
Copy Markdown

roborev: Combined Review (01aad04)

Verdict: Mostly clean, but 2 medium-severity regressions should be addressed before merge.

Medium

  1. Directory CREATE watcher events may miss immediate indexing (regression)

    • Files: cmd/agentsview/main.go:207, internal/sync/engine.go:75, internal/sync/engine.go:108
    • Finding: Watcher batches now call SyncPaths(paths), but directory create paths are not classified as session files, so files inside newly added/moved directories may not be indexed until periodic/manual full sync.
    • Suggested fix: If a changed path is a directory (or classification returns none for a create batch), fall back to SyncAll(nil), or make SyncPaths recursively discover session files under changed directories.
  2. Activity timeline can render all bars dimmed after granularity change

    • Files: frontend/src/lib/components/analytics/ActivityTimeline.svelte:249, frontend/src/lib/components/analytics/ActivityTimeline.svelte:250, frontend/src/lib/stores/analytics.svelte.ts:376
    • Finding: Dimming is keyed on selectedDate !== null; after switching from day to week/month, selectedDate can remain set without matching any bucket date, causing all bars to appear dimmed.
    • Suggested fix: Only apply selected/dimmed styling in day granularity, or clear selectedDate when leaving day.

Synthesized from 4 reviews (agents: codex, gemini | types: default, security)

cursor Bot referenced this pull request in diazMelgarejo/periscope Jun 1, 2026
## 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>
@wesm
wesm deleted the ux-improvements branch June 25, 2026 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant