feat(app): improve workspace service controls
Keep service route choices independent of daemon transport and persist the selected route per host. Use the shared web overlay stack so menus, tooltips, and toasts layer predictably. Track proxy route changes from the Git branch rather than the workspace title to avoid transient service health changes.
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { StateStorage } from "zustand/middleware";
|
||||
import { createWorkspaceServiceRoutePreferencesStore } from "./store";
|
||||
|
||||
function createMemoryStorage(initial?: Record<string, string>): StateStorage & {
|
||||
values: Map<string, string>;
|
||||
} {
|
||||
const values = new Map(Object.entries(initial ?? {}));
|
||||
return {
|
||||
values,
|
||||
getItem: async (name) => values.get(name) ?? null,
|
||||
setItem: async (name, value) => {
|
||||
values.set(name, value);
|
||||
},
|
||||
removeItem: async (name) => {
|
||||
values.delete(name);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe("workspace service route preferences", () => {
|
||||
it("persists each host's preferred route", async () => {
|
||||
const storage = createMemoryStorage();
|
||||
const first = createWorkspaceServiceRoutePreferencesStore(storage);
|
||||
await first.persist.rehydrate();
|
||||
|
||||
first.getState().setPreferredRoute("desktop", "direct");
|
||||
first.getState().setPreferredRoute("devbox", "public");
|
||||
|
||||
const restored = createWorkspaceServiceRoutePreferencesStore(storage);
|
||||
await restored.persist.rehydrate();
|
||||
expect(restored.getState().byServerId).toEqual({ desktop: "direct", devbox: "public" });
|
||||
});
|
||||
|
||||
it("drops invalid route kinds from persisted storage", async () => {
|
||||
const storage = createMemoryStorage({
|
||||
"workspace-service-route-preferences": JSON.stringify({
|
||||
state: { byServerId: { desktop: "direct", broken: "unknown" } },
|
||||
version: 1,
|
||||
}),
|
||||
});
|
||||
const store = createWorkspaceServiceRoutePreferencesStore(storage);
|
||||
await store.persist.rehydrate();
|
||||
|
||||
expect(store.getState().byServerId).toEqual({ desktop: "direct" });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import { create } from "zustand";
|
||||
import { createJSONStorage, persist, type StateStorage } from "zustand/middleware";
|
||||
import type { WorkspaceScriptLinkKind } from "@/utils/workspace-script-links";
|
||||
|
||||
interface WorkspaceServiceRoutePreferencesState {
|
||||
byServerId: Record<string, WorkspaceScriptLinkKind>;
|
||||
setPreferredRoute: (serverId: string, kind: WorkspaceScriptLinkKind) => void;
|
||||
}
|
||||
|
||||
function isWorkspaceScriptLinkKind(value: unknown): value is WorkspaceScriptLinkKind {
|
||||
return value === "public" || value === "paseo" || value === "direct";
|
||||
}
|
||||
|
||||
function sanitizePreferences(value: unknown): Record<string, WorkspaceScriptLinkKind> {
|
||||
if (!value || typeof value !== "object") return {};
|
||||
const byServerId = (value as { byServerId?: unknown }).byServerId;
|
||||
if (!byServerId || typeof byServerId !== "object") return {};
|
||||
|
||||
const result: Record<string, WorkspaceScriptLinkKind> = {};
|
||||
for (const [serverId, kind] of Object.entries(byServerId)) {
|
||||
if (isWorkspaceScriptLinkKind(kind)) result[serverId] = kind;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createWorkspaceServiceRoutePreferencesStore(storage: StateStorage) {
|
||||
return create<WorkspaceServiceRoutePreferencesState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
byServerId: {},
|
||||
setPreferredRoute: (serverId, kind) =>
|
||||
set((state) => ({ byServerId: { ...state.byServerId, [serverId]: kind } })),
|
||||
}),
|
||||
{
|
||||
name: "workspace-service-route-preferences",
|
||||
version: 1,
|
||||
storage: createJSONStorage(() => storage),
|
||||
partialize: (state) => ({ byServerId: state.byServerId }),
|
||||
merge: (persistedState, currentState) => ({
|
||||
...currentState,
|
||||
byServerId: sanitizePreferences(persistedState),
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export const useWorkspaceServiceRoutePreferencesStore =
|
||||
createWorkspaceServiceRoutePreferencesStore(AsyncStorage);
|
||||
Reference in New Issue
Block a user