mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-21 23:33:07 +00:00
fix: Address code review feedback and fix lint errors
This commit is contained in:
74
apps/server/eslint.config.mjs
Normal file
74
apps/server/eslint.config.mjs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { defineConfig, globalIgnores } from 'eslint/config';
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import ts from '@typescript-eslint/eslint-plugin';
|
||||||
|
import tsParser from '@typescript-eslint/parser';
|
||||||
|
|
||||||
|
const eslintConfig = defineConfig([
|
||||||
|
js.configs.recommended,
|
||||||
|
{
|
||||||
|
files: ['**/*.ts'],
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsParser,
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
globals: {
|
||||||
|
// Node.js globals
|
||||||
|
console: 'readonly',
|
||||||
|
process: 'readonly',
|
||||||
|
Buffer: 'readonly',
|
||||||
|
__dirname: 'readonly',
|
||||||
|
__filename: 'readonly',
|
||||||
|
URL: 'readonly',
|
||||||
|
URLSearchParams: 'readonly',
|
||||||
|
AbortController: 'readonly',
|
||||||
|
AbortSignal: 'readonly',
|
||||||
|
fetch: 'readonly',
|
||||||
|
Response: 'readonly',
|
||||||
|
Request: 'readonly',
|
||||||
|
Headers: 'readonly',
|
||||||
|
FormData: 'readonly',
|
||||||
|
RequestInit: 'readonly',
|
||||||
|
// Timers
|
||||||
|
setTimeout: 'readonly',
|
||||||
|
setInterval: 'readonly',
|
||||||
|
clearTimeout: 'readonly',
|
||||||
|
clearInterval: 'readonly',
|
||||||
|
setImmediate: 'readonly',
|
||||||
|
clearImmediate: 'readonly',
|
||||||
|
queueMicrotask: 'readonly',
|
||||||
|
// Node.js types
|
||||||
|
NodeJS: 'readonly',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
'@typescript-eslint': ts,
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
...ts.configs.recommended.rules,
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
caughtErrorsIgnorePattern: '^_',
|
||||||
|
ignoreRestSiblings: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
|
// Server code frequently works with terminal output containing ANSI escape codes
|
||||||
|
'no-control-regex': 'off',
|
||||||
|
'@typescript-eslint/ban-ts-comment': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
'ts-nocheck': 'allow-with-description',
|
||||||
|
minimumDescriptionLength: 10,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
globalIgnores(['dist/**', 'node_modules/**']),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export default eslintConfig;
|
||||||
@@ -37,7 +37,7 @@ export function createCheckoutBranchHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate branch name (basic validation)
|
// Validate branch name (basic validation)
|
||||||
const invalidChars = /[\s~^:?*\[\\]/;
|
const invalidChars = /[\s~^:?*[\\]/;
|
||||||
if (invalidChars.test(branchName)) {
|
if (invalidChars.test(branchName)) {
|
||||||
res.status(400).json({
|
res.status(400).json({
|
||||||
success: false,
|
success: false,
|
||||||
|
|||||||
@@ -125,19 +125,14 @@ export function createOpenInEditorHandler() {
|
|||||||
`Failed to open in editor, falling back to file manager: ${getErrorMessage(editorError)}`
|
`Failed to open in editor, falling back to file manager: ${getErrorMessage(editorError)}`
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
const result = await openInFileManager(worktreePath);
|
||||||
const result = await openInFileManager(worktreePath);
|
res.json({
|
||||||
res.json({
|
success: true,
|
||||||
success: true,
|
result: {
|
||||||
result: {
|
message: `Opened ${worktreePath} in ${result.editorName}`,
|
||||||
message: `Opened ${worktreePath} in ${result.editorName}`,
|
editorName: result.editorName,
|
||||||
editorName: result.editorName,
|
},
|
||||||
},
|
});
|
||||||
});
|
|
||||||
} catch (fallbackError) {
|
|
||||||
// Both editor and file manager failed
|
|
||||||
throw fallbackError;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, 'Open in editor failed');
|
logError(error, 'Open in editor failed');
|
||||||
|
|||||||
@@ -662,7 +662,7 @@ export class ClaudeUsageService {
|
|||||||
|
|
||||||
resetTime = this.parseResetTime(resetText, type);
|
resetTime = this.parseResetTime(resetText, type);
|
||||||
// Strip timezone like "(Asia/Dubai)" from the display text
|
// Strip timezone like "(Asia/Dubai)" from the display text
|
||||||
resetText = resetText.replace(/\s*\([A-Za-z_\/]+\)\s*$/, '').trim();
|
resetText = resetText.replace(/\s*\([A-Za-z_/]+\)\s*$/, '').trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
return { percentage: percentage ?? 0, resetTime, resetText };
|
return { percentage: percentage ?? 0, resetTime, resetText };
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ class DevServerService {
|
|||||||
/(?:Local|Network):\s+(https?:\/\/[^\s]+)/i, // Vite format
|
/(?:Local|Network):\s+(https?:\/\/[^\s]+)/i, // Vite format
|
||||||
/(?:ready|started server).*?(?:url:\s*)?(https?:\/\/[^\s,]+)/i, // Next.js format
|
/(?:ready|started server).*?(?:url:\s*)?(https?:\/\/[^\s,]+)/i, // Next.js format
|
||||||
/(https?:\/\/(?:localhost|127\.0\.0\.1|\[::\]):\d+)/i, // Generic localhost URL
|
/(https?:\/\/(?:localhost|127\.0\.0\.1|\[::\]):\d+)/i, // Generic localhost URL
|
||||||
/(https?:\/\/[^\s<>"{}|\\^`\[\]]+)/i, // Any HTTP(S) URL
|
/(https?:\/\/[^\s<>"{}|\\^`[\]]+)/i, // Any HTTP(S) URL
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const pattern of urlPatterns) {
|
for (const pattern of urlPatterns) {
|
||||||
|
|||||||
@@ -888,7 +888,7 @@ ${contextSection}${existingWorkSection}`;
|
|||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
// Check for numbered items or markdown headers
|
// Check for numbered items or markdown headers
|
||||||
const titleMatch = line.match(/^(?:\d+[\.\)]\s*\*{0,2}|#{1,3}\s+)(.+)/);
|
const titleMatch = line.match(/^(?:\d+[.)]\s*\*{0,2}|#{1,3}\s+)(.+)/);
|
||||||
|
|
||||||
if (titleMatch) {
|
if (titleMatch) {
|
||||||
// Save previous suggestion
|
// Save previous suggestion
|
||||||
|
|||||||
@@ -119,7 +119,15 @@ const eslintConfig = defineConfig([
|
|||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
...ts.configs.recommended.rules,
|
...ts.configs.recommended.rules,
|
||||||
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
argsIgnorePattern: '^_',
|
||||||
|
varsIgnorePattern: '^_',
|
||||||
|
caughtErrorsIgnorePattern: '^_',
|
||||||
|
ignoreRestSiblings: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
'@typescript-eslint/no-explicit-any': 'warn',
|
'@typescript-eslint/no-explicit-any': 'warn',
|
||||||
'@typescript-eslint/ban-ts-comment': [
|
'@typescript-eslint/ban-ts-comment': [
|
||||||
'error',
|
'error',
|
||||||
|
|||||||
@@ -1545,7 +1545,8 @@ export function BoardView() {
|
|||||||
setSpawnParentFeature(feature);
|
setSpawnParentFeature(feature);
|
||||||
setShowAddDialog(true);
|
setShowAddDialog(true);
|
||||||
}}
|
}}
|
||||||
onDuplicate={handleDuplicateFeature}
|
onDuplicate={(feature) => handleDuplicateFeature(feature, false)}
|
||||||
|
onDuplicateAsChild={(feature) => handleDuplicateFeature(feature, true)}
|
||||||
featuresWithContext={featuresWithContext}
|
featuresWithContext={featuresWithContext}
|
||||||
runningAutoTasks={runningAutoTasksAllWorktrees}
|
runningAutoTasks={runningAutoTasksAllWorktrees}
|
||||||
onArchiveAllVerified={() => setShowArchiveAllVerifiedDialog(true)}
|
onArchiveAllVerified={() => setShowArchiveAllVerifiedDialog(true)}
|
||||||
|
|||||||
@@ -31,6 +31,49 @@ import { formatModelName, DEFAULT_MODEL } from '@/lib/agent-context-parser';
|
|||||||
import { DeleteConfirmDialog } from '@/components/ui/delete-confirm-dialog';
|
import { DeleteConfirmDialog } from '@/components/ui/delete-confirm-dialog';
|
||||||
import { getProviderIconForModel } from '@/components/ui/provider-icon';
|
import { getProviderIconForModel } from '@/components/ui/provider-icon';
|
||||||
|
|
||||||
|
function DuplicateMenuItems({
|
||||||
|
onDuplicate,
|
||||||
|
onDuplicateAsChild,
|
||||||
|
}: {
|
||||||
|
onDuplicate?: () => void;
|
||||||
|
onDuplicateAsChild?: () => void;
|
||||||
|
}) {
|
||||||
|
if (!onDuplicate) return null;
|
||||||
|
return (
|
||||||
|
<DropdownMenuSub>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDuplicate();
|
||||||
|
}}
|
||||||
|
className="text-xs flex-1 pr-0 rounded-r-none"
|
||||||
|
>
|
||||||
|
<Copy className="w-3 h-3 mr-2" />
|
||||||
|
Duplicate
|
||||||
|
</DropdownMenuItem>
|
||||||
|
{onDuplicateAsChild && (
|
||||||
|
<DropdownMenuSubTrigger className="text-xs px-1 rounded-l-none border-l border-border/30 h-8" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{onDuplicateAsChild && (
|
||||||
|
<DropdownMenuSubContent>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDuplicateAsChild();
|
||||||
|
}}
|
||||||
|
className="text-xs"
|
||||||
|
>
|
||||||
|
<GitFork className="w-3 h-3 mr-2" />
|
||||||
|
Duplicate as Child
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuSubContent>
|
||||||
|
)}
|
||||||
|
</DropdownMenuSub>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface CardHeaderProps {
|
interface CardHeaderProps {
|
||||||
feature: Feature;
|
feature: Feature;
|
||||||
isDraggable: boolean;
|
isDraggable: boolean;
|
||||||
@@ -122,39 +165,10 @@ export const CardHeaderSection = memo(function CardHeaderSection({
|
|||||||
<GitFork className="w-3 h-3 mr-2" />
|
<GitFork className="w-3 h-3 mr-2" />
|
||||||
Spawn Sub-Task
|
Spawn Sub-Task
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
{onDuplicate && (
|
<DuplicateMenuItems
|
||||||
<DropdownMenuSub>
|
onDuplicate={onDuplicate}
|
||||||
<div className="flex items-center">
|
onDuplicateAsChild={onDuplicateAsChild}
|
||||||
<DropdownMenuItem
|
/>
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onDuplicate();
|
|
||||||
}}
|
|
||||||
className="text-xs flex-1 pr-0 rounded-r-none"
|
|
||||||
>
|
|
||||||
<Copy className="w-3 h-3 mr-2" />
|
|
||||||
Duplicate
|
|
||||||
</DropdownMenuItem>
|
|
||||||
{onDuplicateAsChild && (
|
|
||||||
<DropdownMenuSubTrigger className="text-xs px-1 rounded-l-none border-l border-border/30 h-8" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{onDuplicateAsChild && (
|
|
||||||
<DropdownMenuSubContent>
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onDuplicateAsChild();
|
|
||||||
}}
|
|
||||||
className="text-xs"
|
|
||||||
>
|
|
||||||
<GitFork className="w-3 h-3 mr-2" />
|
|
||||||
Duplicate as Child
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuSubContent>
|
|
||||||
)}
|
|
||||||
</DropdownMenuSub>
|
|
||||||
)}
|
|
||||||
{/* Model info in dropdown */}
|
{/* Model info in dropdown */}
|
||||||
{(() => {
|
{(() => {
|
||||||
const ProviderIcon = getProviderIconForModel(feature.model);
|
const ProviderIcon = getProviderIconForModel(feature.model);
|
||||||
@@ -217,39 +231,10 @@ export const CardHeaderSection = memo(function CardHeaderSection({
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end" className="w-40">
|
<DropdownMenuContent align="end" className="w-40">
|
||||||
{onDuplicate && (
|
<DuplicateMenuItems
|
||||||
<DropdownMenuSub>
|
onDuplicate={onDuplicate}
|
||||||
<div className="flex items-center">
|
onDuplicateAsChild={onDuplicateAsChild}
|
||||||
<DropdownMenuItem
|
/>
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onDuplicate();
|
|
||||||
}}
|
|
||||||
className="text-xs flex-1 pr-0 rounded-r-none"
|
|
||||||
>
|
|
||||||
<Copy className="w-3 h-3 mr-2" />
|
|
||||||
Duplicate
|
|
||||||
</DropdownMenuItem>
|
|
||||||
{onDuplicateAsChild && (
|
|
||||||
<DropdownMenuSubTrigger className="text-xs px-1 rounded-l-none border-l border-border/30 h-8" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{onDuplicateAsChild && (
|
|
||||||
<DropdownMenuSubContent>
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onDuplicateAsChild();
|
|
||||||
}}
|
|
||||||
className="text-xs"
|
|
||||||
>
|
|
||||||
<GitFork className="w-3 h-3 mr-2" />
|
|
||||||
Duplicate as Child
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuSubContent>
|
|
||||||
)}
|
|
||||||
</DropdownMenuSub>
|
|
||||||
)}
|
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
@@ -337,39 +322,10 @@ export const CardHeaderSection = memo(function CardHeaderSection({
|
|||||||
<GitFork className="w-3 h-3 mr-2" />
|
<GitFork className="w-3 h-3 mr-2" />
|
||||||
Spawn Sub-Task
|
Spawn Sub-Task
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
{onDuplicate && (
|
<DuplicateMenuItems
|
||||||
<DropdownMenuSub>
|
onDuplicate={onDuplicate}
|
||||||
<div className="flex items-center">
|
onDuplicateAsChild={onDuplicateAsChild}
|
||||||
<DropdownMenuItem
|
/>
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onDuplicate();
|
|
||||||
}}
|
|
||||||
className="text-xs flex-1 pr-0 rounded-r-none"
|
|
||||||
>
|
|
||||||
<Copy className="w-3 h-3 mr-2" />
|
|
||||||
Duplicate
|
|
||||||
</DropdownMenuItem>
|
|
||||||
{onDuplicateAsChild && (
|
|
||||||
<DropdownMenuSubTrigger className="text-xs px-1 rounded-l-none border-l border-border/30 h-8" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{onDuplicateAsChild && (
|
|
||||||
<DropdownMenuSubContent>
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onDuplicateAsChild();
|
|
||||||
}}
|
|
||||||
className="text-xs"
|
|
||||||
>
|
|
||||||
<GitFork className="w-3 h-3 mr-2" />
|
|
||||||
Duplicate as Child
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuSubContent>
|
|
||||||
)}
|
|
||||||
</DropdownMenuSub>
|
|
||||||
)}
|
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
@@ -440,39 +396,10 @@ export const CardHeaderSection = memo(function CardHeaderSection({
|
|||||||
<GitFork className="w-3 h-3 mr-2" />
|
<GitFork className="w-3 h-3 mr-2" />
|
||||||
Spawn Sub-Task
|
Spawn Sub-Task
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
{onDuplicate && (
|
<DuplicateMenuItems
|
||||||
<DropdownMenuSub>
|
onDuplicate={onDuplicate}
|
||||||
<div className="flex items-center">
|
onDuplicateAsChild={onDuplicateAsChild}
|
||||||
<DropdownMenuItem
|
/>
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onDuplicate();
|
|
||||||
}}
|
|
||||||
className="text-xs flex-1 pr-0 rounded-r-none"
|
|
||||||
>
|
|
||||||
<Copy className="w-3 h-3 mr-2" />
|
|
||||||
Duplicate
|
|
||||||
</DropdownMenuItem>
|
|
||||||
{onDuplicateAsChild && (
|
|
||||||
<DropdownMenuSubTrigger className="text-xs px-1 rounded-l-none border-l border-border/30 h-8" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{onDuplicateAsChild && (
|
|
||||||
<DropdownMenuSubContent>
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onDuplicateAsChild();
|
|
||||||
}}
|
|
||||||
className="text-xs"
|
|
||||||
>
|
|
||||||
<GitFork className="w-3 h-3 mr-2" />
|
|
||||||
Duplicate as Child
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuSubContent>
|
|
||||||
)}
|
|
||||||
</DropdownMenuSub>
|
|
||||||
)}
|
|
||||||
{/* Model info in dropdown */}
|
{/* Model info in dropdown */}
|
||||||
{(() => {
|
{(() => {
|
||||||
const ProviderIcon = getProviderIconForModel(feature.model);
|
const ProviderIcon = getProviderIconForModel(feature.model);
|
||||||
|
|||||||
@@ -85,6 +85,11 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps
|
|||||||
throw new Error('Features API not available');
|
throw new Error('Features API not available');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Capture previous cache snapshot for synchronous rollback on error
|
||||||
|
const previousFeatures = queryClient.getQueryData<Feature[]>(
|
||||||
|
queryKeys.features.all(currentProject.path)
|
||||||
|
);
|
||||||
|
|
||||||
// Optimistically add to React Query cache for immediate board refresh
|
// Optimistically add to React Query cache for immediate board refresh
|
||||||
queryClient.setQueryData<Feature[]>(
|
queryClient.setQueryData<Feature[]>(
|
||||||
queryKeys.features.all(currentProject.path),
|
queryKeys.features.all(currentProject.path),
|
||||||
@@ -95,6 +100,16 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps
|
|||||||
const result = await api.features.create(currentProject.path, feature as ApiFeature);
|
const result = await api.features.create(currentProject.path, feature as ApiFeature);
|
||||||
if (result.success && result.feature) {
|
if (result.success && result.feature) {
|
||||||
updateFeature(result.feature.id, result.feature as Partial<Feature>);
|
updateFeature(result.feature.id, result.feature as Partial<Feature>);
|
||||||
|
// Update cache with server-confirmed feature before invalidating
|
||||||
|
queryClient.setQueryData<Feature[]>(
|
||||||
|
queryKeys.features.all(currentProject.path),
|
||||||
|
(features) => {
|
||||||
|
if (!features) return features;
|
||||||
|
return features.map((f) =>
|
||||||
|
f.id === result.feature!.id ? { ...f, ...(result.feature as Feature) } : f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
} else if (!result.success) {
|
} else if (!result.success) {
|
||||||
throw new Error(result.error || 'Failed to create feature on server');
|
throw new Error(result.error || 'Failed to create feature on server');
|
||||||
}
|
}
|
||||||
@@ -104,7 +119,10 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to persist feature creation:', error);
|
logger.error('Failed to persist feature creation:', error);
|
||||||
// Rollback optimistic update on error
|
// Rollback optimistic update synchronously on error
|
||||||
|
if (previousFeatures) {
|
||||||
|
queryClient.setQueryData(queryKeys.features.all(currentProject.path), previousFeatures);
|
||||||
|
}
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: queryKeys.features.all(currentProject.path),
|
queryKey: queryKeys.features.all(currentProject.path),
|
||||||
});
|
});
|
||||||
@@ -131,7 +149,6 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps
|
|||||||
try {
|
try {
|
||||||
const api = getElectronAPI();
|
const api = getElectronAPI();
|
||||||
if (!api.features) {
|
if (!api.features) {
|
||||||
logger.error('Features API not available');
|
|
||||||
// Rollback optimistic deletion since we can't persist
|
// Rollback optimistic deletion since we can't persist
|
||||||
if (previousFeatures) {
|
if (previousFeatures) {
|
||||||
queryClient.setQueryData(queryKeys.features.all(currentProject.path), previousFeatures);
|
queryClient.setQueryData(queryKeys.features.all(currentProject.path), previousFeatures);
|
||||||
@@ -139,7 +156,7 @@ export function useBoardPersistence({ currentProject }: UseBoardPersistenceProps
|
|||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: queryKeys.features.all(currentProject.path),
|
queryKey: queryKeys.features.all(currentProject.path),
|
||||||
});
|
});
|
||||||
return;
|
throw new Error('Features API not available');
|
||||||
}
|
}
|
||||||
|
|
||||||
await api.features.delete(currentProject.path, featureId);
|
await api.features.delete(currentProject.path, featureId);
|
||||||
|
|||||||
@@ -46,7 +46,8 @@ interface KanbanBoardProps {
|
|||||||
onViewPlan: (feature: Feature) => void;
|
onViewPlan: (feature: Feature) => void;
|
||||||
onApprovePlan: (feature: Feature) => void;
|
onApprovePlan: (feature: Feature) => void;
|
||||||
onSpawnTask?: (feature: Feature) => void;
|
onSpawnTask?: (feature: Feature) => void;
|
||||||
onDuplicate?: (feature: Feature, asChild: boolean) => void;
|
onDuplicate?: (feature: Feature) => void;
|
||||||
|
onDuplicateAsChild?: (feature: Feature) => void;
|
||||||
featuresWithContext: Set<string>;
|
featuresWithContext: Set<string>;
|
||||||
runningAutoTasks: string[];
|
runningAutoTasks: string[];
|
||||||
onArchiveAllVerified: () => void;
|
onArchiveAllVerified: () => void;
|
||||||
@@ -284,6 +285,7 @@ export function KanbanBoard({
|
|||||||
onApprovePlan,
|
onApprovePlan,
|
||||||
onSpawnTask,
|
onSpawnTask,
|
||||||
onDuplicate,
|
onDuplicate,
|
||||||
|
onDuplicateAsChild,
|
||||||
featuresWithContext,
|
featuresWithContext,
|
||||||
runningAutoTasks,
|
runningAutoTasks,
|
||||||
onArchiveAllVerified,
|
onArchiveAllVerified,
|
||||||
@@ -571,8 +573,8 @@ export function KanbanBoard({
|
|||||||
onViewPlan={() => onViewPlan(feature)}
|
onViewPlan={() => onViewPlan(feature)}
|
||||||
onApprovePlan={() => onApprovePlan(feature)}
|
onApprovePlan={() => onApprovePlan(feature)}
|
||||||
onSpawnTask={() => onSpawnTask?.(feature)}
|
onSpawnTask={() => onSpawnTask?.(feature)}
|
||||||
onDuplicate={() => onDuplicate?.(feature, false)}
|
onDuplicate={() => onDuplicate?.(feature)}
|
||||||
onDuplicateAsChild={() => onDuplicate?.(feature, true)}
|
onDuplicateAsChild={() => onDuplicateAsChild?.(feature)}
|
||||||
hasContext={featuresWithContext.has(feature.id)}
|
hasContext={featuresWithContext.has(feature.id)}
|
||||||
isCurrentAutoTask={runningAutoTasks.includes(feature.id)}
|
isCurrentAutoTask={runningAutoTasks.includes(feature.id)}
|
||||||
shortcutKey={shortcutKey}
|
shortcutKey={shortcutKey}
|
||||||
@@ -615,8 +617,8 @@ export function KanbanBoard({
|
|||||||
onViewPlan={() => onViewPlan(feature)}
|
onViewPlan={() => onViewPlan(feature)}
|
||||||
onApprovePlan={() => onApprovePlan(feature)}
|
onApprovePlan={() => onApprovePlan(feature)}
|
||||||
onSpawnTask={() => onSpawnTask?.(feature)}
|
onSpawnTask={() => onSpawnTask?.(feature)}
|
||||||
onDuplicate={() => onDuplicate?.(feature, false)}
|
onDuplicate={() => onDuplicate?.(feature)}
|
||||||
onDuplicateAsChild={() => onDuplicate?.(feature, true)}
|
onDuplicateAsChild={() => onDuplicateAsChild?.(feature)}
|
||||||
hasContext={featuresWithContext.has(feature.id)}
|
hasContext={featuresWithContext.has(feature.id)}
|
||||||
isCurrentAutoTask={runningAutoTasks.includes(feature.id)}
|
isCurrentAutoTask={runningAutoTasks.includes(feature.id)}
|
||||||
shortcutKey={shortcutKey}
|
shortcutKey={shortcutKey}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ function featureToInternal(feature: Feature): FeatureWithId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function internalToFeature(internal: FeatureWithId): Feature {
|
function internalToFeature(internal: FeatureWithId): Feature {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { _id, _locationIds, ...feature } = internal;
|
const { _id, _locationIds, ...feature } = internal;
|
||||||
return feature;
|
return feature;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ function phaseToInternal(phase: RoadmapPhase): PhaseWithId {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function internalToPhase(internal: PhaseWithId): RoadmapPhase {
|
function internalToPhase(internal: PhaseWithId): RoadmapPhase {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { _id, ...phase } = internal;
|
const { _id, ...phase } = internal;
|
||||||
return phase;
|
return phase;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1062,7 +1062,6 @@ if (typeof window !== 'undefined') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mock API for development/fallback when no backend is available
|
// Mock API for development/fallback when no backend is available
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const _getMockElectronAPI = (): ElectronAPI => {
|
const _getMockElectronAPI = (): ElectronAPI => {
|
||||||
return {
|
return {
|
||||||
ping: async () => 'pong (mock)',
|
ping: async () => 'pong (mock)',
|
||||||
|
|||||||
@@ -155,7 +155,6 @@ export const useTestRunnersStore = create<TestRunnersState & TestRunnersActions>
|
|||||||
const finishedAt = new Date().toISOString();
|
const finishedAt = new Date().toISOString();
|
||||||
|
|
||||||
// Remove from active sessions since it's no longer running
|
// Remove from active sessions since it's no longer running
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { [session.worktreePath]: _, ...remainingActive } = state.activeSessionByWorktree;
|
const { [session.worktreePath]: _, ...remainingActive } = state.activeSessionByWorktree;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -202,7 +201,6 @@ export const useTestRunnersStore = create<TestRunnersState & TestRunnersActions>
|
|||||||
const session = state.sessions[sessionId];
|
const session = state.sessions[sessionId];
|
||||||
if (!session) return state;
|
if (!session) return state;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { [sessionId]: _, ...remainingSessions } = state.sessions;
|
const { [sessionId]: _, ...remainingSessions } = state.sessions;
|
||||||
|
|
||||||
// Remove from active if this was the active session
|
// Remove from active if this was the active session
|
||||||
@@ -231,7 +229,6 @@ export const useTestRunnersStore = create<TestRunnersState & TestRunnersActions>
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Remove from active
|
// Remove from active
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const { [worktreePath]: _, ...remainingActive } = state.activeSessionByWorktree;
|
const { [worktreePath]: _, ...remainingActive } = state.activeSessionByWorktree;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user