Improve pull request flow, add branch selection for worktree creation, fix auto-mode concurrency count (#787)

* Changes from fix/fetch-before-pull-fetch

* feat: Improve pull request flow, add branch selection for worktree creation, fix for automode concurrency count

* feat: Add validation for remote names and improve error handling

* Address PR comments and mobile layout fixes

* ```
refactor: Extract PR target resolution logic into dedicated service
```

* feat: Add app shell UI and improve service imports. Address PR comments

* fix: Improve security validation and cache handling in git operations

* feat: Add GET /list endpoint and improve parameter handling

* chore: Improve validation, accessibility, and error handling across apps

* chore: Format vite server port configuration

* fix: Add error handling for gh pr list command and improve offline fallbacks

* fix: Preserve existing PR creation time and improve remote handling
This commit is contained in:
gsxdsm
2026-02-19 21:55:12 -08:00
committed by GitHub
parent ee52333636
commit 7df2182818
80 changed files with 4729 additions and 1107 deletions

View File

@@ -25,6 +25,10 @@ import {
const TEST_TEMP_DIR = createTempDirPath('manual-review-test');
// Generate deterministic projectId once at test module load so both
// setupRealProject and the route interceptor use the same ID
const TEST_PROJECT_ID = `project-manual-review-${Date.now()}`;
test.describe('Feature Manual Review Flow', () => {
let projectPath: string;
const projectName = `test-project-${Date.now()}`;
@@ -71,34 +75,45 @@ test.describe('Feature Manual Review Flow', () => {
});
test('should manually verify a feature in waiting_approval column', async ({ page }) => {
// Set up the project in localStorage
await setupRealProject(page, projectPath, projectName, { setAsCurrent: true });
// Set up the project in localStorage with a deterministic projectId
await setupRealProject(page, projectPath, projectName, {
setAsCurrent: true,
projectId: TEST_PROJECT_ID,
});
// Intercept settings API to ensure our test project remains current
// and doesn't get overridden by server settings
// and doesn't get overridden by server settings.
await page.route('**/api/settings/global', async (route) => {
const method = route.request().method();
if (method === 'PUT') {
// Allow settings sync writes to pass through
return route.continue();
}
const response = await route.fetch();
const json = await response.json();
if (json.settings) {
// Set our test project as the current project
const testProject = {
id: `project-${projectName}`,
name: projectName,
path: projectPath,
lastOpened: new Date().toISOString(),
};
// Add to projects if not already there
const existingProjects = json.settings.projects || [];
const hasProject = existingProjects.some(
(p: { id: string; path: string }) => p.path === projectPath
);
if (!hasProject) {
// Find existing project by path to preserve any server-generated IDs
let testProject = existingProjects.find((p: { path: string }) => p.path === projectPath);
if (!testProject) {
// Project not in server response yet — use the same deterministic TEST_PROJECT_ID
// that was seeded in automaker-settings-cache.currentProjectId via setupRealProject.
testProject = {
id: TEST_PROJECT_ID,
name: projectName,
path: projectPath,
lastOpened: new Date().toISOString(),
};
json.settings.projects = [testProject, ...existingProjects];
}
// Set as current project
// Set as current project using the matching project's ID
json.settings.currentProjectId = testProject.id;
// Ensure CI runs don't redirect to /setup
json.settings.setupComplete = true;
json.settings.isFirstRun = false;
}
await route.fulfill({ response, json });
});
@@ -140,7 +155,7 @@ test.describe('Feature Manual Review Flow', () => {
priority: 2,
};
const API_BASE_URL = process.env.VITE_SERVER_URL || 'http://localhost:3008';
const API_BASE_URL = process.env.SERVER_URL || 'http://localhost:3008';
const createResponse = await page.request.post(`${API_BASE_URL}/api/features/create`, {
data: { projectPath, feature },
headers: { 'Content-Type': 'application/json' },

View File

@@ -35,16 +35,42 @@ test.describe('Project Creation', () => {
// Intercept settings API BEFORE authenticateForTests (which navigates to the page)
// This prevents settings hydration from restoring a project and disables auto-open
await page.route('**/api/settings/global', async (route) => {
const method = route.request().method();
if (method === 'PUT') {
// Allow settings sync writes to pass through
return route.continue();
}
const response = await route.fetch();
const json = await response.json();
// Remove currentProjectId and clear projects to prevent auto-open
if (json.settings) {
json.settings.currentProjectId = null;
json.settings.projects = [];
// Ensure setup is marked complete to prevent redirect to /setup on fresh CI
json.settings.setupComplete = true;
json.settings.isFirstRun = false;
// Preserve lastProjectDir so the new project modal knows where to create projects
json.settings.lastProjectDir = TEST_TEMP_DIR;
}
await route.fulfill({ response, json });
});
// Mock workspace config API to return a valid default directory.
// In CI, ALLOWED_ROOT_DIRECTORY is unset and Documents path is unavailable,
// so without this mock, getDefaultWorkspaceDirectory() returns null and the
// "Will be created at:" text never renders in the new project modal.
await page.route('**/api/workspace/config', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
success: true,
configured: false,
defaultDir: TEST_TEMP_DIR,
}),
});
});
await authenticateForTests(page);
// Navigate directly to dashboard to avoid auto-open logic

View File

@@ -20,6 +20,67 @@ test.describe('Projects Overview Dashboard', () => {
test.beforeEach(async ({ page }) => {
// Set up mock projects state
await setupMockMultipleProjects(page, 3);
// Intercept settings API to preserve mock project data and prevent
// the server's settings from overriding our test setup.
// Without this, background reconciliation can clear the mock projects.
await page.route('**/api/settings/global', async (route) => {
const method = route.request().method();
if (method === 'PUT') {
// Allow settings sync writes to pass through
return route.continue();
}
const response = await route.fetch();
const json = await response.json();
if (json.settings) {
// Always overwrite projects with mock data so CI-provided projects
// that don't contain 'test-project-1' can't break hydration.
json.settings.projects = [
{
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(),
},
{
id: 'test-project-3',
name: 'Test Project 3',
path: '/mock/test-project-3',
lastOpened: new Date(Date.now() - 172800000).toISOString(),
},
];
json.settings.currentProjectId = 'test-project-1';
json.settings.setupComplete = true;
json.settings.isFirstRun = false;
}
await route.fulfill({ response, json });
});
// Mock the initialize-project endpoint for mock paths that don't exist on disk.
// This prevents auto-open from failing when it tries to verify the project directory.
await page.route('**/api/project/initialize', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true }),
});
});
// Mock features list for mock project paths (they don't exist on disk)
await page.route('**/api/features/list**', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ success: true, features: [] }),
});
});
await authenticateForTests(page);
});

View File

@@ -64,6 +64,10 @@ export async function gotoWithAuth(page: Page, url: string): Promise<void> {
await page.goto(url);
}
/** Selector matching any top-level app view by data-testid, used to detect that the app has loaded. */
const APP_CONTENT_SELECTOR =
'[data-testid="welcome-view"], [data-testid="dashboard-view"], [data-testid="board-view"], [data-testid="context-view"], [data-testid="agent-view"], [data-testid="overview-view"]';
/**
* Handle login screen if it appears after navigation
* Returns true if login was handled, false if no login screen was found
@@ -74,9 +78,7 @@ export async function handleLoginScreenIfPresent(page: Page): Promise<boolean> {
const loginInput = page
.locator('[data-testid="login-api-key-input"], input[type="password"][placeholder*="API key"]')
.first();
const appContent = page.locator(
'[data-testid="welcome-view"], [data-testid="dashboard-view"], [data-testid="board-view"], [data-testid="context-view"], [data-testid="agent-view"]'
);
const appContent = page.locator(APP_CONTENT_SELECTOR);
const loggedOutPage = page.getByRole('heading', { name: /logged out/i });
const goToLoginButton = page.locator('button:has-text("Go to login")');

File diff suppressed because it is too large Load Diff