Skip to content

feat(ux): improve session list chronology, pinning, and compact control layout#15

Open
haddadrm wants to merge 21 commits into
nilbuild:mainfrom
haddadrm:codex/ux-integration
Open

feat(ux): improve session list chronology, pinning, and compact control layout#15
haddadrm wants to merge 21 commits into
nilbuild:mainfrom
haddadrm:codex/ux-integration

Conversation

@haddadrm

@haddadrm haddadrm commented Mar 6, 2026

Copy link
Copy Markdown

Summary

This PR delivers a comprehensive UX pass for claude-run focused on session discoverability, chronology accuracy, accessibility, and responsive behavior.

What changed

  • Session chronology now supports last-activity ordering:
    • Added lastUpdatedAt session metadata in API responses.
    • Updated session stream change detection to track lastUpdatedAt.
    • Added list sort modes for Updated (Newest/Oldest) and Started (Newest/Oldest).
  • Session list UX improvements:
    • Grouping, quick recency filters, search highlight, and keyboard navigation.
    • Added pinned conversations with persistent local storage state.
    • Added Pinned quick filter.
    • Added layout polish to prevent filter chip text wrapping in narrow sidebars.
  • Conversation UX improvements:
    • Find in conversation with match navigation.
    • Message metadata visibility.
    • Better long output controls (show more/less) and error-first expansion behavior.
  • App shell and reliability improvements:
    • Persisted selected session/project/sidebar/density state.
    • URL deep-link support for selected session.
    • Connection/reconnect status surfacing in UI.
    • Cross-platform path hardening and safer stream offset parsing.
  • Accessibility/mobile polish:
    • Mobile drawer behavior and focus handling.
    • Improved focus-visible styles and keyboard consistency.

Validation

  • pnpm build passes on the integrated branch after all merges.
  • Manual smoke checks completed for:
    • session load + live updates
    • sorting/filter/search + pinned workflows
    • conversation rendering + find/navigation
    • mobile sidebar and focus/keyboard behavior

Notes

  • Internal planning docs used during multi-stream execution were removed from PR scope.

Copilot AI review requested due to automatic review settings March 6, 2026 12:26

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

UX-focused update to improve session discoverability and conversation usability by adding last-activity chronology, pinned sessions, richer filtering/sorting, find-in-conversation navigation, and stronger accessibility/focus handling across the app shell.

Changes:

  • Add lastUpdatedAt to sessions and use it for update detection + sorting by “last activity”.
  • Enhance session list with grouping, search highlighting, recency filters, pinned sessions (local storage), and keyboard navigation.
  • Improve conversation view with find-in-conversation navigation, message metadata display, and expandable long tool outputs; add connection status + persisted UI preferences.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
web/utils.ts Adds localStorage helpers + URL session query param helpers for persisted UI state and deep linking.
web/index.css Introduces Geist sans font usage, density-based font sizing, root sizing tweaks, and global focus-visible styling.
web/hooks/use-event-source.ts Adds onOpen/onRetry hooks and improves reconnect handling behavior.
web/components/tool-renderers/read-renderer.tsx Adds expandable “show all/less” for truncated file outputs.
web/components/tool-renderers/bash-renderer.tsx Adds expandable “show all/less” for long bash outputs.
web/components/session-view.tsx Adds find-in-conversation UI with match navigation + matched-message highlighting.
web/components/session-list.tsx Adds grouping, recency filters, sort modes, pinned sessions persistence, highlight search matches, and keyboard selection behavior.
web/components/scroll-to-bottom-button.tsx Improves focus styles and adds an ARIA label.
web/components/message-block.tsx Adds message metadata display, improves tool result expansion behavior and focus-visible styling.
web/app.tsx Persists selected project/session/sidebar/density, adds deep linking, mobile drawer behavior, connection indicator, and safer resume command building.
api/storage.ts Adds lastUpdatedAt, improves project path normalization/candidate matching, and computes last activity from file mtimes.
api/server.ts Updates SSE session change detection to use lastUpdatedAt, listens to session file changes, and hardens stream offset parsing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread web/app.tsx
Comment on lines 296 to 305
@@ -98,23 +301,75 @@ function App() {
sessionMap.set(update.id, update);
}
return Array.from(sessionMap.values()).sort(
(a, b) => b.timestamp - a.timestamp,
(a, b) => b.lastUpdatedAt - a.lastUpdatedAt,
);

