Improve pull request flow, add branch selection for worktree creation, fix auto-mode concurrency count (#787)

* Changes from fix/fetch-before-pull-fetch

* feat: Improve pull request flow, add branch selection for worktree creation, fix for automode concurrency count

* feat: Add validation for remote names and improve error handling

* Address PR comments and mobile layout fixes

* ```
refactor: Extract PR target resolution logic into dedicated service
```

* feat: Add app shell UI and improve service imports. Address PR comments

* fix: Improve security validation and cache handling in git operations

* feat: Add GET /list endpoint and improve parameter handling

* chore: Improve validation, accessibility, and error handling across apps

* chore: Format vite server port configuration

* fix: Add error handling for gh pr list command and improve offline fallbacks

* fix: Preserve existing PR creation time and improve remote handling
This commit is contained in:
gsxdsm
2026-02-19 21:55:12 -08:00
committed by GitHub
parent ee52333636
commit 7df2182818
80 changed files with 4729 additions and 1107 deletions

View File

@@ -217,6 +217,52 @@ export { defaultBackgroundSettings, defaultTerminalState, MAX_INIT_OUTPUT_LINES
// Type definitions are imported from ./types/state-types.ts
// AppActions interface is defined in ./types/state-types.ts
/**
* Pre-populate sidebar/UI state from the UI cache at module load time.
* This runs synchronously before createRoot().render(), so the very first
* React render uses the correct sidebar width — eliminating the layout shift
* (wide sidebar → collapsed) that was visible when auth was pre-populated
* but sidebar state wasn't.
*/
function getInitialUIState(): {
sidebarOpen: boolean;
sidebarStyle: 'unified' | 'discord';
collapsedNavSections: Record<string, boolean>;
} {
try {
const raw = localStorage.getItem('automaker-ui-cache');
if (raw) {
const wrapper = JSON.parse(raw);
// zustand/persist wraps state under a "state" key
const cache = wrapper?.state;
if (cache) {
return {
sidebarOpen:
typeof cache.cachedSidebarOpen === 'boolean' ? cache.cachedSidebarOpen : true,
sidebarStyle: cache.cachedSidebarStyle === 'discord' ? 'discord' : 'unified',
collapsedNavSections: (() => {
const raw = cache.cachedCollapsedNavSections;
if (
raw &&
typeof raw === 'object' &&
!Array.isArray(raw) &&
Object.getOwnPropertyNames(raw).every((k) => typeof raw[k] === 'boolean')
) {
return raw as Record<string, boolean>;
}
return {};
})(),
};
}
}
} catch {
// fall through to defaults
}
return { sidebarOpen: true, sidebarStyle: 'unified', collapsedNavSections: {} };
}
const cachedUI = getInitialUIState();
const initialState: AppState = {
projects: [],
currentProject: null,
@@ -224,9 +270,9 @@ const initialState: AppState = {
projectHistory: [],
projectHistoryIndex: -1,
currentView: 'welcome',
sidebarOpen: true,
sidebarStyle: 'unified',
collapsedNavSections: {},
sidebarOpen: cachedUI.sidebarOpen,
sidebarStyle: cachedUI.sidebarStyle,
collapsedNavSections: cachedUI.collapsedNavSections,
mobileSidebarHidden: false,
lastSelectedSessionByProject: {},
theme: getStoredTheme() || 'dark',
@@ -942,6 +988,13 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
runningTasks: [],
branchName,
};
// Prevent duplicate entries - the same feature can trigger multiple
// auto_mode_feature_start events (e.g., from execution-service and
// pipeline-orchestrator), so we must guard against adding the same
// taskId more than once.
if (current.runningTasks.includes(taskId)) {
return state;
}
return {
autoModeByWorktree: {
...state.autoModeByWorktree,

View File

@@ -14,21 +14,63 @@ interface AuthActions {
resetAuth: () => void;
}
const initialState: AuthState = {
authChecked: false,
isAuthenticated: false,
settingsLoaded: false,
};
/**
* Pre-flight check: if localStorage has cached settings with projects AND setup is
* complete, we can optimistically mark auth as complete on the very first render,
* skipping the spinner entirely. The background verify in __root.tsx will correct
* this if the session is invalid.
*
* This runs synchronously at module load time — before createRoot().render() —
* so the first React render never shows the !authChecked spinner for returning users.
*
* We only set settingsLoaded=true when setupComplete is also true in the cache.
* If setupComplete is false, settingsLoaded stays false so the routing effect in
* __root.tsx doesn't immediately redirect to /setup before the setup store is hydrated.
* In practice, returning users who completed setup have both flags in their cache.
*
* Intentionally minimal: only checks for the key existence and basic structure.
* Full hydration (project data, settings) is handled by __root.tsx after mount.
*/
function getInitialAuthState(): AuthState {
try {
const raw = localStorage.getItem('automaker-settings-cache');
if (raw) {
const parsed = JSON.parse(raw) as {
projects?: unknown[];
setupComplete?: boolean;
};
if (parsed?.projects && Array.isArray(parsed.projects) && parsed.projects.length > 0) {
// Returning user with cached settings — optimistically mark as authenticated.
// Only mark settingsLoaded=true when setupComplete is confirmed in cache,
// preventing premature /setup redirects before the setup store is hydrated.
// Background verify in __root.tsx will fix isAuthenticated if session expired.
const setupDone = parsed.setupComplete === true;
return {
authChecked: true,
isAuthenticated: true,
settingsLoaded: setupDone,
};
}
}
} catch {
// Corrupted localStorage or JSON parse error — fall through to cold start
}
return { authChecked: false, isAuthenticated: false, settingsLoaded: false };
}
const initialState: AuthState = getInitialAuthState();
/**
* Web authentication state.
*
* Intentionally NOT persisted: source of truth is server session cookie.
* Initial state is optimistically set from localStorage cache for returning users,
* then verified against the server in the background by __root.tsx.
*/
export const useAuthStore = create<AuthState & AuthActions>((set) => ({
...initialState,
setAuthState: (state) => {
set({ ...state });
},
resetAuth: () => set(initialState),
resetAuth: () => set({ authChecked: false, isAuthenticated: false, settingsLoaded: false }),
}));

View File

@@ -270,10 +270,39 @@ const initialInstallProgress: InstallProgress = {
// Check if setup should be skipped (for E2E testing)
const shouldSkipSetup = import.meta.env.VITE_SKIP_SETUP === 'true';
/**
* Pre-flight check: read setupComplete from localStorage settings cache so that
* the routing effect in __root.tsx doesn't flash /setup for returning users.
*
* The setup store is intentionally NOT persisted (settings sync via API), but on
* first render the routing check fires before the initAuth useEffect can call
* hydrateStoreFromSettings(). If setupComplete starts as false, returning users
* who have completed setup see a /setup redirect flash.
*
* Reading from localStorage here is safe: it's the same key used by
* parseLocalStorageSettings() and written by the settings sync hook.
* On first-ever visit (no cache), this returns false as expected.
*/
function getInitialSetupComplete(): boolean {
if (shouldSkipSetup) return true;
try {
const raw = localStorage.getItem('automaker-settings-cache');
if (raw) {
const parsed = JSON.parse(raw) as { setupComplete?: boolean };
if (parsed?.setupComplete === true) return true;
}
} catch {
// localStorage unavailable or JSON invalid — fall through
}
return false;
}
const initialSetupComplete = getInitialSetupComplete();
const initialState: SetupState = {
isFirstRun: !shouldSkipSetup,
setupComplete: shouldSkipSetup,
currentStep: shouldSkipSetup ? 'complete' : 'welcome',
isFirstRun: !shouldSkipSetup && !initialSetupComplete,
setupComplete: initialSetupComplete,
currentStep: initialSetupComplete ? 'complete' : 'welcome',
claudeCliStatus: null,
claudeAuthStatus: null,
@@ -316,7 +345,11 @@ export const useSetupStore = create<SetupState & SetupActions>()((set, get) => (
resetSetup: () =>
set({
...initialState,
isFirstRun: false, // Don't reset first run flag
// Explicitly override runtime-critical fields that may be stale in the
// module-level initialState (captured at import time from localStorage).
setupComplete: false,
currentStep: 'welcome',
isFirstRun: false, // Don't reset first run flag — user has visited before
}),
setIsFirstRun: (isFirstRun) => set({ isFirstRun }),