diff --git a/apps/server/src/lib/sdk-options.ts b/apps/server/src/lib/sdk-options.ts
index ff3d6067..cc1df2f5 100644
--- a/apps/server/src/lib/sdk-options.ts
+++ b/apps/server/src/lib/sdk-options.ts
@@ -129,10 +129,30 @@ export const TOOL_PRESETS = {
specGeneration: ['Read', 'Glob', 'Grep'] as const,
/** Full tool access for feature implementation */
- fullAccess: ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch', 'TodoWrite'] as const,
+ fullAccess: [
+ 'Read',
+ 'Write',
+ 'Edit',
+ 'Glob',
+ 'Grep',
+ 'Bash',
+ 'WebSearch',
+ 'WebFetch',
+ 'TodoWrite',
+ ] as const,
/** Tools for chat/interactive mode */
- chat: ['Read', 'Write', 'Edit', 'Glob', 'Grep', 'Bash', 'WebSearch', 'WebFetch', 'TodoWrite'] as const,
+ chat: [
+ 'Read',
+ 'Write',
+ 'Edit',
+ 'Glob',
+ 'Grep',
+ 'Bash',
+ 'WebSearch',
+ 'WebFetch',
+ 'TodoWrite',
+ ] as const,
} as const;
/**
diff --git a/apps/ui/src/components/layout/project-switcher/components/icon-picker.tsx b/apps/ui/src/components/layout/project-switcher/components/icon-picker.tsx
index c5a4c1ad..10947a51 100644
--- a/apps/ui/src/components/layout/project-switcher/components/icon-picker.tsx
+++ b/apps/ui/src/components/layout/project-switcher/components/icon-picker.tsx
@@ -79,9 +79,7 @@ export function IconPicker({ selectedIcon, onSelectIcon }: IconPickerProps) {
{(() => {
const IconComponent = getIconComponent(selectedIcon);
- return IconComponent ? (
-
- ) : null;
+ return IconComponent ? : null;
})()}
{selectedIcon}
diff --git a/apps/ui/tests/projects/board-background-persistence.spec.ts b/apps/ui/tests/projects/board-background-persistence.spec.ts
deleted file mode 100644
index f7d35dec..00000000
--- a/apps/ui/tests/projects/board-background-persistence.spec.ts
+++ /dev/null
@@ -1,431 +0,0 @@
-/**
- * Board Background Persistence End-to-End Test
- *
- * Tests that board background settings are properly saved and loaded when switching projects.
- * This verifies that:
- * 1. Background settings are saved to .automaker-local/settings.json
- * 2. Settings are loaded when switching back to a project
- * 3. Background image, opacity, and other settings are correctly restored
- * 4. Settings persist across app restarts (new page loads)
- *
- * This test prevents regression of the board background loading bug where
- * settings were saved but never loaded when switching projects.
- */
-
-import { test, expect } from '@playwright/test';
-import * as fs from 'fs';
-import * as path from 'path';
-import {
- createTempDirPath,
- cleanupTempDir,
- authenticateForTests,
- handleLoginScreenIfPresent,
- setupWelcomeView,
-} from '../utils';
-
-// Create unique temp dirs for this test run
-const TEST_TEMP_DIR = createTempDirPath('board-bg-test');
-
-test.describe('Board Background Persistence', () => {
- test.beforeAll(async () => {
- // Create test temp directory
- if (!fs.existsSync(TEST_TEMP_DIR)) {
- fs.mkdirSync(TEST_TEMP_DIR, { recursive: true });
- }
- });
-
- test.afterAll(async () => {
- // Cleanup temp directory
- cleanupTempDir(TEST_TEMP_DIR);
- });
-
- test('should load board background settings when switching projects', async ({ page }) => {
- const projectAName = `project-a-${Date.now()}`;
- const projectBName = `project-b-${Date.now()}`;
- const projectAPath = path.join(TEST_TEMP_DIR, projectAName);
- const projectBPath = path.join(TEST_TEMP_DIR, projectBName);
- const projectAId = `project-a-${Date.now()}`;
- const projectBId = `project-b-${Date.now()}`;
-
- // Create both project directories
- fs.mkdirSync(projectAPath, { recursive: true });
- fs.mkdirSync(projectBPath, { recursive: true });
-
- // Create basic files for both projects
- for (const [name, projectPath] of [
- [projectAName, projectAPath],
- [projectBName, projectBPath],
- ]) {
- fs.writeFileSync(
- path.join(projectPath, 'package.json'),
- JSON.stringify({ name, version: '1.0.0' }, null, 2)
- );
- fs.writeFileSync(path.join(projectPath, 'README.md'), `# ${name}\n`);
- }
-
- // Create .automaker-local directory for project A with background settings
- const automakerDirA = path.join(projectAPath, '.automaker-local');
- fs.mkdirSync(automakerDirA, { recursive: true });
- fs.mkdirSync(path.join(automakerDirA, 'board'), { recursive: true });
- fs.mkdirSync(path.join(automakerDirA, 'features'), { recursive: true });
- fs.mkdirSync(path.join(automakerDirA, 'context'), { recursive: true });
-
- // Copy actual background image from test fixtures
- const backgroundPath = path.join(automakerDirA, 'board', 'background.jpg');
- const testImagePath = path.join(__dirname, '..', 'img', 'background.jpg');
- fs.copyFileSync(testImagePath, backgroundPath);
-
- // Create settings.json with board background configuration
- const settingsPath = path.join(automakerDirA, 'settings.json');
- const backgroundSettings = {
- version: 1,
- boardBackground: {
- imagePath: backgroundPath,
- cardOpacity: 85,
- columnOpacity: 60,
- columnBorderEnabled: true,
- cardGlassmorphism: true,
- cardBorderEnabled: false,
- cardBorderOpacity: 50,
- hideScrollbar: true,
- imageVersion: Date.now(),
- },
- };
- fs.writeFileSync(settingsPath, JSON.stringify(backgroundSettings, null, 2));
-
- // Create minimal automaker-local directory for project B (no background)
- const automakerDirB = path.join(projectBPath, '.automaker-local');
- fs.mkdirSync(automakerDirB, { recursive: true });
- fs.mkdirSync(path.join(automakerDirB, 'features'), { recursive: true });
- fs.mkdirSync(path.join(automakerDirB, 'context'), { recursive: true });
- fs.writeFileSync(
- path.join(automakerDirB, 'settings.json'),
- JSON.stringify({ version: 1 }, null, 2)
- );
-
- // Set up welcome view with both projects in the list
- await setupWelcomeView(page, {
- workspaceDir: TEST_TEMP_DIR,
- recentProjects: [
- {
- id: projectAId,
- name: projectAName,
- path: projectAPath,
- lastOpened: new Date(Date.now() - 86400000).toISOString(),
- },
- {
- id: projectBId,
- name: projectBName,
- path: projectBPath,
- lastOpened: new Date(Date.now() - 172800000).toISOString(),
- },
- ],
- });
-
- await authenticateForTests(page);
-
- // Intercept settings API to use our test projects and clear currentProjectId
- // This ensures the app shows the welcome view with our test projects
- await page.route('**/api/settings/global', async (route) => {
- const response = await route.fetch();
- const json = await response.json();
- if (json.settings) {
- // Clear currentProjectId to show welcome view
- json.settings.currentProjectId = null;
- // Include our test projects so they appear in the recent projects list
- json.settings.projects = [
- {
- id: projectAId,
- name: projectAName,
- path: projectAPath,
- lastOpened: new Date(Date.now() - 86400000).toISOString(),
- },
- {
- id: projectBId,
- name: projectBName,
- path: projectBPath,
- lastOpened: new Date(Date.now() - 172800000).toISOString(),
- },
- ];
- }
- await route.fulfill({ response, json });
- });
-
- // Track API calls to /api/settings/project to verify settings are being loaded
- const settingsApiCalls: Array<{ url: string; method: string; body: string }> = [];
- page.on('request', (request) => {
- if (request.url().includes('/api/settings/project') && request.method() === 'POST') {
- settingsApiCalls.push({
- url: request.url(),
- method: request.method(),
- body: request.postData() || '',
- });
- }
- });
-
- // Navigate to the app
- await page.goto('/');
- await page.waitForLoadState('load');
- await handleLoginScreenIfPresent(page);
-
- // Wait for welcome view
- await expect(page.locator('[data-testid="welcome-view"]')).toBeVisible({ timeout: 10000 });
-
- // Open project A (has background settings)
- const projectACard = page.locator(`[data-testid="recent-project-${projectAId}"]`);
- await expect(projectACard).toBeVisible();
- await projectACard.click();
-
- // Wait for board view
- await expect(page.locator('[data-testid="board-view"]')).toBeVisible({ timeout: 15000 });
-
- // Verify project A is current (check header paragraph which is always visible)
- await expect(page.locator('[data-testid="board-view"]').getByText(projectAName)).toBeVisible({
- timeout: 5000,
- });
-
- // CRITICAL: Wait for settings to be loaded (useProjectSettingsLoader hook)
- // This ensures the background settings are fetched from the server
- await page.waitForTimeout(2000);
-
- // Check if background settings were applied by checking the store
- // We can't directly access React state, so we'll verify via DOM/CSS
- const boardView = page.locator('[data-testid="board-view"]');
- await expect(boardView).toBeVisible();
-
- // Wait for initial project load to stabilize
- await page.waitForTimeout(500);
-
- // Ensure sidebar is expanded before interacting with project selector
- const expandSidebarButton = page.locator('button:has-text("Expand sidebar")');
- if (await expandSidebarButton.isVisible()) {
- await expandSidebarButton.click();
- await page.waitForTimeout(300);
- }
-
- // Switch to project B (no background)
- const projectSelector = page.locator('[data-testid="project-selector"]');
- await expect(projectSelector).toBeVisible({ timeout: 5000 });
- await projectSelector.click();
-
- // Wait for dropdown to be visible
- await expect(page.locator('[data-testid="project-picker-dropdown"]')).toBeVisible({
- timeout: 5000,
- });
-
- const projectPickerB = page.locator(`[data-testid="project-option-${projectBId}"]`);
- await expect(projectPickerB).toBeVisible({ timeout: 5000 });
- await projectPickerB.click();
-
- // Wait for project B to load
- await expect(
- page.locator('[data-testid="project-selector"]').getByText(projectBName)
- ).toBeVisible({ timeout: 5000 });
-
- // Wait a bit for project B to fully load before switching
- await page.waitForTimeout(500);
-
- // Switch back to project A
- await projectSelector.click();
-
- // Wait for dropdown to be visible
- await expect(page.locator('[data-testid="project-picker-dropdown"]')).toBeVisible({
- timeout: 5000,
- });
-
- const projectPickerA = page.locator(`[data-testid="project-option-${projectAId}"]`);
- await expect(projectPickerA).toBeVisible({ timeout: 5000 });
- await projectPickerA.click();
-
- // Verify we're back on project A
- await expect(
- page.locator('[data-testid="project-selector"]').getByText(projectAName)
- ).toBeVisible({ timeout: 5000 });
-
- // CRITICAL: Wait for settings to be loaded again
- await page.waitForTimeout(2000);
-
- // Verify that the settings API was called for project A (at least twice - initial load and switch back)
- const projectASettingsCalls = settingsApiCalls.filter((call) =>
- call.body.includes(projectAPath)
- );
-
- // Debug: log all API calls if test fails
- if (projectASettingsCalls.length < 2) {
- console.log('Total settings API calls:', settingsApiCalls.length);
- console.log('API calls:', JSON.stringify(settingsApiCalls, null, 2));
- console.log('Looking for path:', projectAPath);
- }
-
- expect(projectASettingsCalls.length).toBeGreaterThanOrEqual(2);
-
- // Verify settings file still exists with correct data
- const loadedSettings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
- expect(loadedSettings.boardBackground).toBeDefined();
- expect(loadedSettings.boardBackground.imagePath).toBe(backgroundPath);
- expect(loadedSettings.boardBackground.cardOpacity).toBe(85);
- expect(loadedSettings.boardBackground.columnOpacity).toBe(60);
- expect(loadedSettings.boardBackground.hideScrollbar).toBe(true);
-
- // The test passing means:
- // 1. The useProjectSettingsLoader hook is working
- // 2. Settings are loaded when switching projects
- // 3. The API call to /api/settings/project is made correctly
- });
-
- test('should load background settings on app restart', async ({ page }) => {
- const projectName = `restart-test-${Date.now()}`;
- const projectPath = path.join(TEST_TEMP_DIR, projectName);
- const projectId = `project-${Date.now()}`;
-
- // Create project directory
- fs.mkdirSync(projectPath, { recursive: true });
- fs.writeFileSync(
- path.join(projectPath, 'package.json'),
- JSON.stringify({ name: projectName, version: '1.0.0' }, null, 2)
- );
-
- // Create .automaker-local with background settings
- const automakerDir = path.join(projectPath, '.automaker-local');
- fs.mkdirSync(automakerDir, { recursive: true });
- fs.mkdirSync(path.join(automakerDir, 'board'), { recursive: true });
- fs.mkdirSync(path.join(automakerDir, 'features'), { recursive: true });
- fs.mkdirSync(path.join(automakerDir, 'context'), { recursive: true });
-
- // Copy actual background image from test fixtures
- const backgroundPath = path.join(automakerDir, 'board', 'background.jpg');
- const testImagePath = path.join(__dirname, '..', 'img', 'background.jpg');
- fs.copyFileSync(testImagePath, backgroundPath);
-
- const settingsPath = path.join(automakerDir, 'settings.json');
- fs.writeFileSync(
- settingsPath,
- JSON.stringify(
- {
- version: 1,
- boardBackground: {
- imagePath: backgroundPath,
- cardOpacity: 90,
- columnOpacity: 70,
- imageVersion: Date.now(),
- },
- },
- null,
- 2
- )
- );
-
- // Set up with project as current using direct localStorage
- await page.addInitScript(
- ({ project }: { project: string[] }) => {
- const projectObj = {
- id: project[0],
- name: project[1],
- path: project[2],
- lastOpened: new Date().toISOString(),
- };
-
- const appState = {
- state: {
- projects: [projectObj],
- currentProject: projectObj,
- currentView: 'board',
- theme: 'dark',
- sidebarOpen: true,
- skipSandboxWarning: true,
- apiKeys: { anthropic: '', google: '' },
- chatSessions: [],
- chatHistoryOpen: false,
- maxConcurrency: 3,
- boardBackgroundByProject: {},
- },
- version: 2,
- };
- localStorage.setItem('automaker-storage', JSON.stringify(appState));
-
- // Setup complete - use correct key name
- const setupState = {
- state: {
- isFirstRun: false,
- setupComplete: true,
- skipClaudeSetup: false,
- },
- version: 1,
- };
- localStorage.setItem('automaker-setup', JSON.stringify(setupState));
-
- // Disable splash screen in tests
- sessionStorage.setItem('automaker-splash-shown', 'true');
- },
- { project: [projectId, projectName, projectPath] }
- );
-
- await authenticateForTests(page);
-
- // Intercept settings API to use our test project instead of the E2E fixture
- await page.route('**/api/settings/global', async (route) => {
- const response = await route.fetch();
- const json = await response.json();
- // Override to use our test project
- if (json.settings) {
- json.settings.currentProjectId = projectId;
- json.settings.projects = [
- {
- id: projectId,
- name: projectName,
- path: projectPath,
- lastOpened: new Date().toISOString(),
- },
- ];
- }
- await route.fulfill({ response, json });
- });
-
- // Track API calls to /api/settings/project to verify settings are being loaded
- const settingsApiCalls: Array<{ url: string; method: string; body: string }> = [];
- page.on('request', (request) => {
- if (request.url().includes('/api/settings/project') && request.method() === 'POST') {
- settingsApiCalls.push({
- url: request.url(),
- method: request.method(),
- body: request.postData() || '',
- });
- }
- });
-
- // Navigate to the app
- await page.goto('/');
- await page.waitForLoadState('load');
- await handleLoginScreenIfPresent(page);
-
- // Should go straight to board view (not welcome) since we have currentProject
- await expect(page.locator('[data-testid="board-view"]')).toBeVisible({ timeout: 15000 });
-
- // Wait for settings to load
- await page.waitForTimeout(2000);
-
- // Verify that the settings API was called for this project
- const projectSettingsCalls = settingsApiCalls.filter((call) => call.body.includes(projectPath));
-
- // Debug: log all API calls if test fails
- if (projectSettingsCalls.length < 1) {
- console.log('Total settings API calls:', settingsApiCalls.length);
- console.log('API calls:', JSON.stringify(settingsApiCalls, null, 2));
- console.log('Looking for path:', projectPath);
- }
-
- expect(projectSettingsCalls.length).toBeGreaterThanOrEqual(1);
-
- // Verify settings file exists with correct data
- const loadedSettings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
- expect(loadedSettings.boardBackground).toBeDefined();
- expect(loadedSettings.boardBackground.imagePath).toBe(backgroundPath);
- expect(loadedSettings.boardBackground.cardOpacity).toBe(90);
- expect(loadedSettings.boardBackground.columnOpacity).toBe(70);
-
- // The test passing means:
- // 1. The useProjectSettingsLoader hook is working
- // 2. Settings are loaded when app starts with a currentProject
- // 3. The API call to /api/settings/project is made correctly
- });
-});