Copilot AI Mar 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleSessionsUpdate sorts only by lastUpdatedAt. When multiple sessions share the same lastUpdatedAt, the ordering becomes unstable (depends on Map insertion order), which can cause UI reordering/flicker and affect the “pick first session” logic. Add a deterministic tie-breaker (e.g. timestamp then id) to keep ordering stable.

Copilot uses AI. Check for mistakes.
Comment thread web/app.tsx
<div className="flex h-screen bg-zinc-950 text-zinc-100">
{!sidebarCollapsed && (
<aside className="w-80 border-r border-zinc-800/60 flex flex-col bg-zinc-950">
<div className="relative flex h-screen bg-zinc-950 text-zinc-100">

Copilot AI Mar 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The app container uses h-screen (100vh), but index.css sets #root to 100dvh when supported. Using h-screen here effectively bypasses the 100dvh fix on mobile browsers. Consider switching this container to h-full (and rely on #root height) to avoid mobile viewport-height issues.

Suggested change
<div className="relative flex h-screen bg-zinc-950 text-zinc-100">
<div className="relative flex h-full bg-zinc-950 text-zinc-100">

Copilot uses AI. Check for mistakes.
Comment on lines +365 to +366
target instanceof HTMLSelectElement ||
target instanceof HTMLButtonElement

Copilot AI Mar 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleListKeyDown bails out when the event target is a HTMLButtonElement. Since each session row is a <button>, arrow-key navigation won’t work after a user clicks a row (focus moves to the button). Consider only excluding form fields where arrow keys are meaningful (inputs/selects/textareas), or explicitly allow navigation when focus is on the session row button.

Suggested change
target instanceof HTMLSelectElement ||
target instanceof HTMLButtonElement
target instanceof HTMLSelectElement

Copilot uses AI. Check for mistakes.
Comment thread web/index.css
Comment on lines +139 to +142

button:focus-visible,
input:focus-visible,
select:focus-visible,

Copilot AI Mar 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Global outline: none removes the default focus indicator for all buttons/inputs/selects/links. Only :focus-visible gets a custom ring, so browsers/environments without :focus-visible support (or if it’s disabled) will have no visible focus state. Consider keeping a :focus fallback (or not clearing outline globally) so keyboard users always have a focus indicator.

Suggested change
button:focus-visible,
input:focus-visible,
select:focus-visible,
button:focus,
button:focus-visible,
input:focus,
input:focus-visible,
select:focus,
select:focus-visible,
a:focus,

Copilot uses AI. Check for mistakes.
Comment thread api/storage.ts
Comment on lines +333 to +350
const sessionsWithActivity = await Promise.all(
sessions.map(async (session) => {
const filePath = await findSessionFile(session.id);
if (!filePath) {
return session;
}

try {
const fileStat = await stat(filePath);
return {
...session,
lastUpdatedAt: Math.max(session.timestamp, fileStat.mtimeMs),
};
} catch {
return session;
}
}),
);

Copilot AI Mar 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getSessions() now stat()s every session file to compute lastUpdatedAt on each call. Since this runs on every sessions SSE publish, it adds O(n) filesystem I/O per update and can become slow with many sessions. Consider caching mtimeMs (keyed by sessionId/filePath) and only restatting the changed session(s) from onSessionChange, or persisting lastUpdatedAt in the history cache so updates are incremental.

Copilot uses AI. Check for mistakes.
Comment thread web/app.tsx

if (isWindowsPath) {
const escapedPath = escapeForDoubleQuotes(projectPath);
return `cd "${escapedPath}"; claude --resume ${sessionId}`;

Copilot AI Mar 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildResumeCommand uses cd "..."; ... for Windows paths. The ; command separator isn't valid in cmd.exe, and plain cd won't switch drives (needs cd /d). Consider emitting a Windows-friendly command (e.g. using && + /d) or offering separate PowerShell vs cmd variants so the copied resume command works reliably on Windows.

Suggested change
return `cd "${escapedPath}"; claude --resume ${sessionId}`;
return `cd /d "${escapedPath}" && claude --resume ${sessionId}`;

Copilot uses AI. Check for mistakes.
@haddadrm

haddadrm commented Apr 1, 2026

Copy link
Copy Markdown
Author

@copilot apply changes based on the comments in this thread

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants