Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
Thumbs.db
ehthumbs.db
Desktop.ini
nul
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n .gitignore | head -20

Repository: AndyMik90/Auto-Claude

Length of output: 527


🏁 Script executed:

git log --oneline -p -- .gitignore | head -100

Repository: AndyMik90/Auto-Claude

Length of output: 1681


🏁 Script executed:

# Search for any references to "nul" or ".bill" in the codebase
rg "nul|\.bill" --type-list | head -20

Repository: AndyMik90/Auto-Claude

Length of output: 454


🏁 Script executed:

# Search for any documentation or comments about these entries
rg -i "nul|bill" --type md --type txt | head -50

Repository: AndyMik90/Auto-Claude

Length of output: 5472


🏁 Script executed:

# Search for any actual "nul" files or references in the codebase
fd nul

Repository: AndyMik90/Auto-Claude

Length of output: 47


🏁 Script executed:

# Search for ".bill" directory or references
fd bill

Repository: AndyMik90/Auto-Claude

Length of output: 47


🏁 Script executed:

# Check if there are any comments or context in the PR/commit about these specific entries
git log --all --oneline --grep="bill\|nul" 2>/dev/null

Repository: AndyMik90/Auto-Claude

Length of output: 1965


🏁 Script executed:

# Look for any README or documentation about .bill directory
find . -type f \( -name "README*" -o -name "*.md" \) -exec grep -l "\.bill\|bill" {} \; 2>/dev/null

Repository: AndyMik90/Auto-Claude

Length of output: 47


🏁 Script executed:

# Check if there's any issue or PR description that might explain these entries
git show 1670dd5 --format=fuller 2>/dev/null | head -50

Repository: AndyMik90/Auto-Claude

Length of output: 2317


🏁 Script executed:

# Check if "nul" appears in any other repositories or gitignore best practices
# Also check if this was copied from somewhere
rg "nul" --type-list 2>&1 | head -5

Repository: AndyMik90/Auto-Claude

Length of output: 179


🏁 Script executed:

# Search for any references to this being a Windows-specific issue or tool
rg -i "windows|device|reserved" -- .gitignore

Repository: AndyMik90/Auto-Claude

Length of output: 47


🏁 Script executed:

# Check the full context of the .gitignore to see the coding guideline compliance
cat -n .gitignore | grep -A 5 -B 5 "auto-claude"

Repository: AndyMik90/Auto-Claude

Length of output: 445


Clarify the purpose of nul and .bill/ entries.

The nul entry at line 10 is a reserved Windows device name that cannot be created as a regular file. This is an unusual .gitignore pattern that lacks explanation.

Similarly, the .bill/ entry at line 15 appears to be a personal development directory but has no documented purpose or relation to the PR's stated objectives (human review checkpoint feature).

Please clarify why these entries are needed and whether they should remain in the repository-wide .gitignore.

🤖 Prompt for AI Agents
In @.gitignore at line 10, The .gitignore contains unusual entries "nul" and
".bill/" that lack context; update the repository-wide .gitignore by either
removing these entries if they are not needed globally, or add a short comment
above each entry explaining its purpose and scope (e.g., Windows reserved device
exclusion for "nul" or a developer-specific directory for ".bill/") so reviewers
understand why "nul" and ".bill/" remain; ensure the comment clarifies whether
the entry is global or should be moved to a local/developer-specific ignore
file.


# ===========================
# Local Development
# ===========================
.bill/

# ===========================
# Security - Environment & Secrets
Expand Down
7 changes: 5 additions & 2 deletions apps/backend/implementation_plan/plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,12 @@ def update_status_from_subtasks(self):
self.planStatus = "in_progress"
else:
# All subtasks pending
# Preserve human_review/review status if it's for plan approval stage
# Preserve review status if it's for plan approval stage
# (spec is complete, waiting for user to approve before coding starts)
if self.status == "human_review" and self.planStatus == "review":
# This covers both:
# - human_review + review: legacy plan review state
# - stopped + awaiting_review: new plan review state (set by spec_runner.py --no-build)
if self.status in ("human_review", "stopped") and self.planStatus in ("review", "awaiting_review"):
# Keep the plan approval status - don't reset to backlog
pass
else:
Expand Down
29 changes: 29 additions & 0 deletions apps/backend/runners/spec_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

