refactor(projects/sidebar): remove temp snapshot side-effects and simplify session metadata UX

Why this change was needed:
- Project listing had an implicit side effect: every fetch wrote a debug snapshot under `.tmp/project-dumps`.
  That added unnecessary disk I/O to a hot path, introduced hidden runtime behavior, and created maintenance
  overhead for code that was not part of product functionality.
- Keeping snapshot-specific exports/tests around made the projects module API broader than needed and coupled
  tests to temporary/debug behavior instead of user-visible behavior.
- Codex sessions could remain stuck with a placeholder name (`Untitled Codex Session`) even after a real title
  became available from newer sync data, which degraded session discoverability in the UI.
- Sidebar session rows showed duplicated provider branding and long-form relative times, which added visual noise
  and reduced scan speed when many sessions are listed.

What changed:
- Removed temporary projects snapshot dumping from `projects-with-sessions-fetch.service.ts`:
  - deleted snapshot types/helpers and file-write flow
  - removed the write call from `getProjectsWithSessions`
- Removed snapshot-related surface area from `projects/index.ts`.
- Removed the snapshot-focused test `projects.service.test.ts` that only validated removed debug behavior.
- Updated `codex-session-synchronizer.provider.ts` to upgrade session names when an existing session still has
  the placeholder title but a real parsed name is now available.
- Updated `SidebarSessionItem.tsx`:
  - removed duplicate provider logo rendering in each session row
  - moved age indicator to the right side
  - made age indicator fade on hover to prioritize action controls
  - switched to compact relative time format (`<1m`, `Xm`, `Xhr`, `Xd`) for faster list scanning

Outcome:
- Lower overhead and fewer hidden side effects in project fetches.
- Cleaner module boundaries in projects.
- Better Codex session naming consistency after sync.
- Cleaner sidebar density and clearer hover/action behavior.
This commit is contained in:
Haileyesus
2026-04-27 21:07:54 +03:00
parent 16954c883b
commit 14e6b5b7b2
5 changed files with 56 additions and 133 deletions

View File

@@ -1,8 +1,8 @@
import { Check, Clock, Edit2, Trash2, X } from 'lucide-react';
import { Check, Edit2, Trash2, X } from 'lucide-react';
import type { TFunction } from 'i18next';
import { Badge, Button } from '../../../../shared/view/ui';
import { cn } from '../../../../lib/utils';
import { formatTimeAgo } from '../../../../utils/dateUtils';
import type { Project, ProjectSession, LLMProvider } from '../../../../types/app';
import type { SessionWithProvider } from '../../types/types';
import { createSessionViewModel } from '../../utils/utils';
@@ -30,6 +30,34 @@ type SidebarSessionItemProps = {
t: TFunction;
};
/**
* Compact relative time for sidebar rows:
* <1m, Xm, Xhr, Xd.
*/
const formatCompactSessionAge = (dateString: string, currentTime: Date): string => {
const date = new Date(dateString);
if (Number.isNaN(date.getTime())) {
return '';
}
const diffInMinutes = Math.floor(Math.max(0, currentTime.getTime() - date.getTime()) / (1000 * 60));
if (diffInMinutes < 1) {
return '<1m';
}
if (diffInMinutes < 60) {
return `${diffInMinutes}m`;
}
const diffInHours = Math.floor(diffInMinutes / 60);
if (diffInHours < 24) {
return `${diffInHours}hr`;
}
const diffInDays = Math.floor(diffInHours / 24);
return `${diffInDays}d`;
};
export default function SidebarSessionItem({
project,
session,
@@ -48,6 +76,7 @@ export default function SidebarSessionItem({
}: SidebarSessionItemProps) {
const sessionView = createSessionViewModel(session, currentTime, t);
const isSelected = selectedSession?.id === session.id;
const compactSessionAge = formatCompactSessionAge(sessionView.sessionTime, currentTime);
// Sessions are owned by a project identified by `projectId` (DB primary key)
// after the projectName → projectId migration.
@@ -94,20 +123,18 @@ export default function SidebarSessionItem({
</div>
<div className="min-w-0 flex-1">
<div className="truncate text-xs font-medium text-foreground">{sessionView.sessionName}</div>
<div className="mt-0.5 flex items-center gap-1">
<Clock className="h-2.5 w-2.5 text-muted-foreground" />
<span className="text-xs text-muted-foreground">
{formatTimeAgo(sessionView.sessionTime, currentTime, t)}
</span>
<div className="flex items-center gap-2">
<div className="truncate text-xs font-medium text-foreground">{sessionView.sessionName}</div>
{compactSessionAge && (
<span className="ml-auto flex-shrink-0 text-[11px] text-muted-foreground">{compactSessionAge}</span>
)}
</div>
<div className="mt-0.5 flex items-center">
{sessionView.messageCount > 0 && (
<Badge variant="secondary" className="ml-auto px-1 py-0 text-xs">
<Badge variant="secondary" className="px-1 py-0 text-xs">
{sessionView.messageCount}
</Badge>
)}
<span className="ml-1 opacity-70">
<SessionProviderLogo provider={session.__provider} className="h-3 w-3" />
</span>
</div>
</div>
@@ -138,23 +165,16 @@ export default function SidebarSessionItem({
<div className="flex w-full min-w-0 items-start gap-2">
<SessionProviderLogo provider={session.__provider} className="mt-0.5 h-3 w-3 flex-shrink-0" />
<div className="min-w-0 flex-1">
<div className="truncate text-xs font-medium text-foreground">{sessionView.sessionName}</div>
<div className="mt-0.5 flex items-center gap-1">
<Clock className="h-2.5 w-2.5 text-muted-foreground" />
<span className="text-xs text-muted-foreground">
{formatTimeAgo(sessionView.sessionTime, currentTime, t)}
</span>
{sessionView.messageCount > 0 && (
<Badge
variant="secondary"
className="ml-auto px-1 py-0 text-xs transition-opacity group-hover:opacity-0"
>
{sessionView.messageCount}
</Badge>
<div className="flex items-center gap-2">
<div className="truncate text-xs font-medium text-foreground">{sessionView.sessionName}</div>
{compactSessionAge && (
<span className="ml-auto flex-shrink-0 text-[11px] text-muted-foreground transition-opacity duration-200 group-hover:opacity-0">
{compactSessionAge}
</span>
)}
<span className="ml-1 opacity-70 transition-opacity group-hover:opacity-0">
<SessionProviderLogo provider={session.__provider} className="h-3 w-3" />
</span>
</div>
<div className="mt-0.5 flex items-center">
{sessionView.messageCount > 0 && <Badge variant="secondary" className="px-1 py-0 text-xs">{sessionView.messageCount}</Badge>}
</div>
</div>
</div>