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

@@ -5,7 +5,6 @@ import { projectsDb, sessionsDb } from '@/modules/database/index.js';
import { sessionSynchronizerService } from '@/modules/providers/index.js';
import { WS_OPEN_STATE, connectedClients } from '@/modules/websocket/index.js';
import type { RealtimeClientConnection } from '@/shared/types.js';
import { findAppRoot, getModuleDir } from '@/utils/runtime-paths.js';
type SessionSummary = {
id: string;
@@ -32,12 +31,6 @@ export type ProjectListItem = {
};
};
export type ProjectsSnapshot = {
generatedAt: string;
projectCount: number;
projects: ProjectListItem[];
};
type ProgressUpdate = {
phase: 'loading' | 'complete';
current: number;
@@ -49,12 +42,6 @@ type GetProjectsWithSessionsOptions = {
skipSynchronization?: boolean;
};
const __dirname = getModuleDir(import.meta.url);
const APP_ROOT = findAppRoot(__dirname);
const PROJECTS_DUMP_DIR = path.join(APP_ROOT, '.tmp', 'project-dumps');
let projectsSnapshotCounter: number | null = null;
/**
* Generate better display name from path.
*/
@@ -126,62 +113,6 @@ function buildSessionsByProviderFromDb(projectPath: string): SessionsByProvider
return byProvider;
}
async function getNextProjectsSnapshotPath(): Promise<string> {
await fs.mkdir(PROJECTS_DUMP_DIR, { recursive: true });
if (projectsSnapshotCounter === null) {
const entries = await fs.readdir(PROJECTS_DUMP_DIR).catch(() => []);
projectsSnapshotCounter = entries.reduce((max, entry) => {
const match = entry.match(/^projects-(\d+)\.json$/);
if (!match) {
return max;
}
return Math.max(max, Number(match[1]));
}, 0);
}
projectsSnapshotCounter += 1;
const suffix = String(projectsSnapshotCounter).padStart(4, '0');
return path.join(PROJECTS_DUMP_DIR, `projects-${suffix}.json`);
}
/**
* Builds a typed snapshot payload for project dumps.
*/
export function createProjectsSnapshot(projects: ProjectListItem[]): ProjectsSnapshot {
return {
generatedAt: new Date().toISOString(),
projectCount: projects.length,
projects,
};
}
/**
* Writes a projects snapshot file as an incrementing artifact.
*/
export async function writeSnapshot(projects: ProjectListItem[]): Promise<void> {
try {
const snapshot = createProjectsSnapshot(projects);
const snapshotJson = JSON.stringify(snapshot, (_, value) => (typeof value === 'bigint' ? value.toString() : value), 2);
while (true) {
const snapshotPath = await getNextProjectsSnapshotPath();
try {
await fs.writeFile(snapshotPath, snapshotJson, { encoding: 'utf8', flag: 'wx' });
break;
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'EEXIST') {
continue;
}
throw error;
}
}
} catch (error) {
console.warn('Could not write projects snapshot:', (error as Error).message);
}
}
// Broadcast progress to all connected WebSocket clients
function broadcastProgress(progress: ProgressUpdate) {
const message = JSON.stringify({
@@ -261,6 +192,5 @@ export async function getProjectsWithSessions(
total: totalProjects,
});
await writeSnapshot(projects);
return projects;
}