fix: address pr comments

This commit is contained in:
Shirone
2026-01-12 18:41:56 +01:00
parent a0669d4262
commit f41a42010c
6 changed files with 158 additions and 36 deletions

View File

@@ -12,12 +12,22 @@ const featureLoader = new FeatureLoader();
export function createApplyHandler() {
return async (req: Request, res: Response): Promise<void> => {
try {
const { projectPath, plan, branchName } = req.body as {
const {
projectPath,
plan,
branchName: rawBranchName,
} = req.body as {
projectPath: string;
plan: BacklogPlanResult;
branchName?: string;
};
// Validate branchName: must be undefined or a non-empty trimmed string
const branchName =
typeof rawBranchName === 'string' && rawBranchName.trim().length > 0
? rawBranchName.trim()
: undefined;
if (!projectPath) {
res.status(400).json({ success: false, error: 'projectPath required' });
return;

View File

@@ -17,6 +17,7 @@ import { cn } from '@/lib/utils';
import { apiGet, apiPut, apiDelete } from '@/lib/api-fetch';
import { toast } from 'sonner';
import { useAppStore } from '@/store/app-store';
import { getHttpApiClient } from '@/lib/http-api-client';
interface WorktreesSectionProps {
useWorktrees: boolean;
@@ -217,9 +218,19 @@ export function WorktreesSection({ useWorktrees, onUseWorktreesChange }: Worktre
<Checkbox
id="show-init-script-indicator"
checked={showIndicator}
onCheckedChange={(checked) => {
onCheckedChange={async (checked) => {
if (currentProject?.path) {
setShowInitScriptIndicator(currentProject.path, checked === true);
const value = checked === true;
setShowInitScriptIndicator(currentProject.path, value);
// Persist to server
try {
const httpClient = getHttpApiClient();
await httpClient.settings.updateProject(currentProject.path, {
showInitScriptIndicator: value,
});
} catch (error) {
console.error('Failed to persist showInitScriptIndicator:', error);
}
}
}}
className="mt-1"
@@ -246,9 +257,19 @@ export function WorktreesSection({ useWorktrees, onUseWorktreesChange }: Worktre
<Checkbox
id="auto-dismiss-indicator"
checked={autoDismiss}
onCheckedChange={(checked) => {
onCheckedChange={async (checked) => {
if (currentProject?.path) {
setAutoDismissInitScriptIndicator(currentProject.path, checked === true);
const value = checked === true;
setAutoDismissInitScriptIndicator(currentProject.path, value);
// Persist to server
try {
const httpClient = getHttpApiClient();
await httpClient.settings.updateProject(currentProject.path, {
autoDismissInitScriptIndicator: value,
});
} catch (error) {
console.error('Failed to persist autoDismissInitScriptIndicator:', error);
}
}
}}
className="mt-1"
@@ -273,9 +294,19 @@ export function WorktreesSection({ useWorktrees, onUseWorktreesChange }: Worktre
<Checkbox
id="default-delete-branch"
checked={defaultDeleteBranch}
onCheckedChange={(checked) => {
onCheckedChange={async (checked) => {
if (currentProject?.path) {
setDefaultDeleteBranch(currentProject.path, checked === true);
const value = checked === true;
setDefaultDeleteBranch(currentProject.path, value);
// Persist to server
try {
const httpClient = getHttpApiClient();
await httpClient.settings.updateProject(currentProject.path, {
defaultDeleteBranch: value,
});
} catch (error) {
console.error('Failed to persist defaultDeleteBranch:', error);
}
}
}}
className="mt-1"

View File

@@ -18,6 +18,11 @@ export function useProjectSettingsLoader() {
const setCardBorderOpacity = useAppStore((state) => state.setCardBorderOpacity);
const setHideScrollbar = useAppStore((state) => state.setHideScrollbar);
const setWorktreePanelVisible = useAppStore((state) => state.setWorktreePanelVisible);
const setShowInitScriptIndicator = useAppStore((state) => state.setShowInitScriptIndicator);
const setDefaultDeleteBranch = useAppStore((state) => state.setDefaultDeleteBranch);
const setAutoDismissInitScriptIndicator = useAppStore(
(state) => state.setAutoDismissInitScriptIndicator
);
const loadingRef = useRef<string | null>(null);
const currentProjectRef = useRef<string | null>(null);
@@ -78,6 +83,27 @@ export function useProjectSettingsLoader() {
if (result.settings.worktreePanelVisible !== undefined) {
setWorktreePanelVisible(requestedProjectPath, result.settings.worktreePanelVisible);
}
// Apply showInitScriptIndicator if present
if (result.settings.showInitScriptIndicator !== undefined) {
setShowInitScriptIndicator(
requestedProjectPath,
result.settings.showInitScriptIndicator
);
}
// Apply defaultDeleteBranch if present
if (result.settings.defaultDeleteBranch !== undefined) {
setDefaultDeleteBranch(requestedProjectPath, result.settings.defaultDeleteBranch);
}
// Apply autoDismissInitScriptIndicator if present
if (result.settings.autoDismissInitScriptIndicator !== undefined) {
setAutoDismissInitScriptIndicator(
requestedProjectPath,
result.settings.autoDismissInitScriptIndicator
);
}
}
} catch (error) {
console.error('Failed to load project settings:', error);

View File

@@ -80,6 +80,9 @@ export type ThemeMode =
// LocalStorage key for theme persistence (fallback when server settings aren't available)
export const THEME_STORAGE_KEY = 'automaker:theme';
// Maximum number of output lines to keep in init script state (prevents unbounded memory growth)
export const MAX_INIT_OUTPUT_LINES = 500;
/**
* Get the theme from localStorage as a fallback
* Used before server settings are loaded (e.g., on login/setup pages)
@@ -1823,16 +1826,28 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
await syncSettingsToServer();
},
setPlanUseSelectedWorktreeBranch: async (enabled) => {
const previous = get().planUseSelectedWorktreeBranch;
set({ planUseSelectedWorktreeBranch: enabled });
// Sync to server settings file
const { syncSettingsToServer } = await import('@/hooks/use-settings-migration');
await syncSettingsToServer();
const ok = await syncSettingsToServer();
if (!ok) {
logger.error('Failed to sync planUseSelectedWorktreeBranch setting to server - reverting');
set({ planUseSelectedWorktreeBranch: previous });
}
},
setAddFeatureUseSelectedWorktreeBranch: async (enabled) => {
const previous = get().addFeatureUseSelectedWorktreeBranch;
set({ addFeatureUseSelectedWorktreeBranch: enabled });
// Sync to server settings file
const { syncSettingsToServer } = await import('@/hooks/use-settings-migration');
await syncSettingsToServer();
const ok = await syncSettingsToServer();
if (!ok) {
logger.error(
'Failed to sync addFeatureUseSelectedWorktreeBranch setting to server - reverting'
);
set({ addFeatureUseSelectedWorktreeBranch: previous });
}
},
// Worktree Settings actions
@@ -3282,14 +3297,20 @@ export const useAppStore = create<AppState & AppActions>()((set, get) => ({
appendInitScriptOutput: (projectPath, branch, content) => {
const key = `${projectPath}::${branch}`;
const current = get().initScriptState[key];
if (!current) return;
// Initialize state if absent to avoid dropping output due to event-order races
const current = get().initScriptState[key] || {
status: 'idle' as const,
branch,
output: [],
};
// Append new content and enforce fixed-size buffer to prevent memory bloat
const newOutput = [...current.output, content].slice(-MAX_INIT_OUTPUT_LINES);
set({
initScriptState: {
...get().initScriptState,
[key]: {
...current,
output: [...current.output, content],
output: newOutput,
},
},
});

View File

@@ -14,8 +14,12 @@ echo ""
# Install dependencies
echo "[1/1] Installing npm dependencies..."
if [ -f "package.json" ]; then
npm install
echo "Dependencies installed successfully!"
if npm install; then
echo "Dependencies installed successfully!"
else
echo "ERROR: npm install failed with exit code $?"
exit 1
fi
else
echo "No package.json found, skipping npm install"
fi

View File

@@ -232,6 +232,27 @@ export function getClaudeProjectsDir(): string {
return path.join(getClaudeConfigDir(), 'projects');
}
/**
* Enumerate directories matching a prefix pattern and return full paths
* Used to resolve dynamic directory names like version numbers
*/
function enumerateMatchingPaths(
parentDir: string,
prefix: string,
...subPathParts: string[]
): string[] {
try {
if (!fsSync.existsSync(parentDir)) {
return [];
}
const entries = fsSync.readdirSync(parentDir);
const matching = entries.filter((entry) => entry.startsWith(prefix));
return matching.map((entry) => path.join(parentDir, entry, ...subPathParts));
} catch {
return [];
}
}
/**
* Get common Git Bash installation paths on Windows
* Git Bash is needed for running shell scripts cross-platform
@@ -242,12 +263,38 @@ export function getGitBashPaths(): string[] {
}
const homeDir = os.homedir();
const localAppData = process.env.LOCALAPPDATA || '';
// Dynamic paths that require directory enumeration
// winget installs to: LocalAppData\Microsoft\WinGet\Packages\Git.Git_<hash>\bin\bash.exe
const wingetGitPaths = localAppData
? enumerateMatchingPaths(
path.join(localAppData, 'Microsoft', 'WinGet', 'Packages'),
'Git.Git_',
'bin',
'bash.exe'
)
: [];
// GitHub Desktop bundles Git at: LocalAppData\GitHubDesktop\app-<version>\resources\app\git\cmd\bash.exe
const githubDesktopPaths = localAppData
? enumerateMatchingPaths(
path.join(localAppData, 'GitHubDesktop'),
'app-',
'resources',
'app',
'git',
'cmd',
'bash.exe'
)
: [];
return [
// Standard Git for Windows installations
'C:\\Program Files\\Git\\bin\\bash.exe',
'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
// User-local installations
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Git', 'bin', 'bash.exe'),
path.join(localAppData, 'Programs', 'Git', 'bin', 'bash.exe'),
// Scoop package manager
path.join(homeDir, 'scoop', 'apps', 'git', 'current', 'bin', 'bash.exe'),
// Chocolatey
@@ -259,27 +306,10 @@ export function getGitBashPaths(): string[] {
'bin',
'bash.exe'
),
// winget typical location
path.join(
process.env.LOCALAPPDATA || '',
'Microsoft',
'WinGet',
'Packages',
'Git.Git_*',
'bin',
'bash.exe'
),
// GitHub Desktop bundled Git
path.join(
process.env.LOCALAPPDATA || '',
'GitHubDesktop',
'app-*',
'resources',
'app',
'git',
'cmd',
'bash.exe'
),
// winget installations (dynamically resolved)
...wingetGitPaths,
// GitHub Desktop bundled Git (dynamically resolved)
...githubDesktopPaths,
].filter(Boolean);
}