refactor: Improve all git operations, add stash support, add improved pull request flow, add worktree file copy options, address code review comments, add cherry pick options

This commit is contained in:
gsxdsm
2026-02-17 22:02:58 -08:00
parent f4e87d4c25
commit 9af63bc1ef
89 changed files with 6811 additions and 351 deletions

View File

@@ -335,6 +335,7 @@ const initialState: AppState = {
defaultDeleteBranchByProject: {},
autoDismissInitScriptIndicatorByProject: {},
useWorktreesByProject: {},
worktreeCopyFilesByProject: {},
worktreePanelCollapsed: false,
lastProjectDir: '',
recentFolders: [],
@@ -359,10 +360,15 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
}
},
removeProject: (projectId) =>
removeProject: (projectId: string) => {
set((state) => ({
projects: state.projects.filter((p) => p.id !== projectId),
})),
currentProject: state.currentProject?.id === projectId ? null : state.currentProject,
}));
// Persist to storage
saveProjects(get().projects);
},
moveProjectToTrash: (projectId: string) => {
const project = get().projects.find((p) => p.id === projectId);
@@ -2394,6 +2400,16 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
return projectOverride !== undefined ? projectOverride : get().useWorktrees;
},
// Worktree Copy Files actions
setWorktreeCopyFiles: (projectPath, files) =>
set((state) => ({
worktreeCopyFilesByProject: {
...state.worktreeCopyFilesByProject,
[projectPath]: files,
},
})),
getWorktreeCopyFiles: (projectPath) => get().worktreeCopyFilesByProject[projectPath] ?? [],
// UI State actions
setWorktreePanelCollapsed: (collapsed) => set({ worktreePanelCollapsed: collapsed }),
setLastProjectDir: (dir) => set({ lastProjectDir: dir }),

View File

@@ -1,4 +1,5 @@
import { create } from 'zustand';
import type { GeminiAuthStatus } from '@automaker/types';
// Note: persist middleware removed - settings now sync via API (use-settings-sync.ts)
// CLI Installation Status
@@ -127,21 +128,8 @@ export interface ZaiAuthStatus {
error?: string;
}
// Gemini Auth Method
export type GeminiAuthMethod =
| 'cli_login' // Gemini CLI is installed and authenticated
| 'api_key_env' // GOOGLE_API_KEY or GEMINI_API_KEY environment variable
| 'api_key' // Manually stored API key
| 'none';
// Gemini Auth Status
export interface GeminiAuthStatus {
authenticated: boolean;
method: GeminiAuthMethod;
hasApiKey?: boolean;
hasEnvApiKey?: boolean;
error?: string;
}
// GeminiAuthStatus is imported from @automaker/types (method: 'google_login' | 'api_key' | 'vertex_ai' | 'none')
export type { GeminiAuthStatus };
// Claude Auth Method - all possible authentication sources
export type ClaudeAuthMethod =

View File

@@ -341,6 +341,10 @@ export interface AppState {
// undefined = use global setting, true/false = project-specific override
useWorktreesByProject: Record<string, boolean | undefined>;
// Worktree Copy Files (per-project, keyed by project path)
// List of relative file paths to copy from project root into new worktrees
worktreeCopyFilesByProject: Record<string, string[]>;
// UI State (previously in localStorage, now synced via API)
/** Whether worktree panel is collapsed in board view */
worktreePanelCollapsed: boolean;
@@ -756,6 +760,10 @@ export interface AppActions {
getProjectUseWorktrees: (projectPath: string) => boolean | undefined; // undefined = using global
getEffectiveUseWorktrees: (projectPath: string) => boolean; // Returns actual value (project or global fallback)
// Worktree Copy Files actions (per-project)
setWorktreeCopyFiles: (projectPath: string, files: string[]) => void;
getWorktreeCopyFiles: (projectPath: string) => string[];
// UI State actions (previously in localStorage, now synced via API)
setWorktreePanelCollapsed: (collapsed: boolean) => void;
setLastProjectDir: (dir: string) => void;

View File

@@ -22,6 +22,7 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { useAppStore } from '@/store/app-store';
interface UICacheState {
/** ID of the currently selected project */
@@ -82,13 +83,27 @@ export function syncUICache(appState: {
worktreePanelCollapsed?: boolean;
collapsedNavSections?: Record<string, boolean>;
}): void {
useUICacheStore.getState().updateFromAppStore({
cachedProjectId: appState.currentProject?.id ?? null,
cachedSidebarOpen: appState.sidebarOpen ?? true,
cachedSidebarStyle: appState.sidebarStyle ?? 'unified',
cachedWorktreePanelCollapsed: appState.worktreePanelCollapsed ?? false,
cachedCollapsedNavSections: appState.collapsedNavSections ?? {},
});
const update: Partial<UICacheState> = {};
if ('currentProject' in appState) {
update.cachedProjectId = appState.currentProject?.id ?? null;
}
if ('sidebarOpen' in appState) {
update.cachedSidebarOpen = appState.sidebarOpen;
}
if ('sidebarStyle' in appState) {
update.cachedSidebarStyle = appState.sidebarStyle;
}
if ('worktreePanelCollapsed' in appState) {
update.cachedWorktreePanelCollapsed = appState.worktreePanelCollapsed;
}
if ('collapsedNavSections' in appState) {
update.cachedCollapsedNavSections = appState.collapsedNavSections;
}
if (Object.keys(update).length > 0) {
useUICacheStore.getState().updateFromAppStore(update);
}
}
/**
@@ -100,7 +115,7 @@ export function syncUICache(appState: {
* This is reconciled later when hydrateStoreFromSettings() overwrites
* the app store with authoritative server data.
*
* @param appStoreSetState - The setState function from the app store (avoids circular import)
* @param appStoreSetState - The setState function from the app store
*/
export function restoreFromUICache(
appStoreSetState: (state: Record<string, unknown>) => void
@@ -112,12 +127,29 @@ export function restoreFromUICache(
return false;
}
appStoreSetState({
// Attempt to resolve the cached project ID to a full project object.
// At early startup the projects array may be empty (server data not yet loaded),
// but if projects are already in the store (e.g. optimistic hydration has run)
// this will restore the project context immediately so tab-discard recovery
// does not lose the selected project when cached settings are missing.
const existingProjects = useAppStore.getState().projects;
const cachedProject = existingProjects.find((p) => p.id === cache.cachedProjectId) ?? null;
const stateUpdate: Record<string, unknown> = {
sidebarOpen: cache.cachedSidebarOpen,
sidebarStyle: cache.cachedSidebarStyle,
worktreePanelCollapsed: cache.cachedWorktreePanelCollapsed,
collapsedNavSections: cache.cachedCollapsedNavSections,
});
};
// Restore the project context when the project object is available.
// When projects are not yet loaded (empty array), currentProject remains
// null and will be properly set later by hydrateStoreFromSettings().
if (cachedProject !== null) {
stateUpdate.currentProject = cachedProject;
}
appStoreSetState(stateUpdate);
return true;
}