Skip to content

Commit e2266fa

Browse files
nmsncursoragent
andcommitted
fix(web): clear Design Browser caches when deleting a project
Successful DELETE already pruned tab state; also remove the per-project history and viewport localStorage keys so browsing data does not linger. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 517f39a commit e2266fa

2 files changed

Lines changed: 33 additions & 8 deletions

File tree

apps/web/src/state/projects.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -369,10 +369,12 @@ export async function deleteProject(id: string): Promise<boolean> {
369369
const resp = await fetch(`/api/projects/${encodeURIComponent(id)}`, {
370370
method: 'DELETE',
371371
});
372-
// Drop the project's local tab-state cache once it is gone server-side, so
373-
// the `open-design:project-tabs:*` keys don't accumulate in localStorage
374-
// for the lifetime of the browser profile as projects are deleted.
375-
if (resp.ok) removeCachedTabs(id);
372+
// Drop per-project browser caches once the project is gone server-side so
373+
// they do not accumulate in localStorage for the lifetime of the profile.
374+
if (resp.ok) {
375+
removeCachedTabs(id);
376+
removeDesignBrowserProjectCache(id);
377+
}
376378
return resp.ok;
377379
} catch {
378380
return false;
@@ -682,6 +684,18 @@ function removeCachedTabs(projectId: string): void {
682684
}
683685
}
684686

687+
// Keep key shapes in sync with DesignBrowserPanel historyStorageKey /
688+
// viewportStorageKey. Cleared here so deleteProject does not import the panel.
689+
function removeDesignBrowserProjectCache(projectId: string): void {
690+
if (typeof window === 'undefined') return;
691+
try {
692+
window.localStorage.removeItem(`od:design-browser:${projectId}:history:v1`);
693+
window.localStorage.removeItem(`od:design-browser:${projectId}:viewport:v1`);
694+
} catch {
695+
// Ignore private-mode/quota errors; the cache entry is best-effort.
696+
}
697+
}
698+
685699
function writeCachedTabs(projectId: string, state: OpenTabsState): OpenTabsState {
686700
const next: OpenTabsState = {
687701
...state,

apps/web/tests/state/projects.test.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -462,15 +462,22 @@ describe('pickLocalFolderPath', () => {
462462
});
463463
});
464464

465-
describe('deleteProject tabs cache', () => {
465+
describe('deleteProject local caches', () => {
466466
afterEach(() => {
467467
vi.unstubAllGlobals();
468468
});
469469

470470
const tabsKey = 'open-design:project-tabs:v1:p1';
471+
// Keep in sync with DesignBrowserPanel historyStorageKey / viewportStorageKey.
472+
const historyKey = 'od:design-browser:p1:history:v1';
473+
const viewportKey = 'od:design-browser:p1:viewport:v1';
471474

472475
function stubWindowStore(): Map<string, string> {
473-
const store = new Map<string, string>([[tabsKey, JSON.stringify({ tabs: [], active: null })]]);
476+
const store = new Map<string, string>([
477+
[tabsKey, JSON.stringify({ tabs: [], active: null })],
478+
[historyKey, JSON.stringify([{ url: 'https://example.com', title: 'Example', lastVisitedAt: 1 }])],
479+
[viewportKey, 'mobile'],
480+
]);
474481
vi.stubGlobal('window', {
475482
localStorage: {
476483
getItem: (k: string) => store.get(k) ?? null,
@@ -485,17 +492,21 @@ describe('deleteProject tabs cache', () => {
485492
return store;
486493
}
487494

488-
it('prunes the project tabs cache on a successful delete', async () => {
495+
it('prunes tabs and Design Browser caches on a successful delete', async () => {
489496
const store = stubWindowStore();
490497
vi.stubGlobal('fetch', vi.fn<typeof fetch>(async () => new Response(null, { status: 200 })));
491498
await expect(deleteProject('p1')).resolves.toBe(true);
492499
expect(store.has(tabsKey)).toBe(false);
500+
expect(store.has(historyKey)).toBe(false);
501+
expect(store.has(viewportKey)).toBe(false);
493502
});
494503

495-
it('keeps the tabs cache when the delete fails', async () => {
504+
it('keeps tabs and Design Browser caches when the delete fails', async () => {
496505
const store = stubWindowStore();
497506
vi.stubGlobal('fetch', vi.fn<typeof fetch>(async () => new Response(null, { status: 500 })));
498507
await expect(deleteProject('p1')).resolves.toBe(false);
499508
expect(store.has(tabsKey)).toBe(true);
509+
expect(store.has(historyKey)).toBe(true);
510+
expect(store.has(viewportKey)).toBe(true);
500511
});
501512
});

0 commit comments

Comments
 (0)