mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-15 03:01:58 +08:00
fix(sessions): canonicalize sidebar ids and timestamps
The sidebar could keep a provider-native id after backend remapping. That left a duplicate non-working session visible until refresh. Fresh sessions could also appear hours old. SQLite CURRENT_TIMESTAMP is UTC without a timezone suffix. Browser parsing then treated those values like local time. Broadcast a canonical session_upserted event when the provider id is mapped. Collapse provider-id aliases onto the stable app session id in the client. Normalize session-row timestamps to ISO UTC when reading from the repository.
This commit is contained in:
@@ -17,10 +17,19 @@ type SessionRow = {
|
||||
const SESSION_ROW_COLUMNS =
|
||||
'session_id, provider, provider_session_id, project_path, jsonl_path, custom_name, isArchived, created_at, updated_at';
|
||||
|
||||
const SQLITE_UTC_TIMESTAMP_REGEX = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
|
||||
|
||||
function normalizeTimestamp(value?: string): string | null {
|
||||
if (!value) return null;
|
||||
|
||||
const parsed = new Date(value);
|
||||
// SQLite CURRENT_TIMESTAMP is stored as UTC without a timezone suffix.
|
||||
// Normalize it here so every session reader returns canonical ISO strings
|
||||
// and the sidebar never interprets fresh rows as local-time "hours old".
|
||||
const normalizedValue = SQLITE_UTC_TIMESTAMP_REGEX.test(value)
|
||||
? `${value.replace(' ', 'T')}Z`
|
||||
: value;
|
||||
|
||||
const parsed = new Date(normalizedValue);
|
||||
if (Number.isNaN(parsed.getTime())) {
|
||||
return null;
|
||||
}
|
||||
@@ -28,6 +37,22 @@ function normalizeTimestamp(value?: string): string | null {
|
||||
return parsed.toISOString();
|
||||
}
|
||||
|
||||
function normalizeSessionRow<T extends SessionRow | null | undefined>(row: T): T {
|
||||
if (!row) {
|
||||
return row;
|
||||
}
|
||||
|
||||
return {
|
||||
...row,
|
||||
created_at: normalizeTimestamp(row.created_at) ?? row.created_at,
|
||||
updated_at: normalizeTimestamp(row.updated_at) ?? row.updated_at,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSessionRows(rows: SessionRow[]): SessionRow[] {
|
||||
return rows.map((row) => normalizeSessionRow(row) as SessionRow);
|
||||
}
|
||||
|
||||
function normalizeProjectPathForProvider(provider: string, projectPath: string): string {
|
||||
void provider;
|
||||
return normalizeProjectPath(projectPath);
|
||||
@@ -207,7 +232,7 @@ export const sessionsDb = {
|
||||
)
|
||||
.get(sessionId) as SessionRow | undefined;
|
||||
|
||||
return row ?? null;
|
||||
return normalizeSessionRow(row) ?? null;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -229,18 +254,20 @@ export const sessionsDb = {
|
||||
)
|
||||
.get(providerSessionId) as SessionRow | undefined;
|
||||
|
||||
return row ?? null;
|
||||
return normalizeSessionRow(row) ?? null;
|
||||
},
|
||||
|
||||
getAllSessions(): SessionRow[] {
|
||||
const db = getConnection();
|
||||
return db
|
||||
const rows = db
|
||||
.prepare(
|
||||
`SELECT ${SESSION_ROW_COLUMNS}
|
||||
FROM sessions
|
||||
WHERE isArchived = 0`
|
||||
)
|
||||
.all() as SessionRow[];
|
||||
|
||||
return normalizeSessionRows(rows);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -249,7 +276,7 @@ export const sessionsDb = {
|
||||
*/
|
||||
getArchivedSessions(): SessionRow[] {
|
||||
const db = getConnection();
|
||||
return db
|
||||
const rows = db
|
||||
.prepare(
|
||||
`SELECT ${SESSION_ROW_COLUMNS}
|
||||
FROM sessions
|
||||
@@ -257,12 +284,14 @@ export const sessionsDb = {
|
||||
ORDER BY datetime(COALESCE(updated_at, created_at)) DESC, session_id DESC`
|
||||
)
|
||||
.all() as SessionRow[];
|
||||
|
||||
return normalizeSessionRows(rows);
|
||||
},
|
||||
|
||||
getSessionsByProjectPath(projectPath: string): SessionRow[] {
|
||||
const db = getConnection();
|
||||
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
||||
return db
|
||||
const rows = db
|
||||
.prepare(
|
||||
`SELECT ${SESSION_ROW_COLUMNS}
|
||||
FROM sessions
|
||||
@@ -270,6 +299,8 @@ export const sessionsDb = {
|
||||
AND isArchived = 0`
|
||||
)
|
||||
.all(normalizedProjectPath) as SessionRow[];
|
||||
|
||||
return normalizeSessionRows(rows);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -279,19 +310,21 @@ export const sessionsDb = {
|
||||
getSessionsByProjectPathIncludingArchived(projectPath: string): SessionRow[] {
|
||||
const db = getConnection();
|
||||
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
||||
return db
|
||||
const rows = db
|
||||
.prepare(
|
||||
`SELECT ${SESSION_ROW_COLUMNS}
|
||||
FROM sessions
|
||||
WHERE project_path = ?`
|
||||
)
|
||||
.all(normalizedProjectPath) as SessionRow[];
|
||||
|
||||
return normalizeSessionRows(rows);
|
||||
},
|
||||
|
||||
getSessionsByProjectPathPage(projectPath: string, limit: number, offset: number): SessionRow[] {
|
||||
const db = getConnection();
|
||||
const normalizedProjectPath = normalizeProjectPath(projectPath);
|
||||
return db
|
||||
const rows = db
|
||||
.prepare(
|
||||
`SELECT ${SESSION_ROW_COLUMNS}
|
||||
FROM sessions
|
||||
@@ -301,6 +334,8 @@ export const sessionsDb = {
|
||||
LIMIT ? OFFSET ?`
|
||||
)
|
||||
.all(normalizedProjectPath, limit, offset) as SessionRow[];
|
||||
|
||||
return normalizeSessionRows(rows);
|
||||
},
|
||||
|
||||
countSessionsByProjectPath(projectPath: string): number {
|
||||
|
||||
Reference in New Issue
Block a user