Skip to content

Commit aec17d2

Browse files
authored
🤖 fix: persist status URL using storage.ts pattern (#832)
Status URLs now use the standard storage.ts key pattern and are properly migrated on workspace rename. ## Changes - Add `getStatusUrlKey` to storage.ts with standard format (`statusUrl:{workspaceId}`) - Register in `PERSISTENT_WORKSPACE_KEY_FUNCTIONS` for fork/delete - Add `migrateWorkspaceStorage` for rename support (fixes pre-existing bug where all workspace storage was lost on rename) - Update `StreamingMessageAggregator` to use centralized key function ## Why Previously, status URLs used a non-standard key format (`mux:workspace:...`) that wasn't registered in storage.ts, so: 1. Keys weren't copied on fork 2. Keys weren't deleted on workspace removal 3. Keys weren't migrated on rename (pre-existing bug affecting all workspace storage) _Generated with `mux`_
1 parent 1e3dce5 commit aec17d2

File tree

3 files changed

+30
-15
lines changed

3 files changed

+30
-15
lines changed

src/browser/contexts/WorkspaceContext.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
import type { FrontendWorkspaceMetadata } from "@/common/types/workspace";
1212
import type { WorkspaceSelection } from "@/browser/components/ProjectSidebar";
1313
import type { RuntimeConfig } from "@/common/types/runtime";
14-
import { deleteWorkspaceStorage } from "@/common/constants/storage";
14+
import { deleteWorkspaceStorage, migrateWorkspaceStorage } from "@/common/constants/storage";
1515
import { usePersistedState } from "@/browser/hooks/usePersistedState";
1616
import { useProjectContext } from "@/browser/contexts/ProjectContext";
1717
import { useWorkspaceStoreRaw } from "@/browser/stores/WorkspaceStore";
@@ -320,6 +320,11 @@ export function WorkspaceProvider(props: WorkspaceProviderProps) {
320320
try {
321321
const result = await window.api.workspace.rename(workspaceId, newName);
322322
if (result.success) {
323+
const newWorkspaceId = result.data.newWorkspaceId;
324+
325+
// Migrate localStorage keys from old to new workspace ID
326+
migrateWorkspaceStorage(workspaceId, newWorkspaceId);
327+
323328
// Backend has already updated the config - reload projects to get updated state
324329
await refreshProjects();
325330

@@ -328,8 +333,6 @@ export function WorkspaceProvider(props: WorkspaceProviderProps) {
328333

329334
// Update selected workspace if it was renamed
330335
if (selectedWorkspace?.workspaceId === workspaceId) {
331-
const newWorkspaceId = result.data.newWorkspaceId;
332-
333336
// Get updated workspace metadata from backend
334337
const newMetadata = await window.api.workspace.getInfo(newWorkspaceId);
335338
if (newMetadata) {

src/browser/utils/messages/StreamingMessageAggregator.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import type {
3131
import { isDynamicToolPart } from "@/common/types/toolParts";
3232
import { createDeltaStorage, type DeltaRecordStorage } from "./StreamingTPSCalculator";
3333
import { computeRecencyTimestamp } from "./recency";
34+
import { getStatusUrlKey } from "@/common/constants/storage";
3435

3536
// Maximum number of messages to display in the DOM for performance
3637
// Full history is still maintained internally for token counting and stats
@@ -125,18 +126,11 @@ export class StreamingMessageAggregator {
125126
this.updateRecency();
126127
}
127128

128-
/** localStorage key for persisting lastStatusUrl. Only call when workspaceId is defined. */
129-
private getStatusUrlKey(): string | undefined {
130-
if (!this.workspaceId) return undefined;
131-
return `mux:workspace:${this.workspaceId}:lastStatusUrl`;
132-
}
133-
134129
/** Load lastStatusUrl from localStorage */
135130
private loadLastStatusUrl(): string | undefined {
136-
const key = this.getStatusUrlKey();
137-
if (!key) return undefined;
131+
if (!this.workspaceId) return undefined;
138132
try {
139-
const stored = localStorage.getItem(key);
133+
const stored = localStorage.getItem(getStatusUrlKey(this.workspaceId));
140134
return stored ?? undefined;
141135
} catch {
142136
return undefined;
@@ -148,10 +142,9 @@ export class StreamingMessageAggregator {
148142
* Once set, the URL can only be replaced with a new URL, never deleted.
149143
*/
150144
private saveLastStatusUrl(url: string): void {
151-
const key = this.getStatusUrlKey();
152-
if (!key) return;
145+
if (!this.workspaceId) return;
153146
try {
154-
localStorage.setItem(key, url);
147+
localStorage.setItem(getStatusUrlKey(this.workspaceId), url);
155148
} catch {
156149
// Ignore localStorage errors
157150
}

src/common/constants/storage.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,15 @@ export function getFileTreeExpandStateKey(workspaceId: string): string {
162162
return `fileTreeExpandState:${workspaceId}`;
163163
}
164164

165+
/**
166+
* Get the localStorage key for persisted status URL for a workspace
167+
* Stores the last URL set via status_set tool (survives compaction)
168+
* Format: "statusUrl:{workspaceId}"
169+
*/
170+
export function getStatusUrlKey(workspaceId: string): string {
171+
return `statusUrl:${workspaceId}`;
172+
}
173+
165174
/**
166175
* Get the localStorage key for unified Review search state per workspace
167176
* Stores: { input: string, useRegex: boolean, matchCase: boolean }
@@ -202,6 +211,7 @@ const PERSISTENT_WORKSPACE_KEY_FUNCTIONS: Array<(workspaceId: string) => string>
202211
getFileTreeExpandStateKey,
203212
getReviewSearchStateKey,
204213
getAutoCompactionEnabledKey,
214+
getStatusUrlKey,
205215
// Note: getAutoCompactionThresholdKey is per-model, not per-workspace
206216
];
207217

@@ -242,3 +252,12 @@ export function deleteWorkspaceStorage(workspaceId: string): void {
242252
localStorage.removeItem(key);
243253
}
244254
}
255+
256+
/**
257+
* Migrate all workspace-specific localStorage keys from old to new workspace ID
258+
* Should be called when a workspace is renamed to preserve settings
259+
*/
260+
export function migrateWorkspaceStorage(oldWorkspaceId: string, newWorkspaceId: string): void {
261+
copyWorkspaceStorage(oldWorkspaceId, newWorkspaceId);
262+
deleteWorkspaceStorage(oldWorkspaceId);
263+
}

0 commit comments

Comments
 (0)