feat: remove old vanilla JS/CSS frontend, make React SPA the sole entry for ccw view

Remove the entire old template-based frontend (~106K lines) and make the React
SPA the only way to access the ccw dashboard via `ccw view`.

Key changes:
- Delete all old frontend files: dashboard-css/ (37 CSS), dashboard-js/ (59 JS),
  assets/, dashboard.html, and legacy HTML templates
- Delete dashboard-generator.ts and dashboard-generator-patch.ts
- Simplify server.ts: remove ~234 lines of old frontend code (template constants,
  MODULE_CSS_FILES/MODULE_FILES arrays, generateServerDashboard(), /assets/* serving)
- Rebase React frontend from /react/ to root / (vite.config.ts, react-frontend.ts)
- Add /react/* -> /* 301 redirect for backward compatibility
- Remove --frontend and --new CLI flags from view and serve commands
- Remove generateDashboard export from public API (index.ts)
- Simplify serve.ts and view.ts to always use React without conditional branching
- Update all affected tests (unit, e2e) for React-only architecture

BREAKING CHANGE: --frontend and --new CLI flags removed; generateDashboard export
removed from ccw package; /react/ base path changed to /
This commit is contained in:
catlog22
2026-02-13 17:26:03 +08:00
parent 31f37751fc
commit bcb736709f
136 changed files with 204 additions and 115952 deletions

View File

@@ -1,17 +1,14 @@
import { startServer } from '../core/server.js';
import { launchBrowser } from '../utils/browser-launcher.js';
import { resolvePath, validatePath } from '../utils/path-resolver.js';
import { validatePath } from '../utils/path-resolver.js';
import { startReactFrontend, stopReactFrontend } from '../utils/react-frontend.js';
import chalk from 'chalk';
import type { Server } from 'http';
interface ServeOptions {
port?: number;
path?: string;
host?: string;
browser?: boolean;
frontend?: 'js' | 'react' | 'both';
new?: boolean;
}
/**
@@ -21,11 +18,8 @@ interface ServeOptions {
export async function serveCommand(options: ServeOptions): Promise<void> {
const port = Number(options.port) || 3456;
const host = options.host || '127.0.0.1';
// --new flag is shorthand for --frontend react
const frontend = options.new ? 'react' : (options.frontend || 'js');
// Keep Vite dev-server proxy aligned with the dashboard server port for direct access
// (e.g. when opening http://localhost:{reactPort} instead of the proxied /react/ path).
// Keep Vite dev-server proxy aligned with the dashboard server port.
process.env.VITE_BACKEND_PORT = port.toString();
// Validate project path
@@ -42,19 +36,15 @@ export async function serveCommand(options: ServeOptions): Promise<void> {
console.log(chalk.blue.bold('\n CCW Dashboard Server\n'));
console.log(chalk.gray(` Initial project: ${initialPath}`));
console.log(chalk.gray(` Host: ${host}`));
console.log(chalk.gray(` Port: ${port}`));
console.log(chalk.gray(` Frontend: ${frontend}\n`));
console.log(chalk.gray(` Port: ${port}\n`));
// Start React frontend if needed
let reactPort: number | undefined;
if (frontend === 'react' || frontend === 'both') {
reactPort = port + 1;
try {
await startReactFrontend(reactPort);
} catch (error) {
console.error(chalk.red(`\n Failed to start React frontend: ${error}\n`));
process.exit(1);
}
// Start React frontend
const reactPort = port + 1;
try {
await startReactFrontend(reactPort);
} catch (error) {
console.error(chalk.red(`\n Failed to start React frontend: ${error}\n`));
process.exit(1);
}
try {
@@ -64,7 +54,6 @@ export async function serveCommand(options: ServeOptions): Promise<void> {
port,
host,
initialPath,
frontend,
reactPort
});
@@ -78,31 +67,12 @@ export async function serveCommand(options: ServeOptions): Promise<void> {
console.log(chalk.green(` Server running at ${boundUrl}`));
// Display frontend URLs
if (frontend === 'both') {
console.log(chalk.gray(` JS Frontend: ${boundUrl}`));
console.log(chalk.gray(` React Frontend: http://${host}:${reactPort}`));
} else if (frontend === 'react') {
console.log(chalk.gray(` React Frontend: http://${host}:${reactPort}`));
}
// Open browser
if (options.browser !== false) {
console.log(chalk.cyan(' Opening in browser...'));
try {
// Determine which URL to open based on frontend setting
let openUrl = browserUrl;
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;
}
// Add path query parameter for workspace switching
const pathParam = initialPath ? `?path=${encodeURIComponent(initialPath)}` : '';
await launchBrowser(openUrl + pathParam);
await launchBrowser(browserUrl + pathParam);
console.log(chalk.green.bold('\n Dashboard opened in browser!'));
} catch (err) {
const error = err as Error;

View File

@@ -9,8 +9,6 @@ interface ViewOptions {
path?: string;
host?: string;
browser?: boolean;
frontend?: 'js' | 'react' | 'both';
new?: boolean;
}
interface SwitchWorkspaceResult {
@@ -77,8 +75,6 @@ export async function viewCommand(options: ViewOptions): Promise<void> {
const port = Number(options.port) || 3456;
const host = options.host || '127.0.0.1';
const browserHost = host === '0.0.0.0' || host === '::' ? 'localhost' : host;
// --new flag is shorthand for --frontend react
const frontend = options.new ? 'react' : (options.frontend || 'js');
// Resolve workspace path
let workspacePath = process.cwd();
@@ -105,12 +101,7 @@ export async function viewCommand(options: ViewOptions): Promise<void> {
if (result.success) {
console.log(chalk.green(` Workspace switched successfully`));
// Determine URL based on frontend type
let urlPath = '';
if (frontend === 'react') {
urlPath = '/react';
}
const url = `http://${browserHost}:${port}${urlPath}/?path=${encodeURIComponent(workspacePath)}`;
const url = `http://${browserHost}:${port}/?path=${encodeURIComponent(workspacePath)}`;
if (options.browser !== false) {
console.log(chalk.cyan(' Opening in browser...'));
@@ -135,8 +126,7 @@ export async function viewCommand(options: ViewOptions): Promise<void> {
path: workspacePath,
port: port,
host,
browser: options.browser,
frontend: frontend
browser: options.browser
});
}
}