mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-14 02:42:04 +08:00
refactor: decouple docs-site from ccw monorepo
- Remove docs-frontend.ts utility and docs proxy from server.ts - Remove docs startup logic from serve.ts - Delete HelpPage.tsx and /help route from frontend - Remove help navigation entry from Sidebar.tsx - Clean /docs proxy from vite.config.ts - Remove docs-related scripts from package.json files - Delete help.spec.ts E2E tests - Remove docs-site directory (migrated to D:\ccw-doc) The docs-site has been moved to D:\ccw-doc as a standalone Docusaurus project with baseUrl='/' for static hosting deployment (GitHub Pages/Netlify).
This commit is contained in:
@@ -4,9 +4,8 @@
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "concurrently \"npm run dev:docs\" \"npm run dev:vite\"",
|
||||
"dev": "vite",
|
||||
"dev:vite": "vite",
|
||||
"dev:docs": "cd ../docs-site && npm run start",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
Terminal,
|
||||
Brain,
|
||||
Settings,
|
||||
HelpCircle,
|
||||
PanelLeftClose,
|
||||
PanelLeftOpen,
|
||||
LayoutDashboard,
|
||||
@@ -112,7 +111,6 @@ const navGroupDefinitions: NavGroupDef[] = [
|
||||
{ path: '/settings/codexlens', labelKey: 'navigation.main.codexlens', icon: Sparkles },
|
||||
{ path: '/api-settings', labelKey: 'navigation.main.apiSettings', icon: Server },
|
||||
{ path: '/settings', labelKey: 'navigation.main.settings', icon: Settings, end: true },
|
||||
{ path: '/help', labelKey: 'navigation.main.help', icon: HelpCircle },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,347 +0,0 @@
|
||||
// ========================================
|
||||
// Help Page
|
||||
// ========================================
|
||||
// Help documentation and guides with link to full documentation
|
||||
|
||||
import {
|
||||
HelpCircle,
|
||||
Book,
|
||||
Video,
|
||||
MessageCircle,
|
||||
ExternalLink,
|
||||
Workflow,
|
||||
FolderKanban,
|
||||
Terminal,
|
||||
FileText,
|
||||
ArrowRight,
|
||||
Search,
|
||||
Code,
|
||||
Layers,
|
||||
} from 'lucide-react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { useIntl } from 'react-intl';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
|
||||
interface HelpSection {
|
||||
i18nKey: string;
|
||||
descriptionI18nKey: string;
|
||||
headingI18nKey?: string;
|
||||
icon: React.ElementType;
|
||||
link?: string;
|
||||
isExternal?: boolean;
|
||||
badge?: string;
|
||||
}
|
||||
|
||||
interface HelpSectionConfig {
|
||||
i18nKey: string;
|
||||
descriptionKey: string;
|
||||
headingKey?: string;
|
||||
icon: React.ElementType;
|
||||
link?: string;
|
||||
isExternal?: boolean;
|
||||
badge?: string;
|
||||
}
|
||||
|
||||
const helpSectionsConfig: HelpSectionConfig[] = [
|
||||
{
|
||||
i18nKey: 'home.help.gettingStarted.title',
|
||||
descriptionKey: 'home.help.gettingStarted.description',
|
||||
headingKey: 'home.help.gettingStarted.heading',
|
||||
icon: Book,
|
||||
link: '/docs/overview',
|
||||
isExternal: false,
|
||||
badge: 'Docs',
|
||||
},
|
||||
{
|
||||
i18nKey: 'home.help.orchestratorGuide.title',
|
||||
descriptionKey: 'home.help.orchestratorGuide.description',
|
||||
icon: Workflow,
|
||||
link: '/docs/workflows/introduction',
|
||||
isExternal: false,
|
||||
badge: 'Docs',
|
||||
},
|
||||
{
|
||||
i18nKey: 'home.help.commands.title',
|
||||
descriptionKey: 'home.help.commands.description',
|
||||
icon: Terminal,
|
||||
link: '/docs/commands/general/ccw',
|
||||
isExternal: false,
|
||||
badge: 'Docs',
|
||||
},
|
||||
{
|
||||
i18nKey: 'home.help.sessionsManagement.title',
|
||||
descriptionKey: 'home.help.sessionsManagement.description',
|
||||
icon: FolderKanban,
|
||||
link: '/sessions',
|
||||
},
|
||||
];
|
||||
|
||||
export function HelpPage() {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
// Build help sections with i18n
|
||||
const helpSections: HelpSection[] = helpSectionsConfig.map(section => ({
|
||||
...section,
|
||||
descriptionI18nKey: section.descriptionKey,
|
||||
headingI18nKey: section.headingKey,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="max-w-6xl mx-auto space-y-8">
|
||||
{/* Page Header with CTA */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
|
||||
<div className="flex-1 min-w-0">
|
||||
<h1 className="text-3xl font-bold text-foreground flex items-center gap-3">
|
||||
<HelpCircle className="w-8 h-8 text-primary flex-shrink-0" />
|
||||
<span>{formatMessage({ id: 'help.title' })}</span>
|
||||
</h1>
|
||||
<p className="text-muted-foreground mt-2 text-base">
|
||||
{formatMessage({ id: 'help.description' })}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="default"
|
||||
size="sm"
|
||||
className="flex-shrink-0"
|
||||
asChild
|
||||
>
|
||||
<a href="/docs" target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-2 whitespace-nowrap">
|
||||
<FileText className="w-4 h-4 flex-shrink-0" />
|
||||
{formatMessage({ id: 'help.fullDocs' })}
|
||||
<ExternalLink className="w-3 h-3 flex-shrink-0" />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Links */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{helpSections.map((section) => {
|
||||
const Icon = section.icon;
|
||||
const isDocsLink = section.link?.startsWith('/docs');
|
||||
const content = (
|
||||
<Card className="p-5 h-full hover:shadow-lg hover:border-primary/50 transition-all cursor-pointer group">
|
||||
<div className="flex flex-col h-full gap-3">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="p-2.5 rounded-lg bg-primary/10 text-primary group-hover:bg-primary group-hover:text-primary-foreground transition-colors flex-shrink-0">
|
||||
<Icon className="w-5 h-5" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<h3 className="font-semibold text-foreground group-hover:text-primary transition-colors">
|
||||
{formatMessage({ id: section.i18nKey })}
|
||||
</h3>
|
||||
{section.badge && (
|
||||
<span className="px-2 py-0.5 text-xs font-medium bg-primary/10 text-primary rounded-full flex-shrink-0">
|
||||
{section.badge}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-1.5 line-clamp-2">
|
||||
{formatMessage({ id: section.descriptionI18nKey })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{(isDocsLink || section.isExternal) && (
|
||||
<div className="flex justify-end mt-auto">
|
||||
<ExternalLink className="w-4 h-4 text-muted-foreground group-hover:text-primary transition-colors" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
if (section.link?.startsWith('/docs')) {
|
||||
return (
|
||||
<a key={section.i18nKey} href={section.link} className="block">
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
if (section.link?.startsWith('/') && !section.link.startsWith('/docs')) {
|
||||
return (
|
||||
<Link key={section.i18nKey} to={section.link}>
|
||||
{content}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<a key={section.i18nKey} href={section.link} target="_blank" rel="noopener noreferrer">
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Documentation Overview Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{/* Commands Card */}
|
||||
<Card className="p-6 hover:shadow-md transition-shadow flex flex-col">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 rounded-lg bg-blue-500/10 text-blue-500 flex-shrink-0">
|
||||
<Terminal className="w-5 h-5" />
|
||||
</div>
|
||||
<h3 className="font-semibold text-foreground">
|
||||
{formatMessage({ id: 'help.commandsOverview.title' })}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mb-4 flex-1">
|
||||
{formatMessage({ id: 'help.commandsOverview.description' })}
|
||||
</p>
|
||||
<div className="space-y-2 mb-4">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<Layers className="w-4 h-4 text-muted-foreground flex-shrink-0" />
|
||||
<span className="text-muted-foreground">Workflow Commands</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<Layers className="w-4 h-4 text-muted-foreground flex-shrink-0" />
|
||||
<span className="text-muted-foreground">Issue Commands</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<Layers className="w-4 h-4 text-muted-foreground flex-shrink-0" />
|
||||
<span className="text-muted-foreground">CLI & Memory Commands</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" className="w-full mt-auto" asChild>
|
||||
<a href="/docs/commands/general/ccw" className="inline-flex items-center justify-center whitespace-nowrap">
|
||||
{formatMessage({ id: 'help.viewAll' })}
|
||||
<ArrowRight className="w-4 h-4 ml-1 flex-shrink-0" />
|
||||
</a>
|
||||
</Button>
|
||||
</Card>
|
||||
|
||||
{/* Workflows Card */}
|
||||
<Card className="p-6 hover:shadow-md transition-shadow flex flex-col">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 rounded-lg bg-green-500/10 text-green-500 flex-shrink-0">
|
||||
<Workflow className="w-5 h-5" />
|
||||
</div>
|
||||
<h3 className="font-semibold text-foreground">
|
||||
{formatMessage({ id: 'help.workflowsOverview.title' })}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mb-4 flex-1">
|
||||
{formatMessage({ id: 'help.workflowsOverview.description' })}
|
||||
</p>
|
||||
<div className="space-y-2 mb-4">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<Code className="w-4 h-4 text-muted-foreground flex-shrink-0" />
|
||||
<span className="text-muted-foreground">Level 1-5 Workflows</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<Search className="w-4 h-4 text-muted-foreground flex-shrink-0" />
|
||||
<span className="text-muted-foreground">Interactive Diagrams</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<FileText className="w-4 h-4 text-muted-foreground flex-shrink-0" />
|
||||
<span className="text-muted-foreground">Best Practices</span>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" className="w-full mt-auto" asChild>
|
||||
<a href="/docs/workflows/introduction" className="inline-flex items-center justify-center whitespace-nowrap">
|
||||
{formatMessage({ id: 'help.viewAll' })}
|
||||
<ArrowRight className="w-4 h-4 ml-1 flex-shrink-0" />
|
||||
</a>
|
||||
</Button>
|
||||
</Card>
|
||||
|
||||
{/* Quick Start Card */}
|
||||
<Card className="p-6 hover:shadow-md transition-shadow flex flex-col">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="p-2 rounded-lg bg-purple-500/10 text-purple-500 flex-shrink-0">
|
||||
<Book className="w-5 h-5" />
|
||||
</div>
|
||||
<h3 className="font-semibold text-foreground">
|
||||
{formatMessage({ id: 'help.quickStart.title' })}
|
||||
</h3>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mb-4 flex-1">
|
||||
{formatMessage({ id: 'help.quickStart.description' })}
|
||||
</p>
|
||||
<div className="space-y-2 mb-4">
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<ExternalLink className="w-4 h-4 text-muted-foreground flex-shrink-0" />
|
||||
<a href="/docs/overview" className="text-muted-foreground hover:text-foreground transition-colors">
|
||||
{formatMessage({ id: 'help.quickStart.guide' })}
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<MessageCircle className="w-4 h-4 text-muted-foreground flex-shrink-0" />
|
||||
<a href="/docs/faq" className="text-muted-foreground hover:text-foreground transition-colors">
|
||||
{formatMessage({ id: 'help.quickStart.faq' })}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" className="w-full mt-auto" asChild>
|
||||
<a href="/docs/overview" className="inline-flex items-center justify-center whitespace-nowrap">
|
||||
{formatMessage({ id: 'help.getStarted' })}
|
||||
<ArrowRight className="w-4 h-4 ml-1 flex-shrink-0" />
|
||||
</a>
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Search Documentation CTA */}
|
||||
<Card className="p-6 sm:p-8 bg-gradient-accent border-primary/20">
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-center gap-4 sm:justify-between">
|
||||
<div className="flex items-start gap-4 flex-1 min-w-0">
|
||||
<div className="p-3 rounded-lg bg-primary/20 flex-shrink-0">
|
||||
<Search className="w-6 h-6 text-primary" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-lg font-semibold text-foreground">
|
||||
{formatMessage({ id: 'help.searchDocs.title' })}
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{formatMessage({ id: 'help.searchDocs.description' })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="default" size="sm" className="flex-shrink-0 w-full sm:w-auto" asChild>
|
||||
<a href="/docs" className="inline-flex items-center justify-center gap-2 whitespace-nowrap">
|
||||
{formatMessage({ id: 'help.searchDocs.button' })}
|
||||
<ArrowRight className="w-4 h-4 flex-shrink-0" />
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Support Section */}
|
||||
<Card className="p-6 bg-primary/5 border-primary/20">
|
||||
<div className="flex flex-col sm:flex-row sm:items-start gap-4">
|
||||
<div className="p-3 rounded-lg bg-primary/10 flex-shrink-0">
|
||||
<MessageCircle className="w-6 h-6 text-primary" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h3 className="text-lg font-semibold text-foreground">
|
||||
{formatMessage({ id: 'help.support.title' })}
|
||||
</h3>
|
||||
<p className="text-muted-foreground text-sm mt-2">
|
||||
{formatMessage({ id: 'help.support.description' })}
|
||||
</p>
|
||||
<div className="flex flex-wrap gap-3 mt-4">
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<a href="/docs/faq" className="inline-flex items-center whitespace-nowrap">
|
||||
<Book className="w-4 h-4 mr-2 flex-shrink-0" />
|
||||
{formatMessage({ id: 'help.support.documentation' })}
|
||||
</a>
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" asChild>
|
||||
<a href="https://github.com/catlog22/Claude-Code-Workflow/issues" target="_blank" rel="noopener noreferrer" className="inline-flex items-center whitespace-nowrap">
|
||||
<Video className="w-4 h-4 mr-2 flex-shrink-0" />
|
||||
{formatMessage({ id: 'help.support.tutorials' })}
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default HelpPage;
|
||||
@@ -18,7 +18,6 @@ export { SkillsManagerPage } from './SkillsManagerPage';
|
||||
export { CommandsManagerPage } from './CommandsManagerPage';
|
||||
export { MemoryPage } from './MemoryPage';
|
||||
export { SettingsPage } from './SettingsPage';
|
||||
export { HelpPage } from './HelpPage';
|
||||
export { HookManagerPage } from './HookManagerPage';
|
||||
export { NotFoundPage } from './NotFoundPage';
|
||||
export { LiteTasksPage } from './LiteTasksPage';
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
CommandsManagerPage,
|
||||
MemoryPage,
|
||||
SettingsPage,
|
||||
HelpPage,
|
||||
NotFoundPage,
|
||||
LiteTasksPage,
|
||||
// LiteTaskDetailPage removed - now using TaskDrawer instead
|
||||
@@ -157,10 +156,6 @@ const routes: RouteObject[] = [
|
||||
path: 'api-settings',
|
||||
element: <ApiSettingsPage />,
|
||||
},
|
||||
{
|
||||
path: 'help',
|
||||
element: <HelpPage />,
|
||||
},
|
||||
{
|
||||
path: 'hooks',
|
||||
element: <HookManagerPage />,
|
||||
@@ -228,7 +223,6 @@ export const ROUTES = {
|
||||
SETTINGS_RULES: '/settings/rules',
|
||||
CODEXLENS_MANAGER: '/settings/codexlens',
|
||||
API_SETTINGS: '/api-settings',
|
||||
HELP: '/help',
|
||||
EXPLORER: '/explorer',
|
||||
GRAPH: '/graph',
|
||||
TEAMS: '/teams',
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
// ========================================
|
||||
// E2E Tests: Help Page
|
||||
// ========================================
|
||||
// End-to-end tests for help documentation page
|
||||
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { setupEnhancedMonitoring, switchLanguageAndVerify } from './helpers/i18n-helpers';
|
||||
|
||||
test.describe('[Help] - Help Page Tests', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/help', { waitUntil: 'networkidle' as const });
|
||||
});
|
||||
|
||||
test('L3.50 - should display help documentation content', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Look for help page content
|
||||
const helpContent = page.getByTestId('help-content').or(
|
||||
page.locator('.help-documentation')
|
||||
).or(
|
||||
page.locator('main')
|
||||
);
|
||||
|
||||
await expect(helpContent).toBeVisible();
|
||||
|
||||
// Verify page title is present
|
||||
const pageTitle = page.getByRole('heading', { name: /help|帮助/i }).or(
|
||||
page.locator('h1')
|
||||
);
|
||||
|
||||
const hasTitle = await pageTitle.isVisible().catch(() => false);
|
||||
expect(hasTitle).toBe(true);
|
||||
|
||||
// Verify help sections are displayed
|
||||
const helpSections = page.locator('a[href*="/docs"], a[href^="/docs"]').or(
|
||||
page.locator('[data-testid*="help"]')
|
||||
);
|
||||
|
||||
const sectionCount = await helpSections.count();
|
||||
expect(sectionCount).toBeGreaterThan(0);
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.51 - should display documentation navigation links', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Look for documentation links
|
||||
const docLinks = page.locator('a[href*="/docs/"], a[href^="/docs"]').or(
|
||||
page.locator('[data-testid="docs-link"]')
|
||||
);
|
||||
|
||||
const linkCount = await docLinks.count();
|
||||
expect(linkCount).toBeGreaterThan(0);
|
||||
|
||||
// Verify links have proper structure
|
||||
for (let i = 0; i < Math.min(linkCount, 3); i++) {
|
||||
const link = docLinks.nth(i);
|
||||
await expect(link).toHaveAttribute('href');
|
||||
}
|
||||
|
||||
// Look for "Full Documentation" button/link
|
||||
const fullDocsLink = page.getByRole('link', { name: /full.*docs|documentation/i }).or(
|
||||
page.locator('a[href="/docs"]')
|
||||
);
|
||||
|
||||
const hasFullDocs = await fullDocsLink.isVisible().catch(() => false);
|
||||
expect(hasFullDocs).toBe(true);
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.52 - should support i18n (English/Chinese switching)', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Get language switcher
|
||||
const languageSwitcher = page.getByRole('combobox', { name: /select language|language/i }).first();
|
||||
|
||||
const hasLanguageSwitcher = await languageSwitcher.isVisible().catch(() => false);
|
||||
|
||||
if (hasLanguageSwitcher) {
|
||||
// Switch to Chinese
|
||||
await switchLanguageAndVerify(page, 'zh', languageSwitcher);
|
||||
|
||||
// Verify help content is in Chinese
|
||||
const pageContent = await page.content();
|
||||
const hasChinese = /[\u4e00-\u9fa5]/.test(pageContent);
|
||||
expect(hasChinese).toBe(true);
|
||||
|
||||
// Switch back to English
|
||||
await switchLanguageAndVerify(page, 'en', languageSwitcher);
|
||||
|
||||
// Verify help content is in English
|
||||
const englishContent = await page.content();
|
||||
const hasEnglish = /[a-zA-Z]{5,}/.test(englishContent);
|
||||
expect(hasEnglish).toBe(true);
|
||||
}
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.53 - should display quick links and overview cards', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Look for quick link cards
|
||||
const quickLinkCards = page.locator('a[href*="/docs"], a[href="/sessions"]').or(
|
||||
page.locator('[data-testid*="card"], .card')
|
||||
);
|
||||
|
||||
const cardCount = await quickLinkCards.count();
|
||||
expect(cardCount).toBeGreaterThan(0);
|
||||
|
||||
// Verify documentation overview cards exist
|
||||
const overviewCards = page.locator('a[href*="/docs/commands"], a[href*="/docs/workflows"], a[href*="/docs/overview"]').or(
|
||||
page.locator('[data-testid*="overview"]')
|
||||
);
|
||||
|
||||
const overviewCount = await overviewCards.count();
|
||||
expect(overviewCount).toBeGreaterThan(0);
|
||||
|
||||
// Look for specific help sections (Getting Started, Orchestrator Guide, Commands)
|
||||
const gettingStartedLink = page.getByRole('link', { name: /getting.*started|入门/i });
|
||||
const orchestratorGuideLink = page.getByRole('link', { name: /orchestrator.*guide|编排指南/i });
|
||||
const commandsLink = page.getByRole('link', { name: /commands|命令/i });
|
||||
|
||||
const hasGettingStarted = await gettingStartedLink.isVisible().catch(() => false);
|
||||
const hasOrchestratorGuide = await orchestratorGuideLink.isVisible().catch(() => false);
|
||||
const hasCommands = await commandsLink.isVisible().catch(() => false);
|
||||
|
||||
// At least one help section should be visible
|
||||
expect(hasGettingStarted || hasOrchestratorGuide || hasCommands).toBe(true);
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
|
||||
test('L3.54 - should ensure basic accessibility and page structure', async ({ page }) => {
|
||||
const monitoring = setupEnhancedMonitoring(page);
|
||||
|
||||
// Verify main content area exists
|
||||
const mainContent = page.locator('main').or(
|
||||
page.locator('#main-content')
|
||||
).or(
|
||||
page.locator('[role="main"]')
|
||||
);
|
||||
|
||||
await expect(mainContent).toBeVisible();
|
||||
|
||||
// Verify page has proper heading structure
|
||||
const h1 = page.locator('h1');
|
||||
const hasH1 = await h1.count();
|
||||
expect(hasH1).toBeGreaterThanOrEqual(1);
|
||||
|
||||
// Look for skip to main content link (accessibility feature)
|
||||
const skipLink = page.getByRole('link', { name: /skip to main content|跳转到主要内容/i });
|
||||
|
||||
const hasSkipLink = await skipLink.isVisible().catch(() => false);
|
||||
// Skip link may not be visible by default, so we don't fail if missing
|
||||
if (hasSkipLink) {
|
||||
await expect(skipLink).toHaveAttribute('href');
|
||||
}
|
||||
|
||||
// Verify focus management on interactive elements
|
||||
const interactiveElements = page.locator('button, a[href], [tabindex]:not([tabindex="-1"])');
|
||||
const interactiveCount = await interactiveElements.count();
|
||||
expect(interactiveCount).toBeGreaterThan(0);
|
||||
|
||||
monitoring.assertClean({ allowWarnings: true });
|
||||
monitoring.stop();
|
||||
});
|
||||
});
|
||||
@@ -13,7 +13,7 @@ const __dirname = path.dirname(__filename)
|
||||
const basePath = process.env.VITE_BASE_URL || '/react/'
|
||||
|
||||
// Backend target for Vite proxy (used when directly opening the Vite dev server port).
|
||||
// In `ccw view`, this is set to the dashboard server port so /api, /ws, and /docs all route correctly.
|
||||
// In `ccw view`, this is set to the dashboard server port so /api and /ws route correctly.
|
||||
const backendHost = process.env.VITE_BACKEND_HOST || 'localhost'
|
||||
const backendPort = Number(process.env.VITE_BACKEND_PORT || '3456')
|
||||
const backendHttpTarget = `http://${backendHost}:${backendPort}`
|
||||
@@ -44,14 +44,6 @@ export default defineConfig({
|
||||
target: backendWsTarget,
|
||||
ws: true,
|
||||
},
|
||||
// Docs proxy
|
||||
// Forwards /docs requests to the dashboard server, which proxies to the docs server.
|
||||
'/docs': {
|
||||
target: backendHttpTarget,
|
||||
changeOrigin: true,
|
||||
// Preserve /docs prefix to match the dashboard's /docs proxy and Docusaurus baseUrl.
|
||||
// Example: /docs/overview -> http://localhost:{backendPort}/docs/overview
|
||||
},
|
||||
},
|
||||
},
|
||||
build: {
|
||||
|
||||
Reference in New Issue
Block a user