feat: add tests and implementation for issue discovery and queue pages

- Implemented `DiscoveryPage` with session management and findings display.
- Added tests for `DiscoveryPage` to ensure proper rendering and functionality.
- Created `QueuePage` for managing issue execution queues with stats and actions.
- Added tests for `QueuePage` to verify UI elements and translations.
- Introduced `useIssues` hooks for fetching and managing issue data.
- Added loading skeletons and error handling for better user experience.
- Created `vite-env.d.ts` for TypeScript support in Vite environment.
This commit is contained in:
catlog22
2026-01-31 21:20:10 +08:00
parent 6d225948d1
commit 1bd082a725
79 changed files with 5870 additions and 449 deletions

View File

@@ -18,7 +18,7 @@ interface ServeOptions {
* @param {Object} options - Command options
*/
export async function serveCommand(options: ServeOptions): Promise<void> {
const port = options.port || 3456;
const port = Number(options.port) || 3456;
const host = options.host || '127.0.0.1';
const frontend = options.frontend || 'js';
@@ -75,9 +75,9 @@ export async function serveCommand(options: ServeOptions): Promise<void> {
// Display frontend URLs
if (frontend === 'both') {
console.log(chalk.gray(` JS Frontend: ${boundUrl}`));
console.log(chalk.gray(` React Frontend: ${boundUrl}/react`));
console.log(chalk.gray(` React Frontend: http://${host}:${reactPort}`));
} else if (frontend === 'react') {
console.log(chalk.gray(` React Frontend: ${boundUrl}/react`));
console.log(chalk.gray(` React Frontend: http://${host}:${reactPort}`));
}
// Open browser
@@ -86,10 +86,17 @@ export async function serveCommand(options: ServeOptions): Promise<void> {
try {
// Determine which URL to open based on frontend setting
let openUrl = browserUrl;
if (frontend === 'react') {
openUrl = `${browserUrl}/react`;
if (frontend === 'react' && reactPort) {
// React frontend: access via proxy path /react/
openUrl = `http://${host}:${port}/react/`;
} else if (frontend === 'both') {
// Both frontends: default to JS frontend at root
openUrl = browserUrl;
}
await launchBrowser(openUrl);
// Add path query parameter for workspace switching
const pathParam = initialPath ? `?path=${encodeURIComponent(initialPath)}` : '';
await launchBrowser(openUrl + pathParam);
console.log(chalk.green.bold('\n Dashboard opened in browser!'));
} catch (err) {
const error = err as Error;
@@ -101,9 +108,9 @@ export async function serveCommand(options: ServeOptions): Promise<void> {
console.log(chalk.gray('\n Press Ctrl+C to stop the server\n'));
// Handle graceful shutdown
process.on('SIGINT', () => {
process.on('SIGINT', async () => {
console.log(chalk.yellow('\n Shutting down server...'));
stopReactFrontend();
await stopReactFrontend();
server.close(() => {
console.log(chalk.green(' Server stopped.\n'));
process.exit(0);
@@ -117,7 +124,7 @@ export async function serveCommand(options: ServeOptions): Promise<void> {
console.error(chalk.yellow(` Port ${port} is already in use.`));
console.error(chalk.gray(` Try a different port: ccw serve --port ${port + 1}\n`));
}
stopReactFrontend();
await stopReactFrontend();
process.exit(1);
}
}

View File

@@ -48,6 +48,7 @@ async function killProcess(pid: string): Promise<boolean> {
*/
export async function stopCommand(options: StopOptions): Promise<void> {
const port = options.port || 3456;
const reactPort = port + 1; // React frontend runs on port + 1
const force = options.force || false;
console.log(chalk.blue.bold('\n CCW Dashboard\n'));
@@ -107,6 +108,23 @@ export async function stopCommand(options: StopOptions): Promise<void> {
if (!pid) {
console.log(chalk.yellow(` No server running on port ${port}\n`));
// Also check and clean up React frontend if it's still running
const reactPid = await findProcessOnPort(reactPort);
if (reactPid) {
console.log(chalk.yellow(` React frontend still running on port ${reactPort} (PID: ${reactPid})`));
if (force) {
console.log(chalk.cyan(' Cleaning up React frontend...'));
const killed = await killProcess(reactPid);
if (killed) {
console.log(chalk.green(' React frontend stopped!\n'));
} else {
console.log(chalk.red(' Failed to stop React frontend.\n'));
}
} else {
console.log(chalk.gray(`\n Use --force to clean it up:\n ccw stop --force\n`));
}
}
process.exit(0);
}
@@ -118,7 +136,17 @@ export async function stopCommand(options: StopOptions): Promise<void> {
const killed = await killProcess(pid);
if (killed) {
console.log(chalk.green.bold('\n Process killed successfully!\n'));
console.log(chalk.green(' Main server killed successfully!'));
// Also try to kill React frontend
const reactPid = await findProcessOnPort(reactPort);
if (reactPid) {
console.log(chalk.cyan(` Cleaning up React frontend on port ${reactPort}...`));
await killProcess(reactPid);
console.log(chalk.green(' React frontend stopped!'));
}
console.log(chalk.green.bold('\n All processes stopped successfully!\n'));
process.exit(0);
} else {
console.log(chalk.red('\n Failed to kill process. Try running as administrator.\n'));

View File

@@ -9,6 +9,7 @@ interface ViewOptions {
path?: string;
host?: string;
browser?: boolean;
frontend?: 'js' | 'react' | 'both';
}
interface SwitchWorkspaceResult {
@@ -72,9 +73,10 @@ export async function viewCommand(options: ViewOptions): Promise<void> {
// Check for updates (fire-and-forget, non-blocking)
checkForUpdates().catch(() => { /* ignore errors */ });
const port = options.port || 3456;
const port = Number(options.port) || 3456;
const host = options.host || '127.0.0.1';
const browserHost = host === '0.0.0.0' || host === '::' ? 'localhost' : host;
const frontend = options.frontend || 'both';
// Resolve workspace path
let workspacePath = process.cwd();
@@ -101,8 +103,12 @@ export async function viewCommand(options: ViewOptions): Promise<void> {
if (result.success) {
console.log(chalk.green(` Workspace switched successfully`));
// Open browser with the new path
const url = `http://${browserHost}:${port}/?path=${encodeURIComponent(result.path!)}`;
// Determine URL based on frontend type
let urlPath = '';
if (frontend === 'react') {
urlPath = '/react';
}
const url = `http://${browserHost}:${port}${urlPath}/?path=${encodeURIComponent(result.path!)}`;
if (options.browser !== false) {
console.log(chalk.cyan(' Opening in browser...'));
@@ -127,7 +133,8 @@ export async function viewCommand(options: ViewOptions): Promise<void> {
path: workspacePath,
port: port,
host,
browser: options.browser
browser: options.browser,
frontend: frontend
});
}
}