Merge branch 'main' into dev
Resolve 38 conflicts preserving both sides' work: - Server: DB-based registries (dev) + WorkspaceGitService/DaemonConfigStore (main) - App: script proxy/voice MCP (dev) + pin/hide tabs/bootstrap boundary (main) - Shared: WorkspaceScriptPayload (dev) + git/GitHub runtime schemas (main) - Deleted workspace-registry-bootstrap.ts (dev's DB approach supersedes)
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { create } from "zustand";
|
||||
import type { SidebarShortcutWorkspaceTarget } from "@/utils/sidebar-shortcuts";
|
||||
|
||||
const SHORTCUT_BADGE_DELAY_MS = 150;
|
||||
|
||||
interface KeyboardShortcutsState {
|
||||
commandCenterOpen: boolean;
|
||||
projectPickerOpen: boolean;
|
||||
@@ -8,6 +10,7 @@ interface KeyboardShortcutsState {
|
||||
capturingShortcut: boolean;
|
||||
altDown: boolean;
|
||||
cmdOrCtrlDown: boolean;
|
||||
showShortcutBadges: boolean;
|
||||
/** Sidebar-visible workspace targets (up to 9), in top-to-bottom visual order. */
|
||||
sidebarShortcutWorkspaceTargets: SidebarShortcutWorkspaceTarget[];
|
||||
/** All visible workspace targets in top-to-bottom visual order. */
|
||||
@@ -24,13 +27,37 @@ interface KeyboardShortcutsState {
|
||||
resetModifiers: () => void;
|
||||
}
|
||||
|
||||
export const useKeyboardShortcutsStore = create<KeyboardShortcutsState>((set) => ({
|
||||
let badgeTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
function updateBadgeTimer(
|
||||
set: (partial: Partial<KeyboardShortcutsState>) => void,
|
||||
get: () => KeyboardShortcutsState,
|
||||
) {
|
||||
const { altDown, cmdOrCtrlDown } = get();
|
||||
const modifierDown = altDown || cmdOrCtrlDown;
|
||||
|
||||
if (badgeTimer) {
|
||||
clearTimeout(badgeTimer);
|
||||
badgeTimer = null;
|
||||
}
|
||||
|
||||
if (modifierDown) {
|
||||
badgeTimer = setTimeout(() => {
|
||||
set({ showShortcutBadges: true });
|
||||
}, SHORTCUT_BADGE_DELAY_MS);
|
||||
} else {
|
||||
set({ showShortcutBadges: false });
|
||||
}
|
||||
}
|
||||
|
||||
export const useKeyboardShortcutsStore = create<KeyboardShortcutsState>((set, get) => ({
|
||||
commandCenterOpen: false,
|
||||
projectPickerOpen: false,
|
||||
shortcutsDialogOpen: false,
|
||||
capturingShortcut: false,
|
||||
altDown: false,
|
||||
cmdOrCtrlDown: false,
|
||||
showShortcutBadges: false,
|
||||
sidebarShortcutWorkspaceTargets: [],
|
||||
visibleWorkspaceTargets: [],
|
||||
|
||||
@@ -38,10 +65,19 @@ export const useKeyboardShortcutsStore = create<KeyboardShortcutsState>((set) =>
|
||||
setProjectPickerOpen: (open) => set({ projectPickerOpen: open }),
|
||||
setShortcutsDialogOpen: (open) => set({ shortcutsDialogOpen: open }),
|
||||
setCapturingShortcut: (capturing) => set({ capturingShortcut: capturing }),
|
||||
setAltDown: (down) => set({ altDown: down }),
|
||||
setCmdOrCtrlDown: (down) => set({ cmdOrCtrlDown: down }),
|
||||
setAltDown: (down) => {
|
||||
set({ altDown: down });
|
||||
updateBadgeTimer(set, get);
|
||||
},
|
||||
setCmdOrCtrlDown: (down) => {
|
||||
set({ cmdOrCtrlDown: down });
|
||||
updateBadgeTimer(set, get);
|
||||
},
|
||||
setSidebarShortcutWorkspaceTargets: (targets) =>
|
||||
set({ sidebarShortcutWorkspaceTargets: targets }),
|
||||
setVisibleWorkspaceTargets: (targets) => set({ visibleWorkspaceTargets: targets }),
|
||||
resetModifiers: () => set({ altDown: false, cmdOrCtrlDown: false }),
|
||||
resetModifiers: () => {
|
||||
set({ altDown: false, cmdOrCtrlDown: false });
|
||||
updateBadgeTimer(set, get);
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -23,7 +23,6 @@ function createWorkspace(
|
||||
workspaceKind: input.workspaceKind ?? "local_checkout",
|
||||
name: input.name ?? "main",
|
||||
status: input.status ?? "done",
|
||||
activityAt: input.activityAt ?? null,
|
||||
diffStat: input.diffStat ?? null,
|
||||
scripts: input.scripts ?? [],
|
||||
};
|
||||
@@ -62,7 +61,6 @@ describe("normalizeWorkspaceDescriptor", () => {
|
||||
scripts,
|
||||
});
|
||||
|
||||
expect(workspace.activityAt).toBeNull();
|
||||
expect(workspace.scripts).toEqual([
|
||||
{
|
||||
scriptName: "web",
|
||||
|
||||
@@ -122,7 +122,6 @@ export interface WorkspaceDescriptor {
|
||||
workspaceKind: WorkspaceDescriptorPayload["workspaceKind"];
|
||||
name: string;
|
||||
status: WorkspaceDescriptorPayload["status"];
|
||||
activityAt: Date | null;
|
||||
diffStat: { additions: number; deletions: number } | null;
|
||||
scripts: WorkspaceDescriptorPayload["scripts"];
|
||||
}
|
||||
@@ -130,7 +129,6 @@ export interface WorkspaceDescriptor {
|
||||
export function normalizeWorkspaceDescriptor(
|
||||
payload: WorkspaceDescriptorPayload,
|
||||
): WorkspaceDescriptor {
|
||||
const activityAt = payload.activityAt ? new Date(payload.activityAt) : null;
|
||||
return {
|
||||
id: normalizeWorkspaceIdentity(String(payload.id)) ?? String(payload.id),
|
||||
projectId: String(payload.projectId),
|
||||
@@ -141,7 +139,6 @@ export function normalizeWorkspaceDescriptor(
|
||||
workspaceKind: payload.workspaceKind,
|
||||
name: payload.name,
|
||||
status: payload.status,
|
||||
activityAt: activityAt && !Number.isNaN(activityAt.getTime()) ? activityAt : null,
|
||||
diffStat: payload.diffStat ?? null,
|
||||
scripts: (payload.scripts ?? []).map((s) => ({ ...s })),
|
||||
};
|
||||
|
||||
@@ -1005,4 +1005,32 @@ describe("workspace-layout-store actions", () => {
|
||||
expect(state.hiddenAgentIdsByWorkspace[workspaceKey]).toBeUndefined();
|
||||
expect(state.getWorkspaceTabs(workspaceKey).map((tab) => tab.tabId)).toEqual(["agent_agent-1"]);
|
||||
});
|
||||
|
||||
it("pinning an agent clears hidden intent", () => {
|
||||
const workspaceKey = createWorkspaceKey();
|
||||
const store = useWorkspaceLayoutStore.getState();
|
||||
|
||||
store.hideAgent(workspaceKey, "agent-1");
|
||||
expect(
|
||||
useWorkspaceLayoutStore.getState().hiddenAgentIdsByWorkspace[workspaceKey],
|
||||
).toBeDefined();
|
||||
|
||||
store.pinAgent(workspaceKey, "agent-1");
|
||||
|
||||
const state = useWorkspaceLayoutStore.getState();
|
||||
expect(state.hiddenAgentIdsByWorkspace[workspaceKey]).toBeUndefined();
|
||||
expect(Array.from(state.pinnedAgentIdsByWorkspace[workspaceKey] ?? [])).toEqual(["agent-1"]);
|
||||
});
|
||||
|
||||
it("retargeting a tab to an agent clears hidden intent", () => {
|
||||
const workspaceKey = createWorkspaceKey();
|
||||
const store = useWorkspaceLayoutStore.getState();
|
||||
|
||||
store.hideAgent(workspaceKey, "agent-1");
|
||||
const tabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/a.ts" });
|
||||
store.retargetTab(workspaceKey, tabId!, { kind: "agent", agentId: "agent-1" });
|
||||
|
||||
const state = useWorkspaceLayoutStore.getState();
|
||||
expect(state.hiddenAgentIdsByWorkspace[workspaceKey]).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user