Skip to content
Merged

Dev #17

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/daemon/timeline-projection-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const dbPath = typeof workerData?.dbPath === 'string' && workerData.dbPath

let db: DatabaseSyncInstance | null = null;
const rebuildPromises = new Map<string, Promise<boolean>>();
const sessionMutationGenerations = new Map<string, number>();
let writesSinceCheckpoint = 0;

function sessionFilePath(sessionId: string): string {
Expand Down Expand Up @@ -145,6 +146,7 @@ function upsertSessionMeta(sessionId: string, meta: {
}

function deleteSessionRows(sessionId: string): void {
sessionMutationGenerations.set(sessionId, (sessionMutationGenerations.get(sessionId) ?? 0) + 1);
const database = ensureDb();
database.prepare('DELETE FROM timeline_projection_events WHERE session_id = ?').run(sessionId);
database.prepare('DELETE FROM timeline_projection_sessions WHERE session_id = ?').run(sessionId);
Expand Down Expand Up @@ -289,7 +291,9 @@ async function rebuildSessionInternal(sessionId: string): Promise<boolean> {
}

function scheduleSessionRebuild(sessionId: string): void {
const scheduledGeneration = sessionMutationGenerations.get(sessionId) ?? 0;
setImmediate(() => {
if ((sessionMutationGenerations.get(sessionId) ?? 0) !== scheduledGeneration) return;
void rebuildSessionInternal(sessionId).catch(() => {
// Query paths must stay fast and fail-open; an explicit rebuild request or
// the next append/query can retry projection repair.
Expand Down
25 changes: 25 additions & 0 deletions test/daemon/timeline-projection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,31 @@ describe('timeline projection', () => {
expect(explicitlyRebuilt?.map((event) => event.seq)).toEqual([2, 3]);
});

it('does not let a stale scheduled rebuild repopulate after delete', async () => {
const { timelineProjection, timelineStore } = await loadModules();
const sessionId = 'projection_delete_cancels_scheduled_rebuild';
const timelineFile = timelineStore.filePath(sessionId);
mkdirSync(join(tempHome!, '.imcodes', 'timeline'), { recursive: true });

timelineStore.append(makeEvent(sessionId, 1, 'assistant.text', { text: 'one' }, 1000));
timelineStore.append(makeEvent(sessionId, 2, 'assistant.text', { text: 'two' }, 1001));
await timelineProjection.rebuildSession(sessionId);

appendFileSync(timelineFile, `${JSON.stringify(makeEvent(sessionId, 3, 'assistant.text', { text: 'three' }, 1002))}\n`);
const stale = await timelineProjection.queryHistory({ sessionId, limit: 10 });
expect(stale?.map((event) => event.seq)).toEqual([1, 2]);

await timelineProjection.deleteSession(sessionId);
await sleep(100);

const afterDelete = await timelineProjection.queryHistory({ sessionId, limit: 10 });
expect(afterDelete?.map((event) => event.seq)).toEqual([]);

await timelineProjection.rebuildSession(sessionId);
const explicitlyRebuilt = await timelineProjection.queryHistory({ sessionId, limit: 10 });
expect(explicitlyRebuilt?.map((event) => event.seq)).toEqual([1, 2, 3]);
});

it('does not parse appended JSONL tails on the read path', async () => {
const { timelineProjection, timelineStore } = await loadModules();
const sessionId = 'projection_incremental_tail';
Expand Down
1 change: 0 additions & 1 deletion web/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3477,7 +3477,6 @@ export function App() {
<div
ref={desktopWorkspaceBoundsRef}
data-testid="desktop-workspace-bounds"
class={desktopLayoutCapable && subSessionBarCollapsed ? 'desktop-workspace-maximized' : undefined}
style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', position: 'relative', overflow: 'hidden' }}
>
{/* Desktop local preview shortcut — available even before a session is active */}
Expand Down
1 change: 0 additions & 1 deletion web/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1003,7 +1003,6 @@ body {
.desktop-view-toggle { display: flex; justify-content: flex-end; padding: 2px 8px; background: #0f172a; gap: 4px; }
.desktop-view-toggle .view-toggle { font-size: 12px; padding: 2px 8px; }
.desktop-view-toggle .desktop-main-maximize-toggle { display: inline-flex; align-items: center; justify-content: center; min-width: 34px; }
.desktop-workspace-maximized { box-shadow: inset 0 0 0 2px #3b82f6, inset 0 0 0 3px rgba(96,165,250,0.35); }
.desktop-fb-float { position: fixed; z-index: 1500; display: flex; flex-direction: column; background: #0f172a; border: 1px solid #334155; border-radius: 8px; box-shadow: 0 8px 32px rgba(0,0,0,0.5); overflow: hidden; }
.desktop-fb-titlebar { display: flex; align-items: center; justify-content: space-between; padding: 6px 10px; border-bottom: 1px solid #334155; cursor: grab; flex-shrink: 0; user-select: none; }
.desktop-fb-titlebar:active { cursor: grabbing; }
Expand Down
Loading