diff --git a/src/routes/api/sessions.ts b/src/routes/api/sessions.ts index b0ee0e8ec..a06cf9817 100644 --- a/src/routes/api/sessions.ts +++ b/src/routes/api/sessions.ts @@ -21,6 +21,37 @@ import { updateLocalSessionTitle, } from '../../server/local-session-store' +const WORKSPACE_CONTEXT_PREFIX_RE = /^\s*>(session: T): T { + const cleanLabel = cleanSessionDisplayText(session.label) + const cleanTitle = cleanSessionDisplayText(session.title) + const cleanDerivedTitle = cleanSessionDisplayText(session.derivedTitle) + const cleanPreview = cleanSessionDisplayText(session.preview) + + const fallbackId = + cleanSessionDisplayText(session.friendlyId) || + cleanSessionDisplayText(session.key) || + cleanSessionDisplayText(session.id) || + 'Session' + + return { + ...session, + label: cleanLabel, + title: cleanTitle, + derivedTitle: cleanDerivedTitle || cleanTitle || cleanLabel || fallbackId, + preview: cleanPreview ?? null, + } +} + export const Route = createFileRoute('/api/sessions')({ server: { handlers: { @@ -41,7 +72,7 @@ export const Route = createFileRoute('/api/sessions')({ try { const sessions = await listSessions(50, 0) - const gatewaySessions = sessions.map(toSessionSummary) + const gatewaySessions = sessions.map(toSessionSummary).map(cleanGatewaySessionSummary) // Merge local portable sessions (Ollama, Atomic Chat, etc.) const localSessions = listLocalSessions() @@ -141,7 +172,7 @@ export const Route = createFileRoute('/api/sessions')({ ok: true, sessionKey: session.id, friendlyId: session.id, - entry: toSessionSummary(session), + entry: cleanGatewaySessionSummary(toSessionSummary(session)), modelApplied: true, }) } catch (err) { @@ -248,7 +279,7 @@ export const Route = createFileRoute('/api/sessions')({ return json({ ok: true, sessionKey, - entry: toSessionSummary(session), + entry: cleanGatewaySessionSummary(toSessionSummary(session)), }) } catch (err) { return json( diff --git a/src/screens/chat/session-title-store.ts b/src/screens/chat/session-title-store.ts index dc0696fc3..fd8015cbf 100644 --- a/src/screens/chat/session-title-store.ts +++ b/src/screens/chat/session-title-store.ts @@ -27,6 +27,23 @@ export type SessionTitleInfo = { const STORAGE_KEY = 'claude.sessionTitles.v1' +const WORKSPACE_CONTEXT_TITLE_REGEX = + /^\s*\s*/i + +function cleanStoredTitleCandidate(value: unknown): string | undefined { + if (typeof value !== 'string') return undefined + const trimmed = value.trim() + if (!trimmed) return undefined + + if (trimmed.includes(' = {} const runtimeStates = new Map() const listeners = new Set<() => void>() @@ -49,11 +66,9 @@ function ensureLoaded() { const normalized: PersistedTitle = {} // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime safety if (value && typeof value === 'object') { - if ( - typeof value.title === 'string' && - value.title.trim().length > 0 - ) { - normalized.title = value.title.trim() + const cleanTitle = cleanStoredTitleCandidate(value.title) + if (cleanTitle) { + normalized.title = cleanTitle } if (value.source === 'auto' || value.source === 'manual') { normalized.source = value.source @@ -167,7 +182,7 @@ export function updateSessionTitleState( const nextRuntime: RuntimeState = { ...prevRuntime } if ('title' in patch) { - const nextTitle = patch.title?.trim() ?? '' + const nextTitle = cleanStoredTitleCandidate(patch.title) ?? '' if (nextTitle.length > 0) { nextPersisted = { ...nextPersisted, diff --git a/src/screens/chat/utils.ts b/src/screens/chat/utils.ts index a329003c5..c346984d4 100644 --- a/src/screens/chat/utils.ts +++ b/src/screens/chat/utils.ts @@ -183,6 +183,20 @@ export function getMessageTimestamp(message: ChatMessage): number { return Date.now() } +function cleanTitleCandidate(raw: string): string | undefined { + const trimmed = raw.trim() + if (!trimmed) return undefined + + if (trimmed.includes(' 0 - ? cleanUserText(session.title.trim()) || session.title.trim() + typeof session.title === 'string' + ? cleanTitleCandidate(session.title) : undefined const derivedTitle = - typeof session.derivedTitle === 'string' && - session.derivedTitle.trim().length > 0 - ? cleanUserText(session.derivedTitle.trim()) || session.derivedTitle.trim() - : typeof session.preview === 'string' && - session.preview.trim().length > 0 - ? cleanUserText(session.preview.trim()) || session.preview.trim() + typeof session.derivedTitle === 'string' + ? cleanTitleCandidate(session.derivedTitle) + : typeof session.preview === 'string' + ? cleanTitleCandidate(session.preview) : undefined const titleStatus = deriveTitleStatus( label, @@ -264,7 +276,7 @@ export function normalizeSessions( titleError: session.titleError ?? null, preview: typeof session.preview === 'string' - ? cleanUserText(session.preview) || session.preview.trim() || null + ? cleanTitleCandidate(session.preview) ?? null : session.preview ?? null, } })