diff --git a/.automaker/feature_list.json b/.automaker/feature_list.json
index 6c0b68c5..e4b8f903 100644
--- a/.automaker/feature_list.json
+++ b/.automaker/feature_list.json
@@ -4,7 +4,7 @@
"category": "Kanban",
"description": "Remove drag icon from cards when in in progress or verified. also add a timer that tracks how long it has been since the agent started, a count up timer basically formatted 00:00",
"steps": [],
- "status": "in_progress"
+ "status": "verified"
},
{
"id": "feature-1765261027396-b78maajg7",
@@ -32,7 +32,7 @@
"category": "Core",
"description": "When the electron ui refreshes, it often redirects me back to the overview, remeber my last route and restore on app load",
"steps": [],
- "status": "in_progress"
+ "status": "verified"
},
{
"id": "feature-1765263521367-vse4n57j8",
@@ -54,5 +54,13 @@
"description": "remove claude-progress from anywhere in code or prompts as it's no longer needed",
"steps": [],
"status": "verified"
+ },
+ {
+ "id": "feature-1765264341539-km1238av9",
+ "category": "Core",
+ "description": "remove the code view link",
+ "steps": [],
+ "status": "in_progress",
+ "startedAt": "2025-12-09T07:12:24.361Z"
}
]
\ No newline at end of file
diff --git a/app/src/components/views/kanban-card.tsx b/app/src/components/views/kanban-card.tsx
index dd261053..fa523fe7 100644
--- a/app/src/components/views/kanban-card.tsx
+++ b/app/src/components/views/kanban-card.tsx
@@ -76,16 +76,15 @@ export function KanbanCard({ feature, onEdit, onDelete, onViewOutput, onVerify,
)}
-
-
-
+ {isDraggable && (
+
+
+
+ )}
{feature.description}
diff --git a/app/src/store/app-store.ts b/app/src/store/app-store.ts
index 6b3a3b64..08604d2d 100644
--- a/app/src/store/app-store.ts
+++ b/app/src/store/app-store.ts
@@ -2,7 +2,7 @@ import { create } from "zustand";
import { persist } from "zustand/middleware";
import type { Project } from "@/lib/electron";
-export type ViewMode = "welcome" | "spec" | "board" | "code" | "agent" | "settings" | "tools" | "interview" | "context";
+export type ViewMode = "welcome" | "spec" | "board" | "agent" | "settings" | "tools" | "interview" | "context";
export type ThemeMode = "light" | "dark" | "system";
export interface ApiKeys {
@@ -398,6 +398,7 @@ export const useAppStore = create()(
partialize: (state) => ({
projects: state.projects,
currentProject: state.currentProject,
+ currentView: state.currentView,
theme: state.theme,
sidebarOpen: state.sidebarOpen,
apiKeys: state.apiKeys,
diff --git a/app/tests/utils.ts b/app/tests/utils.ts
index 6f216be7..68be558a 100644
--- a/app/tests/utils.ts
+++ b/app/tests/utils.ts
@@ -894,3 +894,352 @@ export async function setupMockProjectWithInProgressFeatures(
options
);
}
+
+/**
+ * Navigate to the spec view
+ */
+export async function navigateToSpec(page: Page): Promise {
+ await page.goto("/");
+
+ // Wait for the page to load
+ await page.waitForLoadState("networkidle");
+
+ // Click on the Spec nav button
+ const specNav = page.locator('[data-testid="nav-spec"]');
+ if (await specNav.isVisible().catch(() => false)) {
+ await specNav.click();
+ }
+
+ // Wait for the spec view to be visible
+ await waitForElement(page, "spec-view", { timeout: 10000 });
+}
+
+/**
+ * Get the spec editor element
+ */
+export async function getSpecEditor(page: Page): Promise {
+ return page.locator('[data-testid="spec-editor"]');
+}
+
+/**
+ * Get the spec editor content
+ */
+export async function getSpecEditorContent(page: Page): Promise {
+ const editor = await getSpecEditor(page);
+ return await editor.inputValue();
+}
+
+/**
+ * Set the spec editor content
+ */
+export async function setSpecEditorContent(
+ page: Page,
+ content: string
+): Promise {
+ const editor = await getSpecEditor(page);
+ await editor.fill(content);
+}
+
+/**
+ * Click the save spec button
+ */
+export async function clickSaveSpec(page: Page): Promise {
+ await clickElement(page, "save-spec");
+}
+
+/**
+ * Click the reload spec button
+ */
+export async function clickReloadSpec(page: Page): Promise {
+ await clickElement(page, "reload-spec");
+}
+
+/**
+ * Check if the spec view path display shows the correct .automaker path
+ */
+export async function getDisplayedSpecPath(page: Page): Promise {
+ const specView = page.locator('[data-testid="spec-view"]');
+ const pathElement = specView.locator("p.text-muted-foreground").first();
+ return await pathElement.textContent();
+}
+
+/**
+ * Get a kanban column by its ID
+ */
+export async function getKanbanColumn(
+ page: Page,
+ columnId: string
+): Promise {
+ return page.locator(`[data-testid="kanban-column-${columnId}"]`);
+}
+
+/**
+ * Get the width of a kanban column
+ */
+export async function getKanbanColumnWidth(
+ page: Page,
+ columnId: string
+): Promise {
+ const column = page.locator(`[data-testid="kanban-column-${columnId}"]`);
+ const box = await column.boundingBox();
+ return box?.width ?? 0;
+}
+
+/**
+ * Check if a kanban column has CSS columns (masonry) layout
+ */
+export async function hasKanbanColumnMasonryLayout(
+ page: Page,
+ columnId: string
+): Promise {
+ const column = page.locator(`[data-testid="kanban-column-${columnId}"]`);
+ const contentDiv = column.locator("> div").nth(1); // Second child is the content area
+
+ const columnCount = await contentDiv.evaluate((el) => {
+ const style = window.getComputedStyle(el);
+ return style.columnCount;
+ });
+
+ return columnCount === "2";
+}
+
+/**
+ * Set up a mock project with a specific current view for route persistence testing
+ */
+export async function setupMockProjectWithView(
+ page: Page,
+ view: string
+): Promise {
+ await page.addInitScript((currentView: string) => {
+ const mockProject = {
+ id: "test-project-1",
+ name: "Test Project",
+ path: "/mock/test-project",
+ lastOpened: new Date().toISOString(),
+ };
+
+ const mockState = {
+ state: {
+ projects: [mockProject],
+ currentProject: mockProject,
+ currentView: currentView,
+ theme: "dark",
+ sidebarOpen: true,
+ apiKeys: { anthropic: "", google: "" },
+ chatSessions: [],
+ chatHistoryOpen: false,
+ maxConcurrency: 3,
+ },
+ version: 0,
+ };
+
+ localStorage.setItem("automaker-storage", JSON.stringify(mockState));
+ }, view);
+}
+
+/**
+ * Navigate to a specific view using the sidebar navigation
+ */
+export async function navigateToView(
+ page: Page,
+ viewId: string
+): Promise {
+ const navSelector = viewId === "settings" ? "settings-button" : `nav-${viewId}`;
+ await clickElement(page, navSelector);
+ await page.waitForTimeout(100);
+}
+
+/**
+ * Get the current view from the URL or store (checks which view is active)
+ */
+export async function getCurrentView(page: Page): Promise {
+ // Get the current view from zustand store via localStorage
+ const storage = await page.evaluate(() => {
+ const item = localStorage.getItem("automaker-storage");
+ return item ? JSON.parse(item) : null;
+ });
+
+ return storage?.state?.currentView || null;
+}
+
+/**
+ * Check if the drag handle is visible for a specific feature card
+ */
+export async function isDragHandleVisibleForFeature(
+ page: Page,
+ featureId: string
+): Promise {
+ const dragHandle = page.locator(`[data-testid="drag-handle-${featureId}"]`);
+ return await dragHandle.isVisible().catch(() => false);
+}
+
+/**
+ * Get the drag handle element for a specific feature card
+ */
+export async function getDragHandleForFeature(
+ page: Page,
+ featureId: string
+): Promise {
+ return page.locator(`[data-testid="drag-handle-${featureId}"]`);
+}
+
+/**
+ * Navigate to the welcome view (clear project selection)
+ */
+export async function navigateToWelcome(page: Page): Promise {
+ await page.goto("/");
+ await page.waitForLoadState("networkidle");
+ await waitForElement(page, "welcome-view", { timeout: 10000 });
+}
+
+/**
+ * Set up an empty localStorage (no projects) to show welcome screen
+ */
+export async function setupEmptyLocalStorage(page: Page): Promise {
+ await page.addInitScript(() => {
+ const mockState = {
+ state: {
+ projects: [],
+ currentProject: null,
+ currentView: "welcome",
+ theme: "dark",
+ sidebarOpen: true,
+ apiKeys: { anthropic: "", google: "" },
+ chatSessions: [],
+ chatHistoryOpen: false,
+ maxConcurrency: 3,
+ },
+ version: 0,
+ };
+ localStorage.setItem("automaker-storage", JSON.stringify(mockState));
+ });
+}
+
+/**
+ * Set up mock projects in localStorage but with no current project (for recent projects list)
+ */
+export async function setupMockProjectsWithoutCurrent(page: Page): Promise {
+ await page.addInitScript(() => {
+ const mockProjects = [
+ {
+ id: "test-project-1",
+ name: "Test Project 1",
+ path: "/mock/test-project-1",
+ lastOpened: new Date().toISOString(),
+ },
+ {
+ id: "test-project-2",
+ name: "Test Project 2",
+ path: "/mock/test-project-2",
+ lastOpened: new Date(Date.now() - 86400000).toISOString(), // 1 day ago
+ },
+ ];
+
+ const mockState = {
+ state: {
+ projects: mockProjects,
+ currentProject: null,
+ currentView: "welcome",
+ theme: "dark",
+ sidebarOpen: true,
+ apiKeys: { anthropic: "", google: "" },
+ chatSessions: [],
+ chatHistoryOpen: false,
+ maxConcurrency: 3,
+ },
+ version: 0,
+ };
+
+ localStorage.setItem("automaker-storage", JSON.stringify(mockState));
+ });
+}
+
+/**
+ * Check if the project initialization dialog is visible
+ */
+export async function isProjectInitDialogVisible(page: Page): Promise {
+ const dialog = page.locator('[data-testid="project-init-dialog"]');
+ return await dialog.isVisible();
+}
+
+/**
+ * Wait for the project initialization dialog to appear
+ */
+export async function waitForProjectInitDialog(
+ page: Page,
+ options?: { timeout?: number }
+): Promise {
+ return await waitForElement(page, "project-init-dialog", options);
+}
+
+/**
+ * Close the project initialization dialog
+ */
+export async function closeProjectInitDialog(page: Page): Promise {
+ const closeButton = page.locator('[data-testid="close-init-dialog"]');
+ await closeButton.click();
+}
+
+/**
+ * Check if the project opening overlay is visible
+ */
+export async function isProjectOpeningOverlayVisible(page: Page): Promise {
+ const overlay = page.locator('[data-testid="project-opening-overlay"]');
+ return await overlay.isVisible();
+}
+
+/**
+ * Wait for the project opening overlay to disappear
+ */
+export async function waitForProjectOpeningOverlayHidden(
+ page: Page,
+ options?: { timeout?: number }
+): Promise {
+ await waitForElementHidden(page, "project-opening-overlay", options);
+}
+
+/**
+ * Click on a recent project in the welcome view
+ */
+export async function clickRecentProject(
+ page: Page,
+ projectId: string
+): Promise {
+ await clickElement(page, `recent-project-${projectId}`);
+}
+
+/**
+ * Click the open project card in the welcome view
+ */
+export async function clickOpenProjectCard(page: Page): Promise {
+ await clickElement(page, "open-project-card");
+}
+
+/**
+ * Check if a navigation item exists in the sidebar
+ */
+export async function isNavItemVisible(
+ page: Page,
+ navId: string
+): Promise {
+ const navItem = page.locator(`[data-testid="nav-${navId}"]`);
+ return await navItem.isVisible().catch(() => false);
+}
+
+/**
+ * Get all visible navigation items in the sidebar
+ */
+export async function getVisibleNavItems(page: Page): Promise {
+ const navItems = page.locator('[data-testid^="nav-"]');
+ const count = await navItems.count();
+ const items: string[] = [];
+
+ for (let i = 0; i < count; i++) {
+ const testId = await navItems.nth(i).getAttribute("data-testid");
+ if (testId) {
+ items.push(testId.replace("nav-", ""));
+ }
+ }
+
+ return items;
+}