import asyncio
import io
import json
import os
from pathlib import Path

Expand Down Expand Up @@ -372,6 +373,34 @@ def main():
# Execute run.py - replace current process
os.execv(sys.executable, run_cmd)

else:
# --no-build specified: Set planStatus to 'awaiting_review' to signal frontend
# that planning is complete and user review is required before coding starts
debug("spec_runner", "--no-build flag detected, setting awaiting_review status")
print()
print_status("--no-build flag: Setting plan to awaiting review", "info")

plan_path = orchestrator.spec_dir / "implementation_plan.json"
debug("spec_runner", f"Looking for plan file at: {plan_path}")

if plan_path.exists():
try:
plan_data = json.loads(plan_path.read_text(encoding="utf-8"))
plan_data["planStatus"] = "awaiting_review"
plan_data["status"] = "stopped" # Mark as stopped for review
plan_path.write_text(json.dumps(plan_data, indent=2, ensure_ascii=False), encoding="utf-8")
debug("spec_runner", "Set planStatus to 'awaiting_review' for frontend")
print()
print_status("Planning complete. Awaiting review before coding.", "success")
print(f" {muted('Review the spec at:')} {orchestrator.spec_dir / 'spec.md'}")
print(f" {muted('When ready, restart the task in the UI to begin coding.')}")
except (json.JSONDecodeError, OSError) as e:
debug_error("spec_runner", f"Failed to update plan status: {e}")
print_status(f"Failed to update plan status: {e}", "error")
else:
debug_error("spec_runner", f"Plan file not found at: {plan_path}")
print_status(f"Warning: implementation_plan.json not found at {plan_path}", "warning")

sys.exit(0)

