From 9663f08fcb498bfdb376cd72f714cb66472d1884 Mon Sep 17 00:00:00 2001 From: Haileyesus <118998054+blackmammoth@users.noreply.github.com> Date: Mon, 27 Apr 2026 14:21:03 +0300 Subject: [PATCH] refactor: move rename and delete sessions to modules --- server/index.js | 9 +- server/modules/database/index.ts | 2 +- .../database/repositories/sessions.db.ts | 117 +---------- server/modules/providers/provider.routes.ts | 61 ++++++ .../services/session-synchronizer.service.ts | 197 +----------------- .../providers/services/sessions.service.ts | 52 +++++ server/routes/codex.js | 3 +- server/routes/gemini.js | 3 +- server/services/notification-orchestrator.js | 3 +- .../sidebar/hooks/useSidebarController.ts | 41 +--- src/components/sidebar/types/types.ts | 4 +- src/i18n/config.js | 2 +- src/utils/api.js | 18 +- 13 files changed, 144 insertions(+), 368 deletions(-) diff --git a/server/index.js b/server/index.js index 4ff5e598..6542a9fe 100755 --- a/server/index.js +++ b/server/index.js @@ -306,7 +306,7 @@ app.delete('/api/projects/:projectId/sessions/:sessionId', authenticateToken, as const { projectId, sessionId } = req.params; console.log(`[API] Deleting session: ${sessionId} from project: ${projectId}`); await deleteSessionById(projectId, sessionId); - sessionsDb.deleteName(sessionId, 'claude'); + sessionsDb.deleteSessionById(sessionId); console.log(`[API] Session ${sessionId} deleted successfully`); res.json({ success: true }); } catch (error) { @@ -323,17 +323,14 @@ app.put('/api/sessions/:sessionId/rename', authenticateToken, async (req, res) = if (!safeSessionId || safeSessionId !== String(sessionId)) { return res.status(400).json({ error: 'Invalid sessionId' }); } - const { summary, provider } = req.body; + const { summary } = req.body; if (!summary || typeof summary !== 'string' || summary.trim() === '') { return res.status(400).json({ error: 'Summary is required' }); } if (summary.trim().length > 500) { return res.status(400).json({ error: 'Summary must not exceed 500 characters' }); } - if (!provider || !VALID_PROVIDERS.includes(provider)) { - return res.status(400).json({ error: `Provider must be one of: ${VALID_PROVIDERS.join(', ')}` }); - } - sessionsDb.setName(safeSessionId, provider, summary.trim()); + sessionsDb.updateSessionCustomName(safeSessionId, summary.trim()); res.json({ success: true }); } catch (error) { console.error(`[API] Error renaming session ${req.params.sessionId}:`, error); diff --git a/server/modules/database/index.ts b/server/modules/database/index.ts index 8a11d3ac..787521ed 100644 --- a/server/modules/database/index.ts +++ b/server/modules/database/index.ts @@ -7,6 +7,6 @@ export { notificationPreferencesDb } from '@/modules/database/repositories/notif export { projectsDb } from '@/modules/database/repositories/projects.db.js'; export { pushSubscriptionsDb } from '@/modules/database/repositories/push-subscriptions.js'; export { scanStateDb } from '@/modules/database/repositories/scan-state.db.js'; -export { sessionsDb, applyCustomSessionNames } from '@/modules/database/repositories/sessions.db.js'; +export { sessionsDb } from '@/modules/database/repositories/sessions.db.js'; export { userDb } from '@/modules/database/repositories/users.js'; export { vapidKeysDb } from '@/modules/database/repositories/vapid-keys.js'; diff --git a/server/modules/database/repositories/sessions.db.ts b/server/modules/database/repositories/sessions.db.ts index 41b821bf..991c30c8 100644 --- a/server/modules/database/repositories/sessions.db.ts +++ b/server/modules/database/repositories/sessions.db.ts @@ -3,11 +3,6 @@ import path from 'node:path'; import { getConnection } from '@/modules/database/connection.js'; import { projectsDb } from '@/modules/database/repositories/projects.db.js'; -type SessionNameLookupRow = { - session_id: string; - custom_name: string; -}; - type SessionRow = { session_id: string; provider: string; @@ -23,11 +18,6 @@ type SessionMetadataLookupRow = Pick< 'session_id' | 'provider' | 'project_path' | 'jsonl_path' | 'custom_name' | 'created_at' | 'updated_at' >; -type LegacySessionSummary = { - id: string; - summary?: string; -}; - function normalizeTimestamp(value?: string): string | null { if (!value) return null; @@ -179,111 +169,8 @@ export const sessionsDb = { return row?.custom_name ?? null; }, - getSessionNames(sessionIds: string[], provider: string): Map { - if (sessionIds.length === 0) return new Map(); - + deleteSessionById(sessionId: string): boolean { const db = getConnection(); - const placeholders = sessionIds.map(() => '?').join(','); - const rows = db - .prepare( - `SELECT session_id, custom_name - FROM sessions - WHERE session_id IN (${placeholders}) - AND provider = ? - AND custom_name IS NOT NULL` - ) - .all(...sessionIds, provider) as SessionNameLookupRow[]; - - return new Map(rows.map((row) => [row.session_id, row.custom_name])); - }, - - /** - * Legacy-compatibility method kept for parity with `server/database/db.js`. - * - * Renaming a session is a metadata-only change — it's not actual activity, - * so existing rows intentionally keep their `updated_at` untouched. This - * prevents the sidebar's "last activity" timestamp from jumping around when - * a user simply edits a session's label. - * - * When the row doesn't exist yet we still have to seed `created_at`/ - * `updated_at`; we write ISO-8601 UTC (with the `Z` suffix) rather than - * rely on SQLite's `CURRENT_TIMESTAMP`, which stores a naive - * `"YYYY-MM-DD HH:MM:SS"` value that JavaScript's `new Date(...)` parses as - * local time and displays with the wrong offset. - * - * TODO: Remove after all legacy imports are migrated to the new repository API. - */ - setName(sessionId: string, provider: string, customName: string): void { - const db = getConnection(); - const nowIso = new Date().toISOString(); - db.prepare( - `INSERT INTO sessions (session_id, provider, custom_name, created_at, updated_at) - VALUES (?, ?, ?, ?, ?) - ON CONFLICT(session_id, provider) DO UPDATE SET - custom_name = excluded.custom_name` - ).run(sessionId, provider, customName, nowIso, nowIso); - }, - - /** - * Legacy-compatibility method kept for parity with `server/database/db.js`. - * TODO: Remove after all legacy imports are migrated to the new repository API. - */ - getName(sessionId: string, provider: string): string | null { - return sessionsDb.getSessionName(sessionId, provider); - }, - - /** - * Legacy-compatibility method kept for parity with `server/database/db.js`. - * TODO: Remove after all legacy imports are migrated to the new repository API. - */ - getNames(sessionIds: string[], provider: string): Map { - return sessionsDb.getSessionNames(sessionIds, provider); - }, - - /** - * Legacy-compatibility method kept for parity with `server/database/db.js`. - * TODO: Remove after all legacy imports are migrated to the new repository API. - */ - deleteName(sessionId: string, provider: string): boolean { - const db = getConnection(); - return ( - db - .prepare( - `DELETE FROM sessions - WHERE session_id = ? AND provider = ?` - ) - .run(sessionId, provider).changes > 0 - ); - }, - - deleteSession(sessionId: string): void { - const db = getConnection(); - db.prepare('DELETE FROM sessions WHERE session_id = ?').run(sessionId); + return db.prepare('DELETE FROM sessions WHERE session_id = ?').run(sessionId).changes > 0; }, }; - -/** - * Legacy-compatibility helper kept for parity with `server/database/db.js`. - * TODO: Remove after all legacy imports are migrated to the new repository API. - */ -export function applyCustomSessionNames( - sessions: LegacySessionSummary[] | null | undefined, - provider: string -): void { - if (!sessions?.length) return; - - try { - const sessionIds = sessions.map((session) => session.id); - const customNames = sessionsDb.getNames(sessionIds, provider); - - for (const session of sessions) { - const customName = customNames.get(session.id); - if (customName) { - session.summary = customName; - } - } - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - console.warn(`[DB] Failed to apply custom session names for ${provider}:`, message); - } -} diff --git a/server/modules/providers/provider.routes.ts b/server/modules/providers/provider.routes.ts index 895aba84..0ce8f2de 100644 --- a/server/modules/providers/provider.routes.ts +++ b/server/modules/providers/provider.routes.ts @@ -2,6 +2,7 @@ import express, { type Request, type Response } from 'express'; import { providerAuthService } from '@/modules/providers/services/provider-auth.service.js'; import { providerMcpService } from '@/modules/providers/services/mcp.service.js'; +import { sessionsService } from '@/modules/providers/services/sessions.service.js'; import type { LLMProvider, McpScope, McpTransport, UpsertProviderMcpServerInput } from '@/shared/types.js'; import { AppError, asyncHandler, createApiSuccessResponse } from '@/shared/utils.js'; @@ -25,6 +26,20 @@ const readPathParam = (value: unknown, name: string): string => { const normalizeProviderParam = (value: unknown): string => readPathParam(value, 'provider').trim().toLowerCase(); +const SESSION_ID_PATTERN = /^[a-zA-Z0-9._-]{1,120}$/; + +const parseSessionId = (value: unknown): string => { + const sessionId = readPathParam(value, 'sessionId').trim(); + if (!SESSION_ID_PATTERN.test(sessionId)) { + throw new AppError('Invalid sessionId.', { + code: 'INVALID_SESSION_ID', + statusCode: 400, + }); + } + + return sessionId; +}; + const readOptionalQueryString = (value: unknown): string | undefined => { if (typeof value !== 'string') { return undefined; @@ -143,6 +158,33 @@ const parseProvider = (value: unknown): LLMProvider => { }); }; +const parseSessionRenameSummary = (payload: unknown): string => { + if (!payload || typeof payload !== 'object') { + throw new AppError('Request body must be an object.', { + code: 'INVALID_REQUEST_BODY', + statusCode: 400, + }); + } + + const body = payload as Record; + const summary = typeof body.summary === 'string' ? body.summary.trim() : ''; + if (!summary) { + throw new AppError('Summary is required.', { + code: 'INVALID_SESSION_SUMMARY', + statusCode: 400, + }); + } + + if (summary.length > 500) { + throw new AppError('Summary must not exceed 500 characters.', { + code: 'INVALID_SESSION_SUMMARY', + statusCode: 400, + }); + } + + return summary; +}; + router.get( '/:provider/auth/status', asyncHandler(async (req: Request, res: Response) => { @@ -214,4 +256,23 @@ router.post( }), ); +router.delete( + '/sessions/:sessionId', + asyncHandler(async (req: Request, res: Response) => { + const sessionId = parseSessionId(req.params.sessionId); + const result = await sessionsService.deleteSessionById(sessionId); + res.json(createApiSuccessResponse(result)); + }), +); + +router.put( + '/sessions/:sessionId', + asyncHandler(async (req: Request, res: Response) => { + const sessionId = parseSessionId(req.params.sessionId); + const summary = parseSessionRenameSummary(req.body); + const result = sessionsService.renameSessionById(sessionId, summary); + res.json(createApiSuccessResponse(result)); + }), +); + export default router; diff --git a/server/modules/providers/services/session-synchronizer.service.ts b/server/modules/providers/services/session-synchronizer.service.ts index d0279a86..bb37aaa0 100644 --- a/server/modules/providers/services/session-synchronizer.service.ts +++ b/server/modules/providers/services/session-synchronizer.service.ts @@ -1,129 +1,16 @@ -import path from 'node:path'; -import fsp, { readFile } from 'node:fs/promises'; - -import { scanStateDb, sessionsDb, projectsDb } from '@/modules/database/index.js'; +import { scanStateDb } from '@/modules/database/index.js'; import { providerRegistry } from '@/modules/providers/provider.registry.js'; -import { sessionsService } from '@/modules/providers/services/sessions.service.js'; -import type { LLMProvider, NormalizedMessage } from '@/shared/types.js'; -import { AppError } from '@/shared/utils.js'; +import type { LLMProvider } from '@/shared/types.js'; type SessionSynchronizeResult = { processedByProvider: Record; failures: string[]; }; -type SessionHistoryPayload = { - sessionId: string; - provider: string; - projectPath: string | null; - filePath: string; - fileType: 'jsonl' | 'json'; - entries: unknown[]; - messages: NormalizedMessage[]; -}; - -const SESSION_ID_PATTERN = /^[a-zA-Z0-9._-]{1,120}$/; - -/** - * Restricts session ids before they are used in DB and filesystem operations. - */ -function sanitizeSessionId(sessionId: string): string { - const value = String(sessionId).trim(); - if (!SESSION_ID_PATTERN.test(value)) { - throw new AppError('Invalid session id format.', { - code: 'INVALID_SESSION_ID', - statusCode: 400, - }); - } - return value; -} - -/** - * Removes one file if it exists. - */ -async function removeFileIfExists(filePath: string): Promise { - try { - await fsp.unlink(filePath); - return true; - } catch (error) { - const code = (error as NodeJS.ErrnoException).code; - if (code === 'ENOENT') { - return false; - } - throw error; - } -} - -/** - * Parses newline-delimited JSON and preserves malformed lines as raw entries. - */ -function parseJsonl(content: string): unknown[] { - const entries: unknown[] = []; - const lines = content.split(/\r?\n/); - - for (const line of lines) { - const trimmed = line.trim(); - if (!trimmed) { - continue; - } - - try { - entries.push(JSON.parse(trimmed)); - } catch { - entries.push({ raw: trimmed, parseError: true }); - } - } - - return entries; -} - -/** - * Parses JSON and normalizes object payloads into a single-element array. - */ -function parseJson(content: string): unknown[] { - try { - const parsed = JSON.parse(content) as unknown; - return Array.isArray(parsed) ? parsed : [parsed]; - } catch { - return [{ raw: content, parseError: true }]; - } -} - /** * Orchestrates provider-specific session indexers and indexed-session lifecycle operations. */ export const sessionSynchronizerService = { - /** - * Lists indexed sessions from DB, optionally scoped to one provider. - */ - listIndexedSessions(provider?: string) { - const allSessions = sessionsDb.getAllSessions(); - if (!provider) { - return allSessions; - } - - return allSessions.filter((session) => session.provider === provider); - }, - - /** - * Reads one indexed session row and enriches it with the associated project id. - */ - getIndexedSession(sessionId: string) { - const session = sessionsDb.getSessionById(sessionId); - if (!session) { - throw new AppError(`Session "${sessionId}" was not found.`, { - code: 'SESSION_NOT_FOUND', - statusCode: 404, - }); - } - - const project = session.project_path ? projectsDb.getProjectPath(session.project_path) : null; - return { - ...session, - project_id: project?.project_id ?? null, - }; - }, - /** * Runs all provider synchronizers and updates scan_state.last_scanned_at. */ @@ -177,84 +64,4 @@ export const sessionSynchronizerService = { sessionId, }; }, - - /** - * Updates one indexed session custom name after validating existence. - */ - updateSessionCustomName(sessionId: string, sessionCustomName: string): void { - const sessionMetadata = sessionsDb.getSessionById(sessionId); - if (!sessionMetadata) { - throw new AppError('Session not found.', { - code: 'SESSION_NOT_FOUND', - statusCode: 404, - }); - } - - sessionsDb.updateSessionCustomName(sessionId, sessionCustomName); - }, - - /** - * Deletes a session artifact path from disk (if present) and deletes DB metadata. - */ - async deleteSessionArtifacts(rawSessionId: string): Promise<{ - sessionId: string; - deletedFromDisk: boolean; - deletedFromDatabase: boolean; - }> { - const sessionId = sanitizeSessionId(rawSessionId); - const existingSession = sessionsDb.getSessionById(sessionId); - const sessionFilePath = existingSession?.jsonl_path ?? null; - const deletedFromDisk = sessionFilePath ? await removeFileIfExists(sessionFilePath) : false; - - if (existingSession) { - sessionsDb.deleteSession(sessionId); - } - - return { - sessionId, - deletedFromDisk, - deletedFromDatabase: Boolean(existingSession), - }; - }, - - /** - * Reads indexed session history directly from session json path and normalizes entries. - */ - async getSessionHistory(sessionId: string): Promise { - const session = sessionsDb.getSessionById(sessionId); - if (!session) { - throw new AppError(`Session "${sessionId}" was not found.`, { - code: 'SESSION_NOT_FOUND', - statusCode: 404, - }); - } - - if (!session.jsonl_path) { - throw new AppError(`Session "${sessionId}" does not have a history file path.`, { - code: 'SESSION_HISTORY_NOT_AVAILABLE', - statusCode: 404, - }); - } - - const filePath = session.jsonl_path; - const fileContent = await readFile(filePath, 'utf8'); - const extension = path.extname(filePath).toLowerCase(); - const isGeminiJson = session.provider === 'gemini' || extension === '.json'; - const entries = isGeminiJson ? parseJson(fileContent) : parseJsonl(fileContent); - - const messages: NormalizedMessage[] = []; - for (const entry of entries) { - messages.push(...sessionsService.normalizeMessage(session.provider, entry, session.session_id)); - } - - return { - sessionId: session.session_id, - provider: session.provider, - projectPath: session.project_path, - filePath, - fileType: isGeminiJson ? 'json' : 'jsonl', - entries, - messages, - }; - }, }; diff --git a/server/modules/providers/services/sessions.service.ts b/server/modules/providers/services/sessions.service.ts index adff6e8f..1afa1677 100644 --- a/server/modules/providers/services/sessions.service.ts +++ b/server/modules/providers/services/sessions.service.ts @@ -1,3 +1,6 @@ +import fsp from 'node:fs/promises'; + +import { sessionsDb } from '@/modules/database/index.js'; import { providerRegistry } from '@/modules/providers/provider.registry.js'; import type { FetchHistoryOptions, @@ -5,6 +8,24 @@ import type { LLMProvider, NormalizedMessage, } from '@/shared/types.js'; +import { AppError } from '@/shared/utils.js'; + + +/** + * Removes one file if it exists. + */ +async function removeFileIfExists(filePath: string): Promise { + try { + await fsp.unlink(filePath); + return true; + } catch (error) { + const code = (error as NodeJS.ErrnoException).code; + if (code === 'ENOENT') { + return false; + } + throw error; + } +} /** * Application service for provider-backed session message operations. @@ -42,4 +63,35 @@ export const sessionsService = { ): Promise { return providerRegistry.resolveProvider(providerName).sessions.fetchHistory(sessionId, options); }, + + /** + * Deletes one persisted session row by id. + */ + deleteSessionById(sessionId: string): { sessionId: string } { + const deleted = sessionsDb.deleteSessionById(sessionId); + if (!deleted) { + throw new AppError(`Session "${sessionId}" was not found.`, { + code: 'SESSION_NOT_FOUND', + statusCode: 404, + }); + } + + return { sessionId }; + }, + + /** + * Renames one session by id without requiring the caller to pass provider. + */ + renameSessionById(sessionId: string, summary: string): { sessionId: string; summary: string } { + const session = sessionsDb.getSessionById(sessionId); + if (!session) { + throw new AppError(`Session "${sessionId}" was not found.`, { + code: 'SESSION_NOT_FOUND', + statusCode: 404, + }); + } + + sessionsDb.updateSessionCustomName(sessionId, summary); + return { sessionId, summary }; + }, }; diff --git a/server/routes/codex.js b/server/routes/codex.js index 1bf26990..5a6ebf64 100644 --- a/server/routes/codex.js +++ b/server/routes/codex.js @@ -1,4 +1,5 @@ import express from 'express'; + import { deleteCodexSession } from '../projects.js'; import { sessionsDb } from '../modules/database/index.js'; @@ -8,7 +9,7 @@ router.delete('/sessions/:sessionId', async (req, res) => { try { const { sessionId } = req.params; await deleteCodexSession(sessionId); - sessionsDb.deleteName(sessionId, 'codex'); + sessionsDb.deleteSessionById(sessionId); res.json({ success: true }); } catch (error) { console.error(`Error deleting Codex session ${req.params.sessionId}:`, error); diff --git a/server/routes/gemini.js b/server/routes/gemini.js index 81b11100..341365b4 100644 --- a/server/routes/gemini.js +++ b/server/routes/gemini.js @@ -1,4 +1,5 @@ import express from 'express'; + import sessionManager from '../sessionManager.js'; import { sessionsDb } from '../modules/database/index.js'; @@ -13,7 +14,7 @@ router.delete('/sessions/:sessionId', async (req, res) => { } await sessionManager.deleteSession(sessionId); - sessionsDb.deleteName(sessionId, 'gemini'); + sessionsDb.deleteSessionById(sessionId); res.json({ success: true }); } catch (error) { console.error(`Error deleting Gemini session ${req.params.sessionId}:`, error); diff --git a/server/services/notification-orchestrator.js b/server/services/notification-orchestrator.js index e5ab39cd..43a7d058 100644 --- a/server/services/notification-orchestrator.js +++ b/server/services/notification-orchestrator.js @@ -1,4 +1,5 @@ import webPush from 'web-push'; + import { notificationPreferencesDb, pushSubscriptionsDb, sessionsDb } from '../modules/database/index.js'; const KIND_TO_PREF_KEY = { @@ -107,7 +108,7 @@ function resolveSessionName(event) { return null; } - return normalizeSessionName(sessionsDb.getName(event.sessionId, event.provider)); + return normalizeSessionName(sessionsDb.getSessionName(event.sessionId, event.provider)); } function buildPushBody(event) { diff --git a/src/components/sidebar/hooks/useSidebarController.ts b/src/components/sidebar/hooks/useSidebarController.ts index 586b61db..134df91d 100644 --- a/src/components/sidebar/hooks/useSidebarController.ts +++ b/src/components/sidebar/hooks/useSidebarController.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import type React from 'react'; import type { TFunction } from 'i18next'; + import { api } from '../../../utils/api'; import type { Project, ProjectSession, LLMProvider } from '../../../types/app'; import type { @@ -340,21 +340,6 @@ export function useSidebarController({ }; }, [searchFilter, searchMode]); - const handleTouchClick = useCallback( - (callback: () => void) => - (event: React.TouchEvent) => { - const target = event.target as HTMLElement; - if (target.closest('.overflow-y-auto') || target.closest('[data-scroll-container]')) { - return; - } - - event.preventDefault(); - event.stopPropagation(); - callback(); - }, - [], - ); - // All sidebar state keys (expanded, starred, loading, etc.) use the DB // `projectId` as their identifier after the migration. const toggleProject = useCallback((projectId: string) => { @@ -522,8 +507,8 @@ export function useSidebarController({ ); const showDeleteSessionConfirmation = useCallback( - // `projectId` (not the legacy folder-encoded name) is what the DELETE - // /api/projects/:projectId/sessions/:sessionId endpoint expects. + // Kept with project/provider arguments for component wiring compatibility; + // deletion now uses only `sessionId` via /api/providers/sessions/:sessionId. ( projectId: string, sessionId: string, @@ -540,19 +525,11 @@ export function useSidebarController({ return; } - const { projectId, sessionId, provider } = sessionDeleteConfirmation; + const { sessionId } = sessionDeleteConfirmation; setSessionDeleteConfirmation(null); try { - let response; - if (provider === 'codex') { - response = await api.deleteCodexSession(sessionId); - } else if (provider === 'gemini') { - response = await api.deleteGeminiSession(sessionId); - } else { - // Claude sessions are owned by the DB project row; pass projectId. - response = await api.deleteSession(projectId, sessionId); - } + const response = await api.deleteSession(sessionId); if (response.ok) { onSessionDelete?.(sessionId); @@ -634,9 +611,9 @@ export function useSidebarController({ }, [onRefresh]); const updateSessionSummary = useCallback( - // `_projectId` is unused by the rename endpoint but preserved in the - // callback signature so existing wiring from sidebar components works. - async (_projectId: string, sessionId: string, summary: string, provider: LLMProvider) => { + // `_projectId` and `_provider` are preserved for compatibility with + // existing sidebar callback signatures; backend rename only needs sessionId. + async (_projectId: string, sessionId: string, summary: string, _provider: LLMProvider) => { const trimmed = summary.trim(); if (!trimmed) { setEditingSession(null); @@ -644,7 +621,7 @@ export function useSidebarController({ return; } try { - const response = await api.renameSession(sessionId, trimmed, provider); + const response = await api.renameSession(sessionId, trimmed); if (response.ok) { await onRefresh(); } else { diff --git a/src/components/sidebar/types/types.ts b/src/components/sidebar/types/types.ts index e29e6df3..0ef6da51 100644 --- a/src/components/sidebar/types/types.ts +++ b/src/components/sidebar/types/types.ts @@ -11,8 +11,8 @@ export type DeleteProjectConfirmation = { sessionCount: number; }; -// Delete confirmation payload; `projectId` is the DB primary key used by the -// DELETE /api/projects/:projectId/sessions/:sessionId endpoint. +// Delete confirmation payload used by sidebar UX. `projectId`/`provider` are +// kept for wiring compatibility, while API deletion now keys only by sessionId. export type SessionDeleteConfirmation = { projectId: string; sessionId: string; diff --git a/src/i18n/config.js b/src/i18n/config.js index 16a9330e..7fcda777 100644 --- a/src/i18n/config.js +++ b/src/i18n/config.js @@ -187,7 +187,7 @@ i18n fallbackLng: 'en', // Enable debug mode in development (logs missing keys to console) - debug: import.meta.env.DEV, + debug: false, // Namespaces - load only what's needed ns: ['common', 'settings', 'auth', 'sidebar', 'chat', 'codeEditor', 'tasks'], diff --git a/src/utils/api.js b/src/utils/api.js index ebefc01a..bd04f26f 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -76,22 +76,14 @@ export const api = { method: 'PUT', body: JSON.stringify({ displayName }), }), - deleteSession: (projectId, sessionId) => - authenticatedFetch(`/api/projects/${projectId}/sessions/${sessionId}`, { + deleteSession: (sessionId) => + authenticatedFetch(`/api/providers/sessions/${sessionId}`, { method: 'DELETE', }), - renameSession: (sessionId, summary, provider) => - authenticatedFetch(`/api/sessions/${sessionId}/rename`, { + renameSession: (sessionId, summary) => + authenticatedFetch(`/api/providers/sessions/${sessionId}`, { method: 'PUT', - body: JSON.stringify({ summary, provider }), - }), - deleteCodexSession: (sessionId) => - authenticatedFetch(`/api/codex/sessions/${sessionId}`, { - method: 'DELETE', - }), - deleteGeminiSession: (sessionId) => - authenticatedFetch(`/api/gemini/sessions/${sessionId}`, { - method: 'DELETE', + body: JSON.stringify({ summary }), }), // `hardDelete` => server `?force=true` (remove DB row + Claude *.jsonl + sessions rows for path). deleteProject: (projectId, hardDelete = false) => {