except KeyboardInterrupt:
Expand Down
1 change: 1 addition & 0 deletions apps/backend/task_logger/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ def update_phase_status(
self._data["phases"][phase]["status"] = status
if completed_at:
self._data["phases"][phase]["completed_at"] = completed_at
self.save()

def set_phase_started(self, phase: str, started_at: str) -> None:
"""
Expand Down
14 changes: 11 additions & 3 deletions apps/frontend/src/main/agent/agent-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,14 @@ export class AgentManager extends EventEmitter {
}

// Check if user requires review before coding
if (!metadata?.requireReviewBeforeCoding) {
if (metadata?.requireReviewBeforeCoding) {
// Human review required: Stop after spec creation, don't start build
// Frontend will handle the review gate and resume coding after approval
// NOTE: Also pass --auto-approve so the spec gets approved during creation
// (otherwise spec_runner exits early with "spec not approved" error)
args.push('--no-build');
args.push('--auto-approve');
} else {
// Auto-approve: When user starts a task from the UI without requiring review
args.push('--auto-approve');
}
Expand All @@ -175,8 +182,9 @@ export class AgentManager extends EventEmitter {
// Store context for potential restart
this.storeTaskContext(taskId, projectPath, '', {}, true, taskDescription, specDir, metadata, baseBranch);

// Note: This is spec-creation but it chains to task-execution via run.py
await this.processManager.spawnProcess(taskId, autoBuildSource, args, combinedEnv, 'task-execution');
// Use 'spec-creation' processType so exit handler can distinguish planning from coding
// This prevents infinite loop when auto-continue triggers after coding completes
await this.processManager.spawnProcess(taskId, autoBuildSource, args, combinedEnv, 'spec-creation');
}

/**
Expand Down
100 changes: 94 additions & 6 deletions apps/frontend/src/main/ipc-handlers/agent-events-handlers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { BrowserWindow } from "electron";
import path from "path";
import { existsSync } from "fs";
import { existsSync, readFileSync, writeFileSync } from "fs";
import { IPC_CHANNELS, AUTO_BUILD_PATHS, getSpecsDir } from "../../shared/constants";
import {
wouldPhaseRegress,
Expand All @@ -22,7 +22,7 @@ import { titleGenerator } from "../title-generator";
import { fileWatcher } from "../file-watcher";
import { projectStore } from "../project-store";
import { notificationService } from "../notification-service";
import { persistPlanStatusSync, getPlanPath } from "./task/plan-file-utils";
import { persistPlanStatusSync, getPlanPath, createPlanIfNotExists } from "./task/plan-file-utils";
import { findTaskWorktree } from "../worktree-paths";
import { findTaskAndProject } from "./task/shared";
import { safeSendToRenderer } from "./utils";
Expand Down Expand Up @@ -135,7 +135,7 @@ export function registerAgenteventsHandlers(

agentManager.on("exit", (taskId: string, code: number | null, processType: ProcessType) => {
// Get project info early for multi-project filtering (issue #723)
const { project: exitProject } = findTaskAndProject(taskId);
const { project: exitProject, task: exitTask } = findTaskAndProject(taskId);
const exitProjectId = exitProject?.id;

// Send final plan state to renderer BEFORE unwatching
Expand All @@ -153,9 +153,97 @@ export function registerAgenteventsHandlers(

fileWatcher.unwatch(taskId);

if (processType === "spec-creation") {
console.warn(`[Task ${taskId}] Spec creation completed with code ${code}`);
return;
// FIX: Planning-to-coding transition (Issue #1231)
// Only run this logic for spec-creation process (planning phase).
// This prevents infinite loop: without this check, when coding (task-execution) completes,
// this handler would incorrectly call startTaskExecution again, causing an infinite loop.
// The processType check ensures we only auto-continue from planning → coding, not coding → coding.
if (processType === 'spec-creation' && (code === 0 || code === 1) && exitProject && exitTask) {
const specsBaseDir = getSpecsDir(exitProject.autoBuildPath);
const specDir = path.join(exitProject.path, specsBaseDir, exitTask.specId);
const specFilePath = path.join(specDir, AUTO_BUILD_PATHS.SPEC_FILE);
const planPath = path.join(specDir, AUTO_BUILD_PATHS.IMPLEMENTATION_PLAN);
const metadataPath = path.join(specDir, "task_metadata.json");

// Check if spec and plan exist (planning completed)
const hasSpec = existsSync(specFilePath);
const hasPlan = existsSync(planPath);

if (hasSpec && hasPlan) {
// Read metadata and plan once to avoid multiple file reads
let metadata: Record<string, unknown> = {};
let planContent: Record<string, unknown> = {};

if (existsSync(metadataPath)) {
try {
metadata = JSON.parse(readFileSync(metadataPath, "utf-8"));
} catch (err) {
console.error(`[Task ${taskId}] Failed to read metadata:`, err);
}
}

try {
planContent = JSON.parse(readFileSync(planPath, "utf-8"));
} catch (err) {
console.error(`[Task ${taskId}] Failed to read plan:`, err);
}

const requireReviewBeforeCoding = metadata.requireReviewBeforeCoding === true;
const allSubtasks = ((planContent.phases as Array<{ subtasks?: unknown[] }>) || [])
.flatMap((p) => p.subtasks || []);
const planHasSubtasks = allSubtasks.length > 0;

Comment on lines 173 to 196
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider logging when planning-to-coding transition is skipped due to read failures.

If both metadata and plan reads fail silently, the transition logic effectively becomes a no-op. The task continues without stopping for review or auto-continuing, which may leave it in an ambiguous state. Adding a log when the block exits without taking action improves debuggability.

💡 Optional improvement
         const planHasSubtasks = allSubtasks.length > 0;
 
+        if (!planHasSubtasks) {
+          console.warn(`[Task ${taskId}] Plan exists but has no subtasks - skipping transition logic`);
+        }
+
         if (planHasSubtasks && requireReviewBeforeCoding) {
🤖 Prompt for AI Agents
In `@apps/frontend/src/main/ipc-handlers/agent-events-handlers.ts` around lines
172 - 195, The reads for metadata and plan (reading metadataPath into metadata
and planPath into planContent) can both fail silently causing
requireReviewBeforeCoding and planHasSubtasks logic to be unreliable; update the
block that reads metadata and plan (the code around hasSpec/hasPlan, metadata,
planContent, metadataPath, planPath) to detect and log when either or both reads
failed (include taskId and which path(s) failed or parse errors), and emit a
clear warning that the planning->coding transition is being skipped due to read
failures so operators can debug the ambiguous task state.

if (planHasSubtasks && requireReviewBeforeCoding) {
// User wants to review before coding - STOP the task
// Note: spec_runner.py with --no-build already sets these values, but we set them
// here as a fallback in case the Python process didn't complete the update
try {
metadata.stoppedForPlanReview = true;
writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
} catch (err) {
console.error(`[Task ${taskId}] Failed to update task_metadata.json:`, err);
}

try {
planContent.status = "stopped";
planContent.planStatus = "awaiting_review";
writeFileSync(planPath, JSON.stringify(planContent, null, 2));
} catch (err) {
console.error(`[Task ${taskId}] Failed to update implementation_plan.json:`, err);
}

// Invalidate cache so UI reflects the new status
projectStore.invalidateTasksCache(exitProject.id);

// Notify renderer of status change
safeSendToRenderer(
getMainWindow,
IPC_CHANNELS.TASK_STATUS_CHANGE,
taskId,
"stopped" as TaskStatus,
exitProjectId
);

return;
}

if (planHasSubtasks && !requireReviewBeforeCoding) {
// Auto-continue to coding phase
const baseBranch = (exitTask.metadata?.baseBranch as string | undefined) || exitProject.settings?.mainBranch;
agentManager.startTaskExecution(
taskId,
exitProject.path,
exitTask.specId,
{
parallel: false,
workers: 1,
baseBranch,
useWorktree: exitTask.metadata?.useWorktree as boolean | undefined
}
);
return;
}
}
}

let task: Task | undefined;
Expand Down
45 changes: 45 additions & 0 deletions apps/frontend/src/main/ipc-handlers/settings-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,51 @@ export function registerSettingsHandlers(
}
);

ipcMain.handle(
IPC_CHANNELS.SHELL_OPEN_PATH,
async (_, filePath: string): Promise<IPCResult<string>> => {
try {
// Validate filePath input
if (!filePath || typeof filePath !== 'string' || filePath.trim() === '') {
return {
success: false,
error: 'File path is required and must be a non-empty string'
};
}

// Resolve to absolute path
const resolvedPath = path.resolve(filePath);

// Verify path exists
if (!existsSync(resolvedPath)) {
return {
success: false,
error: `File does not exist: ${resolvedPath}`
};
}

// Open with system default application
const result = await shell.openPath(resolvedPath);

// shell.openPath returns empty string on success, error message on failure
if (result) {
return {
success: false,
error: result
};
}

return { success: true, data: resolvedPath };
} catch (error) {
const errorMsg = error instanceof Error ? error.message : 'Unknown error';
return {
success: false,
error: `Failed to open file: ${errorMsg}`
};
}
}
);

// ============================================
// Auto-Build Source Environment Operations
// ============================================
Expand Down
15 changes: 15 additions & 0 deletions apps/frontend/src/main/ipc-handlers/task/execution-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,21 @@ export function registerTaskExecutionHandlers(
// Use default description
}

// Clear stoppedForPlanReview flag if it was set (user is resuming after review)
if (task.metadata?.stoppedForPlanReview) {
const metadataPath = path.join(specDir, 'task_metadata.json');
try {
if (existsSync(metadataPath)) {
const metadata = JSON.parse(readFileSync(metadataPath, 'utf-8'));
metadata.stoppedForPlanReview = false;
writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
console.warn('[TASK_START] Cleared stoppedForPlanReview flag');
}
} catch (err) {
console.error('[TASK_START] Failed to clear stoppedForPlanReview flag:', err);
}
}

console.warn('[TASK_START] Starting task execution (no subtasks) for:', task.specId);
// Start task execution which will create the implementation plan
// Note: No parallel mode for planning phase - parallel only makes sense with multiple subtasks
Expand Down
5 changes: 4 additions & 1 deletion apps/frontend/src/preload/api/modules/shell-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { IPCResult } from '../../../shared/types';
export interface ShellAPI {
openExternal: (url: string) => Promise<void>;
openTerminal: (dirPath: string) => Promise<IPCResult<void>>;
openPath: (filePath: string) => Promise<IPCResult<string>>;
}

/**
Expand All @@ -17,5 +18,7 @@ export const createShellAPI = (): ShellAPI => ({
openExternal: (url: string): Promise<void> =>
invokeIpc(IPC_CHANNELS.SHELL_OPEN_EXTERNAL, url),
openTerminal: (dirPath: string): Promise<IPCResult<void>> =>
invokeIpc(IPC_CHANNELS.SHELL_OPEN_TERMINAL, dirPath)
invokeIpc(IPC_CHANNELS.SHELL_OPEN_TERMINAL, dirPath),
openPath: (filePath: string): Promise<IPCResult<string>> =>
invokeIpc(IPC_CHANNELS.SHELL_OPEN_PATH, filePath)
});
4 changes: 3 additions & 1 deletion apps/frontend/src/renderer/__tests__/task-order.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
human_review: [],
pr_created: [],
done: [],
stopped: [],
...overrides
};
}
Expand Down Expand Up @@ -243,7 +244,7 @@
it('should create empty task order if no stored order exists', () => {
useTaskStore.getState().loadTaskOrder('project-1');

expect(useTaskStore.getState().taskOrder).toEqual({

Check failure on line 247 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (macos-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > loadTaskOrder > should create empty task order if no stored order exists

AssertionError: expected { backlog: [], in_progress: [], …(5) } to deeply equal { backlog: [], in_progress: [], …(4) } - Expected + Received @@ -3,6 +3,7 @@ "backlog": [], "done": [], "human_review": [], "in_progress": [], "pr_created": [], + "stopped": [], } ❯ src/renderer/__tests__/task-order.test.ts:247:49

Check failure on line 247 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (ubuntu-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > loadTaskOrder > should create empty task order if no stored order exists

AssertionError: expected { backlog: [], in_progress: [], …(5) } to deeply equal { backlog: [], in_progress: [], …(4) } - Expected + Received @@ -3,6 +3,7 @@ "backlog": [], "done": [], "human_review": [], "in_progress": [], "pr_created": [], + "stopped": [], } ❯ src/renderer/__tests__/task-order.test.ts:247:49

Check failure on line 247 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (windows-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > loadTaskOrder > should create empty task order if no stored order exists

AssertionError: expected { backlog: [], in_progress: [], …(5) } to deeply equal { backlog: [], in_progress: [], …(4) } - Expected + Received @@ -3,6 +3,7 @@ "backlog": [], "done": [], "human_review": [], "in_progress": [], "pr_created": [], + "stopped": [], } ❯ src/renderer/__tests__/task-order.test.ts:247:49
backlog: [],
in_progress: [],
ai_review: [],
Expand Down Expand Up @@ -275,7 +276,7 @@
useTaskStore.getState().loadTaskOrder('project-1');

// Should fall back to empty order state
expect(useTaskStore.getState().taskOrder).toEqual({

Check failure on line 279 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (macos-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > loadTaskOrder > should handle corrupted localStorage data gracefully

AssertionError: expected { backlog: [], in_progress: [], …(5) } to deeply equal { backlog: [], in_progress: [], …(4) } - Expected + Received @@ -3,6 +3,7 @@ "backlog": [], "done": [], "human_review": [], "in_progress": [], "pr_created": [], + "stopped": [], } ❯ src/renderer/__tests__/task-order.test.ts:279:49

Check failure on line 279 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (ubuntu-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > loadTaskOrder > should handle corrupted localStorage data gracefully

AssertionError: expected { backlog: [], in_progress: [], …(5) } to deeply equal { backlog: [], in_progress: [], …(4) } - Expected + Received @@ -3,6 +3,7 @@ "backlog": [], "done": [], "human_review": [], "in_progress": [], "pr_created": [], + "stopped": [], } ❯ src/renderer/__tests__/task-order.test.ts:279:49

Check failure on line 279 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (windows-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > loadTaskOrder > should handle corrupted localStorage data gracefully

AssertionError: expected { backlog: [], in_progress: [], …(5) } to deeply equal { backlog: [], in_progress: [], …(4) } - Expected + Received @@ -3,6 +3,7 @@ "backlog": [], "done": [], "human_review": [], "in_progress": [], "pr_created": [], + "stopped": [], } ❯ src/renderer/__tests__/task-order.test.ts:279:49
backlog: [],
in_progress: [],
ai_review: [],
Expand All @@ -295,13 +296,13 @@
// Mock localStorage.getItem to throw
const originalGetItem = localStorage.getItem;
localStorage.getItem = vi.fn(() => {
throw new Error('Storage quota exceeded');

Check failure on line 299 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (macos-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > clearTaskOrder > should use project-specific localStorage keys

Error: Storage quota exceeded ❯ Object.<anonymous> src/renderer/__tests__/task-order.test.ts:299:15 ❯ src/renderer/__tests__/task-order.test.ts:409:27

Check failure on line 299 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (macos-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > clearTaskOrder > should clear task order from localStorage

Error: Storage quota exceeded ❯ Object.<anonymous> src/renderer/__tests__/task-order.test.ts:299:15 ❯ src/renderer/__tests__/task-order.test.ts:399:27

Check failure on line 299 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (macos-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > saveTaskOrder > should overwrite existing stored order

Error: Storage quota exceeded ❯ Object.<anonymous> src/renderer/__tests__/task-order.test.ts:299:15 ❯ src/renderer/__tests__/task-order.test.ts:386:46

Check failure on line 299 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (macos-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > saveTaskOrder > should use project-specific localStorage keys

Error: Storage quota exceeded ❯ Object.<anonymous> src/renderer/__tests__/task-order.test.ts:299:15 ❯ src/renderer/__tests__/task-order.test.ts:349:27

Check failure on line 299 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (macos-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > saveTaskOrder > should not save if taskOrder is null

Error: Storage quota exceeded ❯ Object.<anonymous> src/renderer/__tests__/task-order.test.ts:299:15 ❯ src/renderer/__tests__/task-order.test.ts:339:35

Check failure on line 299 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (macos-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > saveTaskOrder > should save task order to localStorage

Error: Storage quota exceeded ❯ Object.<anonymous> src/renderer/__tests__/task-order.test.ts:299:15 ❯ src/renderer/__tests__/task-order.test.ts:329:35

Check failure on line 299 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (ubuntu-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > clearTaskOrder > should use project-specific localStorage keys

Error: Storage quota exceeded ❯ Object.<anonymous> src/renderer/__tests__/task-order.test.ts:299:15 ❯ src/renderer/__tests__/task-order.test.ts:409:27

Check failure on line 299 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (ubuntu-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > clearTaskOrder > should clear task order from localStorage

Error: Storage quota exceeded ❯ Object.<anonymous> src/renderer/__tests__/task-order.test.ts:299:15 ❯ src/renderer/__tests__/task-order.test.ts:399:27

Check failure on line 299 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (ubuntu-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > saveTaskOrder > should overwrite existing stored order

Error: Storage quota exceeded ❯ Object.<anonymous> src/renderer/__tests__/task-order.test.ts:299:15 ❯ src/renderer/__tests__/task-order.test.ts:386:46

Check failure on line 299 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (ubuntu-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > saveTaskOrder > should use project-specific localStorage keys

Error: Storage quota exceeded ❯ Object.<anonymous> src/renderer/__tests__/task-order.test.ts:299:15 ❯ src/renderer/__tests__/task-order.test.ts:349:27

Check failure on line 299 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (ubuntu-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > saveTaskOrder > should not save if taskOrder is null

Error: Storage quota exceeded ❯ Object.<anonymous> src/renderer/__tests__/task-order.test.ts:299:15 ❯ src/renderer/__tests__/task-order.test.ts:339:35

Check failure on line 299 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (ubuntu-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > saveTaskOrder > should save task order to localStorage

Error: Storage quota exceeded ❯ Object.<anonymous> src/renderer/__tests__/task-order.test.ts:299:15 ❯ src/renderer/__tests__/task-order.test.ts:329:35

Check failure on line 299 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (windows-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > clearTaskOrder > should use project-specific localStorage keys

Error: Storage quota exceeded ❯ Object.<anonymous> src/renderer/__tests__/task-order.test.ts:299:15 ❯ src/renderer/__tests__/task-order.test.ts:409:27

Check failure on line 299 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (windows-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > clearTaskOrder > should clear task order from localStorage

Error: Storage quota exceeded ❯ Object.<anonymous> src/renderer/__tests__/task-order.test.ts:299:15 ❯ src/renderer/__tests__/task-order.test.ts:399:27

Check failure on line 299 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (windows-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > saveTaskOrder > should overwrite existing stored order

Error: Storage quota exceeded ❯ Object.<anonymous> src/renderer/__tests__/task-order.test.ts:299:15 ❯ src/renderer/__tests__/task-order.test.ts:386:46

Check failure on line 299 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (windows-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > saveTaskOrder > should use project-specific localStorage keys

Error: Storage quota exceeded ❯ Object.<anonymous> src/renderer/__tests__/task-order.test.ts:299:15 ❯ src/renderer/__tests__/task-order.test.ts:349:27

Check failure on line 299 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (windows-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > saveTaskOrder > should not save if taskOrder is null

Error: Storage quota exceeded ❯ Object.<anonymous> src/renderer/__tests__/task-order.test.ts:299:15 ❯ src/renderer/__tests__/task-order.test.ts:339:35

Check failure on line 299 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (windows-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > saveTaskOrder > should save task order to localStorage

Error: Storage quota exceeded ❯ Object.<anonymous> src/renderer/__tests__/task-order.test.ts:299:15 ❯ src/renderer/__tests__/task-order.test.ts:329:35
});

useTaskStore.getState().loadTaskOrder('project-1');

// Should fall back to empty order state
expect(useTaskStore.getState().taskOrder).toEqual({

Check failure on line 305 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (macos-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > loadTaskOrder > should handle localStorage access errors

AssertionError: expected { backlog: [], in_progress: [], …(5) } to deeply equal { backlog: [], in_progress: [], …(4) } - Expected + Received @@ -3,6 +3,7 @@ "backlog": [], "done": [], "human_review": [], "in_progress": [], "pr_created": [], + "stopped": [], } ❯ src/renderer/__tests__/task-order.test.ts:305:49

Check failure on line 305 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (ubuntu-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > loadTaskOrder > should handle localStorage access errors

AssertionError: expected { backlog: [], in_progress: [], …(5) } to deeply equal { backlog: [], in_progress: [], …(4) } - Expected + Received @@ -3,6 +3,7 @@ "backlog": [], "done": [], "human_review": [], "in_progress": [], "pr_created": [], + "stopped": [], } ❯ src/renderer/__tests__/task-order.test.ts:305:49

Check failure on line 305 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (windows-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > loadTaskOrder > should handle localStorage access errors

AssertionError: expected { backlog: [], in_progress: [], …(5) } to deeply equal { backlog: [], in_progress: [], …(4) } - Expected + Received @@ -3,6 +3,7 @@ "backlog": [], "done": [], "human_review": [], "in_progress": [], "pr_created": [], + "stopped": [], } ❯ src/renderer/__tests__/task-order.test.ts:305:49
backlog: [],
in_progress: [],
ai_review: [],
Expand Down Expand Up @@ -495,7 +496,8 @@
ai_review: [],
human_review: [],
pr_created: [],
done: []
done: [],
stopped: []
} as TaskOrderState;
useTaskStore.setState({ taskOrder: order });

Expand Down Expand Up @@ -586,7 +588,7 @@
useTaskStore.getState().loadTaskOrder('project-1');

// Empty string causes JSON.parse to throw - should fall back to empty order
expect(useTaskStore.getState().taskOrder).toEqual({

Check failure on line 591 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (macos-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > localStorage persistence edge cases > should handle empty string in localStorage

AssertionError: expected { backlog: [], in_progress: [], …(5) } to deeply equal { backlog: [], in_progress: [], …(4) } - Expected + Received @@ -3,6 +3,7 @@ "backlog": [], "done": [], "human_review": [], "in_progress": [], "pr_created": [], + "stopped": [], } ❯ src/renderer/__tests__/task-order.test.ts:591:49

Check failure on line 591 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (ubuntu-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > localStorage persistence edge cases > should handle empty string in localStorage

AssertionError: expected { backlog: [], in_progress: [], …(5) } to deeply equal { backlog: [], in_progress: [], …(4) } - Expected + Received @@ -3,6 +3,7 @@ "backlog": [], "done": [], "human_review": [], "in_progress": [], "pr_created": [], + "stopped": [], } ❯ src/renderer/__tests__/task-order.test.ts:591:49

Check failure on line 591 in apps/frontend/src/renderer/__tests__/task-order.test.ts

View workflow job for this annotation

GitHub Actions / test-frontend (windows-latest)

src/renderer/__tests__/task-order.test.ts > Task Order State Management > localStorage persistence edge cases > should handle empty string in localStorage

AssertionError: expected { backlog: [], in_progress: [], …(5) } to deeply equal { backlog: [], in_progress: [], …(4) } - Expected + Received @@ -3,6 +3,7 @@ "backlog": [], "done": [], "human_review": [], "in_progress": [], "pr_created": [], + "stopped": [], } ❯ src/renderer/__tests__/task-order.test.ts:591:49
backlog: [],
in_progress: [],
ai_review: [],
Expand Down
13 changes: 9 additions & 4 deletions apps/frontend/src/renderer/components/KanbanBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,13 @@ function isValidDropColumn(id: string): id is typeof TASK_STATUS_COLUMNS[number]
/**
* Get the visual column for a task status.
* pr_created tasks are displayed in the 'done' column, so we map them accordingly.
* stopped tasks are displayed in the 'backlog' column.
* This is used to compare visual positions during drag-and-drop operations.
*/
function getVisualColumn(status: TaskStatus): typeof TASK_STATUS_COLUMNS[number] {
return status === 'pr_created' ? 'done' : status;
if (status === 'pr_created') return 'done';
if (status === 'stopped') return 'backlog';
return status;
}

interface KanbanBoardProps {
Expand Down Expand Up @@ -478,6 +481,7 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR

const tasksByStatus = useMemo(() => {
// Note: pr_created tasks are shown in the 'done' column since they're essentially complete
// Note: stopped tasks are shown in the 'backlog' column
const grouped: Record<typeof TASK_STATUS_COLUMNS[number], Task[]> = {
backlog: [],
in_progress: [],
Expand All @@ -487,8 +491,8 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR
};

filteredTasks.forEach((task) => {
// Map pr_created tasks to the done column
const targetColumn = task.status === 'pr_created' ? 'done' : task.status;
// Map pr_created tasks to the done column, stopped tasks to backlog
const targetColumn = getVisualColumn(task.status);
if (grouped[targetColumn]) {
grouped[targetColumn].push(task);
}
Expand Down Expand Up @@ -741,7 +745,8 @@ export function KanbanBoard({ tasks, onTaskClick, onNewTaskClick, onRefresh, isR
ai_review: [],
human_review: [],
pr_created: [],
done: []
done: [],
stopped: []
};

for (const status of Object.keys(taskOrder) as Array<keyof typeof taskOrder>) {
Expand Down
Loading
Loading