Skip to content

Instantly share code, notes, and snippets.

@1nk1
Last active March 12, 2026 19:52
Show Gist options
  • Select an option

  • Save 1nk1/c26294471bfc049cd89b18e5614c7f2f to your computer and use it in GitHub Desktop.

Select an option

Save 1nk1/c26294471bfc049cd89b18e5614c7f2f to your computer and use it in GitHub Desktop.
Repository Wiki — generated by GitNexus
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>test-forge — Wiki</title>
<script src="https://cdn.jsdelivr.net/npm/marked@11.0.0/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{
--bg:#ffffff;--sidebar-bg:#f8f9fb;--border:#e5e7eb;
--text:#1e293b;--text-muted:#64748b;--primary:#2563eb;
--primary-soft:#eff6ff;--hover:#f1f5f9;--code-bg:#f1f5f9;
--radius:8px;--shadow:0 1px 3px rgba(0,0,0,.08);
}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
line-height:1.65;color:var(--text);background:var(--bg)}
.layout{display:flex;min-height:100vh}
.sidebar{width:280px;background:var(--sidebar-bg);border-right:1px solid var(--border);
position:fixed;top:0;left:0;bottom:0;overflow-y:auto;padding:24px 16px;
display:flex;flex-direction:column;z-index:10}
.content{margin-left:280px;flex:1;padding:48px 64px;max-width:960px}
.sidebar-header{margin-bottom:20px;padding-bottom:16px;border-bottom:1px solid var(--border)}
.sidebar-title{font-size:16px;font-weight:700;color:var(--text);display:flex;align-items:center;gap:8px}
.sidebar-title svg{flex-shrink:0}
.sidebar-meta{font-size:11px;color:var(--text-muted);margin-top:6px}
.nav-section{margin-bottom:2px}
.nav-item{display:block;padding:7px 12px;border-radius:var(--radius);cursor:pointer;
font-size:13px;color:var(--text);text-decoration:none;transition:all .15s;
white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.nav-item:hover{background:var(--hover)}
.nav-item.active{background:var(--primary-soft);color:var(--primary);font-weight:600}
.nav-item.overview{font-weight:600;margin-bottom:4px}
.nav-children{padding-left:14px;border-left:1px solid var(--border);margin-left:12px}
.nav-group-label{font-size:11px;font-weight:600;color:var(--text-muted);
text-transform:uppercase;letter-spacing:.5px;padding:12px 12px 4px;user-select:none}
.sidebar-footer{margin-top:auto;padding-top:16px;border-top:1px solid var(--border);
font-size:11px;color:var(--text-muted);text-align:center}
.content h1{font-size:28px;font-weight:700;margin-bottom:8px;line-height:1.3}
.content h2{font-size:22px;font-weight:600;margin:32px 0 12px;padding-bottom:6px;border-bottom:1px solid var(--border)}
.content h3{font-size:17px;font-weight:600;margin:24px 0 8px}
.content h4{font-size:15px;font-weight:600;margin:20px 0 6px}
.content p{margin:12px 0}
.content ul,.content ol{margin:12px 0 12px 24px}
.content li{margin:4px 0}
.content a{color:var(--primary);text-decoration:none}
.content a:hover{text-decoration:underline}
.content blockquote{border-left:3px solid var(--primary);padding:8px 16px;margin:16px 0;
background:var(--primary-soft);border-radius:0 var(--radius) var(--radius) 0;
color:var(--text-muted);font-size:14px}
.content code{font-family:'SF Mono',Consolas,'Courier New',monospace;font-size:13px;
background:var(--code-bg);padding:2px 6px;border-radius:4px}
.content pre{background:#1e293b;color:#e2e8f0;border-radius:var(--radius);padding:16px;
overflow-x:auto;margin:16px 0}
.content pre code{background:none;padding:0;font-size:13px;line-height:1.6;color:inherit}
.content table{border-collapse:collapse;width:100%;margin:16px 0}
.content th,.content td{border:1px solid var(--border);padding:8px 12px;text-align:left;font-size:14px}
.content th{background:var(--sidebar-bg);font-weight:600}
.content img{max-width:100%;border-radius:var(--radius)}
.content hr{border:none;border-top:1px solid var(--border);margin:32px 0}
.content .mermaid{margin:20px 0;text-align:center}
.menu-toggle{display:none;position:fixed;top:12px;left:12px;z-index:20;
background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);
padding:8px 12px;cursor:pointer;font-size:18px;box-shadow:var(--shadow)}
@media(max-width:768px){
.sidebar{transform:translateX(-100%);transition:transform .2s}
.sidebar.open{transform:translateX(0);box-shadow:2px 0 12px rgba(0,0,0,.1)}
.content{margin-left:0;padding:24px 20px;padding-top:56px}
.menu-toggle{display:block}
}
.empty-state{text-align:center;padding:80px 20px;color:var(--text-muted)}
.empty-state h2{font-size:20px;margin-bottom:8px;border:none}
</style>
</head>
<body>
<button class="menu-toggle" id="menu-toggle" aria-label="Toggle menu">&#9776;</button>
<div class="layout">
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
<div class="sidebar-title">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2z"/><path d="M22 3h-6a4 4 0 00-4 4v14a3 3 0 013-3h7z"/></svg>
test-forge
</div>
<div class="sidebar-meta" id="meta-info"></div>
</div>
<div id="nav-tree"></div>
<div class="sidebar-footer">Generated by GitNexus</div>
</nav>
<main class="content" id="content">
<div class="empty-state"><h2>Loading…</h2></div>
</main>
</div>
<script>
var PAGES = {"api-scripts":"# API Scripts\n\n# API Scripts Module\n\nThis module contains two standalone scripts for the Test Forge application:\n\n1. **`seed.ts`** — Populates the database with realistic test data for development and testing\n2. **`smoke-test.ts`** — Validates that API endpoints return expected data shapes\n\nThese scripts are run manually (not as part of the application runtime) and are essential for local development, CI/CD verification, and ensuring the application works with populated data.\n\n---\n\n## seed.ts\n\n### Purpose\n\nThe seed script populates the Test Forge database with realistic mock data representing a typical testing environment. It creates:\n\n- 5 projects of different types (E2E, API, Performance, Integration, SEO)\n- ~150+ unique test definitions across all projects\n- 50 runs (10 per project) spread over 30 days\n- ~2,500+ test results with various statuses\n- Performance metrics for the K6 project\n- Retention configuration\n\nThis data enables developers to:\n- Test the UI with populated data\n- Verify API responses return meaningful results\n- Test filtering, sorting, and aggregation logic\n- Validate retention cleanup jobs\n\n### How It Works\n\nThe seed process follows a specific order to respect foreign key constraints:\n\n```mermaid\nflowchart TD\n A[Clear all tables] --> B[Insert Projects]\n B --> C[Generate Test Definitions]\n C --> D[Insert Tests]\n D --> E[Generate Runs]\n E --> F[Generate Test Results]\n F --> G[Generate Perf Metrics]\n G --> H[Insert Retention Config]\n \n E --> F\n F --> G\n```\n\n### Key Components\n\n#### Helper Functions\n\n| Function | Purpose |\n|----------|---------|\n| `randomInt(min, max)` | Returns a random integer between min and max (inclusive) |\n| `randomPick(arr)` | Returns a random element from an array |\n| `randomDate(daysAgo)` | Returns a random date within the last N days |\n| `hashTitle(projectSlug, title)` | Creates a SHA-256 hash for test title uniqueness |\n| `randomHex(length)` | Generates a random hex string (for commit SHAs) |\n| `batchInsert(table, rows)` | Inserts rows in batches of 50 to avoid DB overload |\n| `batchInsertReturning(table, rows)` | Same as batchInsert but returns inserted rows with IDs |\n\n#### Test Definition Generators\n\nEach project type has a dedicated generator function:\n\n- **`generateE2ETests()`** — Creates tests for 6 functional areas (auth, casino, sport, promos, payments, profile) across all partner/device combinations. Includes 5 predefined \"flaky\" tests.\n\n- **`generateAPITests()`** — Creates tests for 5 API endpoint groups (auth, betting, payments, users, content) with various HTTP methods and status checks.\n\n- **`generateK6Tests()`** — Creates 8 performance check definitions (response time, status codes, etc.).\n\n- **`generateIntegrationTests()`** — Creates end-to-end flow tests for 5 business processes (auth, bet placement, deposit, withdrawal, registration).\n\n- **`generateSEOTests()`** — Creates tests for Lighthouse audits, meta tags, structured data, and Core Web Vitals.\n\n#### Run Generator\n\n`generateRunsForProject(config)` creates realistic run data:\n\n- **10 runs per project** spread chronologically over 30 days\n- **Pass rate trends** — later runs have slightly better pass rates (simulating improvements)\n- **Flaky test behavior** — tests marked `isFlaky` randomly pass/fail with retries\n- **Realistic error messages** — project-specific error strings (E2E, API, Integration, SEO)\n- **Performance degradation** — K6 project shows degrading metrics in later runs\n\n#### Performance Metrics Generator\n\n`generatePerfMetrics(runId, runIndex, createdAt)` generates k6-style metrics:\n\n- Global metrics (http_req_duration, http_reqs, iterations, vus)\n- Per-endpoint metrics with percentiles (p90, p95, p99)\n- Degradation simulation — some endpoints fail thresholds in later runs\n\n### Data Constants\n\nThe script uses predefined constants for realistic data:\n\n```typescript\nconst PARTNERS = [\"crypto\", \"ua14\", \"allwin\", \"betfavbet\", \"ro\"];\nconst DEVICES = [\"desktop\", \"mobile\"];\nconst ENVIRONMENTS = [\"staging\", \"production\"];\nconst TRIGGER_TYPES = [\"schedule\", \"push\", \"manual\", \"merge_request\"];\nconst BRANCHES = [\"main\", \"develop\", \"feature/new-deposit-flow\", ...];\n```\n\n### Running the Seed Script\n\n```bash\n# From the api package\ncd packages/api\nnpx tsx scripts/seed.ts\n```\n\nExpected output:\n```\n🌱 Seeding Test Forge database...\n\nClearing existing data...\n Cleared all tables\n\nInserting projects...\n Inserted 5 projects\n\nGenerating test definitions...\n Inserted 100 tests for fe-e2e-unified\n Inserted 21 tests for webapi-tests\n ...\n Total: 157 unique tests\n\nGenerating runs and test results...\n Inserted 10 runs for fe-e2e-unified\n ...\n\nInserting 2500 test results...\n Inserted 2500 test results\n\nInserting 320 perf metrics...\n Inserted 320 perf metrics\n\n==================================================\n📊 Seed Summary:\n Projects: 5\n Unique tests: 157\n Runs: 50\n Test results: 2500\n Perf metrics: 320\n Retention cfg: 5\n==================================================\n\n✅ Seed complete!\n```\n\n---\n\n## smoke-test.ts\n\n### Purpose\n\nThe smoke test validates that core API endpoints return data in the expected shape. It's a quick sanity check to ensure:\n\n- The server is running and responding\n- Database queries return data (after seeding)\n- Response schemas match what the frontend expects\n- Filtering and pagination work correctly\n\n### How It Works\n\nThe script defines an array of test cases, each with:\n- A human-readable name\n- The API URL to test\n- A validation function that checks the response shape\n\nIt then iterates through all tests, making HTTP requests and validating responses.\n\n### Test Coverage\n\n| Category | Endpoints Tested |\n|----------|-----------------|\n| Health | `GET /health` |\n| Dashboard | `GET /dashboard` (summary, byProject, recentRuns) |\n| Runs | `GET /runs` (list, filtering by projectSlug) |\n| Run Detail | `GET /runs/:id` (run, project, results) |\n| Flaky | `GET /flaky` |\n| Trends | `GET /tests/:id/trend` |\n\n### Running the Smoke Test\n\n```bash\n# Start the server first\ncd packages/api\nnpm run dev\n\n# In another terminal, run the smoke test\ncd packages/api\nnpx tsx scripts/smoke-test.ts\n```\n\nOr add to `package.json`:\n```json\n{\n \"scripts\": {\n \"smoke\": \"tsx scripts/smoke-test.ts\"\n }\n}\n```\n\nThen run:\n```bash\nnpm run smoke\n```\n\nExpected output:\n```\n Test Forge — Smoke Test\n\n ✓ GET /health returns status ok\n ✓ GET /dashboard returns summary with runs\n ✓ GET /dashboard byProject has 5 items\n ✓ GET /dashboard recentRuns has up to 10 items\n ✓ GET /runs returns data and total\n ✓ GET /runs items have required fields\n ✓ GET /runs?projectSlug=fe-e2e-unified filters correctly\n ✓ GET /runs/1 returns run, project, results\n ✓ GET /runs/1 results have test info\n ✓ GET /flaky returns data array\n ✓ GET /tests/1/trend returns test and history\n ✓ GET /tests/1/trend history has entries\n\n 12 passed, 0 failed\n```\n\nThe script exits with code 1 if any test fails.\n\n---\n\n## Relationship to Other Modules\n\n### Database Schema\n\nBoth scripts interact with the database schema defined in `packages/api/src/db/schema.js`:\n\n- `projects` — Test projects (FE E2E, WebAPI, K6, etc.)\n- `tests` — Test definitions with titles, suites, tags\n- `runs` — Execution runs with pass/fail counts, duration, metadata\n- `testResults` — Individual test outcomes within runs\n- `perfMetrics` — k6 performance metrics\n- `retentionConfig` — Data retention policies per project\n\n### API Routes\n\nThe smoke test validates routes defined in `packages/api/src/routes/`. The seed data must match what these routes expect (field names, types, relationships).\n\n### Frontend\n\nThe seed data is designed to exercise frontend components:\n- Dashboard charts and summaries\n- Run lists with filtering\n- Test detail pages with history\n- Flaky test detection UI\n\n---\n\n## Common Tasks\n\n### Reset the Database\n\n```bash\n# Just run the seed script — it clears all tables first\nnpx tsx scripts/seed.ts\n```\n\n### Add a New Project Type\n\n1. Add the project to the `PROJECTS` constant\n2. Create a new generator function (e.g., `generateNewTypeTests()`)\n3. Add the project config to `projectConfigs` in `seed()`\n4. Add error messages if needed\n\n### Add a New Smoke Test\n\nAdd to the `tests` array in `smoke-test.ts`:\n\n```typescript\n{\n name: \"GET /your-endpoint does something\",\n url: \"/your-endpoint\",\n validate: (d) => d.yourField !== undefined,\n}\n```\n\n### Adjust Seed Data Volume\n\nModify values in `projectConfigs`:\n\n```typescript\n{\n projectSlug: \"fe-e2e-unified\",\n runCount: 10, // Increase for more runs\n testsPerRun: [80, 100], // Increase for more results per run\n // ...\n}\n```","api-server":"# API Server\n\n# API Server Module\n\n## Overview\n\nThe API Server module (`@test-forge/api`) is the backend core of the Test Forge platform. It provides a RESTful API for test run management, analytics, and defect tracking, while simultaneously serving the frontend SPA in production.\n\nBuilt on [Hono](https://hono.dev/) — a fast, lightweight web framework for the Edges — the server handles:\n\n- **Test run lifecycle management** — tracking, status updates, and artifacts\n- **Analytics & insights** — trends, performance metrics, flaky test detection\n- **Integration endpoints** — JIRA webhooks, Teams notifications\n- **Static asset delivery** — serving the built React frontend\n\n## Architecture\n\nThe server follows a modular route-based architecture where each domain gets its own router. The Hono app acts as a composition root that merges these routers under a common `/api` prefix.\n\n```mermaid\nflowchart TB\n subgraph Client[\"Client (Browser)\"]\n SPA[SPA Frontend]\n end\n \n subgraph Server[\"API Server (Hono)\"]\n subgraph Middleware[\"Middleware Stack\"]\n Logger[logger]\n CORS[cors]\n end\n \n subgraph Routes[\"API Routes\"]\n Health[health]\n Runs[runs]\n Tests[tests]\n Flaky[flaky]\n Mute[mute]\n Perf[perf]\n Errors[errors]\n Compare[compare]\n Gate[gate]\n Projects[projects]\n Insights[insights]\n Trends[trends]\n Defects[defects]\n Dashboard[dashboard]\n end\n \n Static[Static Files<br/>./public]\n SPA_Fallback[SPA Fallback<br/>index.html]\n end\n \n subgraph Data[\"Data Layer\"]\n DB[(PostgreSQL)]\n Drizzle[Drizzle ORM]\n PGBoss[pg-boss<br/>Background Jobs]\n end\n \n subgraph External[\"External Services\"]\n S3[S3 Compatible Storage]\n JIRA[JIRA]\n Teams[MS Teams]\n end\n \n Client -->|HTTP| Server\n Server -->|Middleware| Middleware\n Middleware -->|Route Match| Routes\n Routes -->|Query/Mutate| Drizzle\n Drizzle -->|SQL| DB\n Routes -->|Queue Jobs| PGBoss\n Routes -->|Upload/Download| S3\n Routes -->|Webhooks| JIRA\n Routes -->|Notifications| Teams\n Server -->|Static Assets| Static\n Static -->|Non-API Route| SPA_Fallback\n SPA -->|Initial Load| Static\n```\n\n## Key Components\n\n### Server Entry Point (`server.ts`)\n\nThe main entry point initializes the Hono application, configures middleware, registers all route handlers, and starts the HTTP server.\n\n```typescript\nconst app = new Hono();\n```\n\n**Middleware Stack:**\n\n| Middleware | Purpose |\n|------------|---------|\n| `logger()` | Request/response logging via Hono's built-in logger |\n| `cors()` | Enables Cross-Origin Resource Sharing for `/api/*` routes |\n\n**Route Registration Pattern:**\n\nAll API routes are mounted under the `/api` base path using `app.route()`:\n\n```typescript\napp.route(\"/api\", runs); // → /api/runs\napp.route(\"/api\", tests); // → /api/tests\napp.route(\"/api\", projects); // → /api/projects\n// ... etc\n```\n\nThis pattern allows each route module to define its own paths relative to the mount point, keeping route definitions clean and self-contained.\n\n**Static File Serving:**\n\nIn production, the server serves:\n1. Static assets from `./public` (JS bundles, CSS, images)\n2. `index.html` as a fallback for all non-API routes (SPA routing)\n\n```typescript\napp.use(\"/*\", serveStatic({ root: \"./public\" }));\napp.get(\"/*\", serveStatic({ root: \"./public\", path: \"index.html\" }));\n```\n\n### Configuration (`lib/config.ts`)\n\nEnvironment validation is handled by [envalid](https://github.com/af/envalid), which ensures required environment variables are present and properly typed at startup.\n\n```typescript\nexport const config = cleanEnv(process.env, {\n DATABASE_URL: url({ default: \"postgresql://forge:forge@localhost:5432/test_forge\" }),\n PORT: port({ default: 3000 }),\n HOST: str({ default: \"0.0.0.0\" }),\n // ... more options\n});\n```\n\n**Configuration Options:**\n\n| Variable | Type | Default | Description |\n|----------|------|---------|-------------|\n| `DATABASE_URL` | URL | `postgresql://forge:forge@localhost:5432/test_forge` | PostgreSQL connection string |\n| `PORT` | number | `3000` | HTTP server port |\n| `HOST` | string | `0.0.0.0` | Bind address |\n| `S3_ENDPOINT` | string | `\"\"` | S3-compatible storage endpoint |\n| `S3_BUCKET` | string | `test-artifacts` | Bucket for test artifacts (logs, screenshots) |\n| `S3_REGION` | string | `eu-central-1` | S3 region |\n| `JIRA_BASE_URL` | string | `\"\"` | JIRA instance for defect linking |\n| `TEAMS_WEBHOOK_URL` | string | `\"\"` | Microsoft Teams webhook for notifications |\n| `DEFAULT_RETENTION_DAYS` | number | `90` | Days to retain test run data |\n| `PGBOSS_SCHEMA` | string | `pgboss` | PostgreSQL schema for pg-boss job queue |\n\n### Data Layer\n\n**Drizzle ORM** provides type-safe database access. The schema is defined in `src/db/schema.ts` and migrations are managed via Drizzle Kit.\n\n```typescript\n// drizzle.config.ts\nexport default defineConfig({\n schema: \"./src/db/schema.ts\",\n out: \"./drizzle\",\n dialect: \"postgresql\",\n // ...\n});\n```\n\n**Database Scripts:**\n\n| Script | Purpose |\n|--------|---------|\n| `db:generate` | Generate migration files from schema changes |\n| `db:migrate` | Apply pending migrations |\n| `db:seed` | Populate database with initial test data |\n\n**pg-boss** handles asynchronous job processing (e.g., processing test results, sending notifications).\n\n## API Routes\n\nEach route module exports a Hono router with its own path definitions. The following routes are registered:\n\n| Route Module | Base Path | Description |\n|--------------|-----------|-------------|\n| `health` | `/api/health` | Health check endpoint |\n| `runs` | `/api/runs` | Test run CRUD and execution |\n| `tests` | `/api/tests` | Test case management |\n| `dashboard` | `/api/dashboard` | Dashboard summary data |\n| `flaky` | `/api/flaky` | Flaky test detection and tracking |\n| `mute` | `/api/mute` | Mute/ignore test rules |\n| `perf` | `/api/perf` | Performance metrics |\n| `errors` | `/api/errors` | Error aggregation and tracking |\n| `compare` | `/api/compare` | Compare test runs |\n| `gate` | `/api/gate` | Quality gates and thresholds |\n| `projects` | `/api/projects` | Project/workspace management |\n| `insights` | `/api/insights` | AI-powered insights |\n| `trends` | `/api/trends` | Historical trend analysis |\n| `defects` | `/api/defects` | Defect tracking and linking |\n\n## Development Workflow\n\n### Running the Server\n\n```bash\n# Development with hot reload\nnpm run dev\n\n# Production build\nnpm run build\n\n# Type checking\nnpm run typecheck\n```\n\n### Database Operations\n\n```bash\n# Generate migration after schema changes\nnpm run db:generate\n\n# Apply migrations\nnpm run db:migrate\n\n# Seed test data\nnpm run db:seed\n```\n\n### Testing\n\n```bash\n# Run smoke tests against running server\nnpm run smoke\n```\n\n## Integration Points\n\n### Frontend\n\nThe server serves the built frontend from `./public`. In development, the frontend typically runs on a separate dev server (Vite) that proxies API requests to this server.\n\n### External Services\n\n- **S3-Compatible Storage**: Test artifacts (logs, screenshots, traces) are uploaded to S3\n- **JIRA**: Defects can be linked to JIRA issues when `JIRA_BASE_URL` is configured\n- **Microsoft Teams**: Notifications sent via webhook when `TEAMS_WEBHOOK_URL` is set\n\n## Dependencies\n\n| Package | Purpose |\n|---------|---------|\n| `hono` | Web framework |\n| `@hono/node-server` | Node.js adapter for Hono |\n| `drizzle-orm` | SQL ORM |\n| `pg` | PostgreSQL driver |\n| `pg-boss` | Job queue |\n| `envalid` | Environment validation |\n| `zod` | Schema validation |\n\n## Extending the API\n\nTo add a new API endpoint:\n\n1. Create a new route file in `src/routes/`\n2. Define a Hono router with your endpoints\n3. Register it in `server.ts`:\n\n```typescript\nimport { myNewRoute } from \"./routes/myNewRoute.js\";\n\napp.route(\"/api\", myNewRoute); // → /api/my-new-route/*\n```\n\nThe route module should export a router that handles its own path definitions relative to the mount point.","api-static-assets-api":"# API Static Assets — api\n\n# API Static Assets Module\n\n## Overview\n\nThe **API Static Assets** module (`packages/api/public/`) contains the pre-bundled frontend assets for the **Test Forge** application — a test analytics and quality monitoring platform. This module serves as the static file server for the API package, delivering compiled JavaScript, CSS, and the HTML shell that renders the application's user interface.\n\nThe assets in this module are the output of a frontend build process (likely Vite-based, given the file naming patterns like `index-DjJN5Y-u.js`). The application is a Vue.js SPA that communicates with backend API routes to display test run data, analytics, defect tracking, and quality metrics.\n\n## Directory Structure\n\n```\npackages/api/public/\n├── index.html # Application entry point\n├── favicon.svg # Site favicon\n└── assets/\n ├── index-DjJN5Y-u.js # Core Vue app bundle (vendor/runtime)\n ├── index-BUpJ6Hkz.js # Additional core bundle (Chart.js, vue-router)\n ├── client-BtmuWjQn.js # API client (axios instance)\n ├── Compare-*.js/css # Build comparison page\n ├── Dashboard-*.js/css # Main dashboard page\n ├── Defects-*.js/css # Defect triage page\n ├── ErrorClusters-*.js # Error clustering page\n ├── Filters-*.js/CSS # Settings page\n ├── FlakyBoard-*.js/css # Flaky test tracking\n ├── MutedTests-*.js/css # Muted tests management\n └── Performance-*.css # Performance metrics page\n```\n\n## Application Architecture\n\nThe Test Forge frontend follows a single-page application (SPA) architecture built with Vue 3. The application loads a minimal HTML shell that bootstraps the Vue runtime, which then handles routing, data fetching, and UI rendering entirely on the client side.\n\n### Core Dependencies (from bundle analysis)\n\nThe compiled bundles reveal the following core technologies:\n\n- **Vue 3** — Reactive UI framework with Composition API\n- **Vue Router** — Client-side routing\n- **Chart.js** — Data visualization (donut charts, stacked bar charts)\n- **Axios** — HTTP client for API communication\n\n### Entry Point\n\nThe `index.html` file serves as the application shell:\n\n```html\n<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Test Forge</title>\n <link rel=\"icon\" type=\"image/svg+xml\" href=\"/favicon.svg\" />\n <script type=\"module\" crossorigin src=\"/assets/index-DjJN5Y-u.js\"><\/script>\n <link rel=\"stylesheet\" crossorigin href=\"/assets/index-eKnenK2P.css\">\n </head>\n <body>\n <div id=\"app\"></div>\n </body>\n</html>\n```\n\nThe `#app` div is the mounting point where Vue injects the application. The core bundle (`index-DjJN5Y-u.js`) initializes the Vue app, sets up routing, and renders the appropriate component based on the URL.\n\n## Page Components\n\nThe application consists of several distinct views, each with its own bundled JavaScript and CSS. Below is a summary of each page's purpose and key features.\n\n### Dashboard (`Dashboard-*.js`)\n\nThe main landing page providing an overview of test execution health.\n\n**Key Features:**\n- Summary statistics: Total runs, total tests, average pass rate, average duration\n- Trend visualization: Stacked bar chart showing daily pass/fail/skipped counts\n- Donut chart: Overall pass/fail/skipped distribution\n- Recent runs list with status indicators and pass rates\n- Component health table: Per-project breakdown with pass rate trends\n- Insights section: Stability score, always-failing tests, new failures\n\n**Data Dependencies:**\n- `getDashboard({ days })` — Fetches summary stats and trend data\n- `getInsights({ days })` — Fetches stability metrics\n\n### Compare (`Compare-*.js`)\n\nAllows side-by-side comparison of two test runs to identify regressions or improvements.\n\n**Key Features:**\n- Run picker UI: Input fields for base and head run IDs\n- Run cards: Displays metadata (ID, project, branch, commit, duration, pass rate)\n- Delta badges: Shows pass rate and duration changes between runs\n- Category tabs: New failures, fixed, still failing, new tests, removed\n- Diff table: Detailed test-by-test comparison with status pills and duration deltas\n\n**Data Dependencies:**\n- `getCompare(base, head)` — Fetches comparison data between two runs\n\n### Defects (`Defects-*.js`)\n\nDefect triage interface for categorizing test failure root causes.\n\n**Key Features:**\n- Category filter: Dropdown to filter by project\n- KPI cards: Counts for each defect category (product bug, automation bug, system issue, etc.)\n- Distribution chart: Doughnut chart showing category breakdown\n- Recent labeled tests table: Shows test title, project, area, category, labeled by, when\n\n**Categories:**\n- `product_bug` (red)\n- `automation_bug` (amber)\n- `system_issue` (purple)\n- `to_investigate` (gray)\n- `no_defign` (green)\n\n**Data Dependencies:**\n- `getDefects({ days, projectSlug })` — Fetches defect data\n- `getProjects()` — Fetches project list for filtering\n\n### Error Clusters (`ErrorClusters-*.js`)\n\nGroups similar test failures by error message to identify recurring issues.\n\n**Key Features:**\n- Filter bar: Project and time period filters\n- Summary stats: Total failures, unique error patterns, affected runs\n- Cluster list: Expandable cards showing error messages with occurrence counts\n- Meta information: Affected runs/tests, first/last seen dates, top areas/partners\n- Example links: Navigate to specific run examples\n\n**Data Dependencies:**\n- `getErrors({ days, projectSlug })` — Fetches clustered error data\n\n### Flaky Board (`FlakyBoard-*.js`)\n\nTracks tests that pass and fail intermittently.\n\n**Key Features:**\n- Filter bar: Period, partner, project filters\n- Summary stats: Flaky test count, worst rate, average rate\n- Flaky table: Test name, partner, area, stability, flaky rate, runs, failures\n- Sparkline visualization: Last 10 run statuses\n- Batch actions: Select and mute multiple flaky tests\n- Individual mute/unmute controls\n\n**Data Dependencies:**\n- `getFlaky({ days, partner, projectSlug })` — Fetches flaky test data\n- `muteTest({ testId, projectSlug })` — Mutes a test\n- `unmuteTest(testId)` — Unmutes a test\n\n### Muted Tests (`MutedTests-*.js`)\n\nManagement interface for tests that have been suppressed from flaky alerts.\n\n**Key Features:**\n- Table view: Test, project, reason, muted by, muted at, expires\n- Unmute action: Re-enable a previously muted test\n- Empty state when no tests are muted\n\n**Data Dependencies:**\n- `getMuted()` — Fetches muted test list\n- `unmuteTest(testId)` — Unmutes a test\n\n### Filters / Settings (`Filters-*.js`)\n\nUser preferences and default settings stored in localStorage.\n\n**Key Features:**\n- Default filters: Time range, page size, group-by option\n- Display preferences: Show flaky badge, expand retries, show muted in flaky board\n- Quality gate defaults: Min pass rate, max failures pre-fill values\n\n**Storage:**\nSettings are persisted in `localStorage` under the key `test-forge:settings`.\n\n### Performance (`Performance-*.css`)\n\n*(CSS only visible in source — likely a performance metrics view)*\n\nBased on the CSS selectors, this page displays:\n- Stat cards for performance metrics\n- Widget-based layout for charts\n- Project-level performance breakdown\n\n## API Client\n\nThe `client-BtmuWjQn.js` bundle contains the axios-based HTTP client used throughout the application. All page components import this client to make API calls:\n\n```javascript\nimport { a as apiClient } from \"./client-BtmuWjQn.js\";\n```\n\nThe client is configured to communicate with the backend routes defined in the API package (compare.ts, dashboard.ts, defects.ts, errors.ts, flaky.ts, etc.).\n\n## Integration with Backend\n\nThe static assets integrate with the backend through the API routes. Based on the call graph data, the following route handlers serve as integration points:\n\n| Route File | Endpoint Purpose |\n|------------|------------------|\n| `compare.ts` | `/api/compare` — Run comparison data |\n| `dashboard.ts` | `/api/dashboard` — Summary statistics |\n| `defects.ts` | `/api/defects` — Defect triage data |\n| `errors.ts` | `/api/errors` — Error clustering |\n| `flaky.ts` | `/api/flaky` — Flaky test detection |\n| `gate.ts` | `/api/gate` — Quality gate checks |\n| `insights.ts` | `/api/insights` — Stability metrics |\n| `mute.ts` | `/api/mute` — Mute/unmute operations |\n| `perf.ts` | `/api/perf` — Performance data |\n| `runs.ts` | `/api/runs` — Test run data |\n| `tests.ts` | `/api/tests` — Individual test data |\n| `trends.ts` | `/api/trends` — Historical trends |\n\n## Data Flow\n\nThe typical data flow for a page request follows this pattern:\n\n1. **User navigates** to a route (e.g., `/runs/123`)\n2. **Vue Router** matches the route and mounts the appropriate component\n3. **Component** calls the API client (e.g., `api.getRun(id)`)\n4. **Axios** sends request to the backend API\n5. **Backend** queries the database and returns JSON\n6. **Component** receives response, updates reactive state\n7. **Vue** re-renders the template with new data\n\nLoading states are handled with skeleton UI components, and errors display in dedicated error boxes with user-friendly messages.\n\n## Build and Deployment\n\nThese assets are generated by a frontend build tool (likely Vite, based on the chunk naming convention). The build process:\n\n1. Compiles `.vue` components and `.ts`/`.js` source files\n2. Chunks code by route (code splitting for lazy loading)\n3. Produces hashed filenames for cache busting\n4. Outputs to `packages/api/public/assets/`\n\nThe API server (Express-based, from `server.ts` in the call graph) serves these static files alongside the API routes, typically using middleware like `express.static()` or Vite's dev server in development.\n\n## Summary\n\nThe API Static Assets module is the compiled frontend of the Test Forge application. It provides a comprehensive UI for:\n\n- **Monitoring** test execution health via the Dashboard\n- **Comparing** test runs to identify regressions\n- **Triaging** defects by categorizing failure root causes\n- **Analyzing** error patterns across runs\n- **Tracking** flaky tests and managing alerts\n- **Configuring** user preferences and defaults\n\nThe frontend communicates with backend API routes to fetch data, while the static file server delivers the bundled assets. This separation allows the frontend to be developed independently and deployed as a static SPA.","api-static-assets":"# API Static Assets\n\n# API Static Assets Module\n\n## Overview\n\nThe **API Static Assets** module (`packages/api/public/`) serves the pre-bundled frontend for the **Test Forge** application — a test analytics and quality monitoring platform. This module delivers the compiled JavaScript, CSS, and HTML shell that renders the application's user interface.\n\n## Purpose\n\nThis module acts as the static file server for the API package. It serves:\n\n- **HTML Entry Point** — The single-page application shell that loads the frontend\n- **JavaScript Bundles** — Compiled application code (Vue.js SPA)\n- **Associated Assets** — Any CSS or static resources required by the frontend\n\nThe frontend communicates with backend API routes to display test run data, analytics, defect tracking, and quality metrics.\n\n## Key Components\n\n| Component | Description |\n|-----------|-------------|\n| `index.html` | SPA entry point that loads the JavaScript bundles |\n| `index-*.js` | Compiled JavaScript bundles (Vue.js application) |\n\n## Architecture\n\nThe static assets are the output of a frontend build process (Vite-based, based on the hashed filename pattern). The application runs as a Vue.js SPA served directly from this directory.\n\n```\n┌─────────────────────────────────────────┐\n│ API Static Assets │\n│ (packages/api/public/) │\n├─────────────────────────────────────────┤\n│ index.html → Loads Vue.js SPA │\n│ │\n│ index-*.js → Application bundles │\n│ (core + vendor chunks) │\n└─────────────────────────────────────────┘\n │\n ▼\n┌─────────────────────────────────────────┐\n│ Test Forge Frontend UI │\n│ (Test Analytics / Quality Metrics) │\n└─────────────────────────────────────────┘\n```\n\n## Integration\n\nThe static assets integrate with the broader Test Forge system by:\n\n1. **Serving the UI** — Delivering the compiled frontend to client browsers\n2. **API Communication** — The SPA makes requests to backend API routes for test data\n3. **Quality Monitoring** — Displaying analytics, defect tracking, and metrics dashboards\n\n---\n\n**Related Documentation**\n\n- [API Package](../api/overview.md) — Parent package documentation\n- [API Routes](../api/routes.md) — Backend endpoints the frontend consumes","background-jobs":"# Background Jobs\n\n# Background Jobs Module\n\nThe Background Jobs module provides asynchronous job processing for the Test Forge API using [pgBoss](https://github.com/timgit/pg-boss), a PostgreSQL-based job queue. It handles CPU-intensive and time-sensitive tasks outside the request-response cycle, ensuring the API remains responsive while processing test results, sending notifications, and maintaining data hygiene.\n\n## Architecture Overview\n\npgBoss stores jobs in PostgreSQL tables, providing reliable job queuing with built-in retry logic, scheduling, and batching. The module initializes a central boss instance that coordinates four distinct workers and two scheduled recurring jobs.\n\n```mermaid\nflowchart TB\n subgraph pgBoss[\"pgBoss Job Queue\"]\n direction TB\n process-run[\"process-run<br/>batch size: 3\"]\n cleanup[\"cleanup<br/>daily @ 3AM\"]\n refresh-views[\"refresh-views<br/>every 5 min\"]\n notify-teams[\"notify-teams<br/>batch size: 2\"]\n end\n \n subgraph Workers[\"Job Handlers\"]\n process-run --> db[(Database)]\n cleanup --> db\n refresh-views --> db\n notify-teams --> teams[(\"MS Teams<br/>Webhooks\")]\n end\n \n API[API Routes] -->|enqueue jobs| pgBoss\n```\n\n## Initialization\n\nThe module is initialized by calling `startBoss()` at application startup. This function:\n\n1. Creates a PgBoss instance with connection and retention settings\n2. Attaches an error listener for graceful error handling\n3. Registers all workers with their respective handlers\n4. Schedules recurring jobs using cron expressions\n\n### Configuration (boss.ts)\n\n```typescript\nnew PgBoss({\n connectionString: config.DATABASE_URL,\n schema: config.PGBOSS_SCHEMA,\n retryLimit: 3, // Retry failed jobs up to 3 times\n retryDelay: 30, // Wait 30 seconds between retries\n expireInMinutes: 15, // Jobs expire if not completed within 15 min\n archiveCompletedAfterSeconds: 86400, // Archive after 24 hours\n deleteAfterDays: 7, // Delete archived jobs after 7 days\n monitorStateIntervalMinutes: 5,\n})\n```\n\nThe boss instance is exposed via `getBoss()` for use by other modules that need to enqueue jobs.\n\n---\n\n## Job Handlers\n\n### process-run Handler\n\n**File:** `process-run.ts`\n\nThe primary handler for ingesting test results. It processes the `process-run` job type, which receives raw test results from CI pipelines and persists them to the database.\n\n**Payload structure:**\n```typescript\ninterface ProcessRunPayload {\n runId: number;\n projectSlug: string;\n results: Array<{\n title: string;\n suite?: string;\n file?: string;\n status: string;\n durationMs: number;\n errorMessage?: string;\n errorStack?: string;\n traceUrl?: string;\n videoUrl?: string;\n screenshotUrl?: string;\n retryCount?: number;\n partner?: string;\n area?: string;\n device?: string;\n ticket?: string;\n tags?: string[];\n metadata?: Record<string, unknown>;\n }>;\n}\n```\n\n**Processing logic:**\n\n1. **Upsert test definitions** — For each result, compute a SHA-256 hash of `projectSlug::title` to identify unique tests. If a test with that hash exists, update its `lastSeen` timestamp; otherwise, insert a new test record.\n\n2. **Insert test results** — Create a `testResults` record linking to the run and test, storing all result metadata including error details, trace/video/screenshot URLs, and custom metadata.\n\n3. **Update run totals** — After processing all results, update the parent `runs` record with final counts (total, passed, failed, skipped) and set status to `completed`.\n\n**Batching:** Configured with `batchSize: 3`, allowing up to 3 runs to be processed in a single worker invocation for improved throughput.\n\n---\n\n### cleanup Handler\n\n**File:** `cleanup.ts`\n\nA maintenance job that runs daily at 3 AM to manage data retention and database health.\n\n**Operations performed:**\n\n1. **Delete old runs** — Removes runs older than `config.DEFAULT_RETENTION_DAYS`. Due to foreign key constraints, this cascades to delete associated `test_results`.\n\n2. **Delete orphaned tests** — Removes test definitions that no longer have any associated results (tests that were created but never had results recorded).\n\n3. **VACUUM ANALYZE** — Reclaims disk space and updates statistics on the `runs`, `tests`, `test_results`, and `perf_metrics` tables for query optimization.\n\nThis handler runs as a scheduled job (`0 3 * * *`) rather than being triggered by enqueueing.\n\n---\n\n### refresh-views Handler\n\n**File:** `refresh-views.ts`\n\nMaintains materialized views that power analytics and reporting features.\n\n**Views managed:**\n\n1. **mv_project_stats** — Aggregates 30-day statistics per project:\n - Total run count\n - Average pass rate\n - Average duration\n - Last run timestamp\n\n2. **mv_daily_trend** — Provides 90-day daily trends per project:\n - Daily totals (passed, failed, skipped)\n - Run count per day\n\nBoth views use `REFRESH MATERIALIZED VIEW CONCURRENTLY` to allow reads during refresh without blocking.\n\nThis handler runs every 5 minutes (`*/5 * * * *`) to keep analytics data reasonably current.\n\n---\n\n### notify-teams Handler\n\n**File:** `notify-teams.ts`\n\nSends Microsoft Teams notifications with test run summaries to configured webhook endpoints.\n\n**Payload structure:**\n```typescript\ninterface NotifyTeamsPayload {\n runId: number;\n projectName: string;\n partner?: string;\n branch?: string;\n total: number;\n passed: number;\n failed: number;\n pipelineId?: number;\n topFailures: Array<{ title: string; error?: string }>;\n}\n```\n\n**Behavior:**\n\n- If `config.TEAMS_WEBHOOK_URL` is not set, the handler exits early without sending.\n- Sends an Adaptive Card formatted as a MessageCard with:\n - Theme color: red (`f38ba8`) if failures exist, green (`a6e3a1`) if all passed\n - Summary stats: total, passed, failed, pass rate\n - Top 5 failures with truncated error messages (first 100 chars)\n- Throws on HTTP errors, triggering pgBoss retry logic\n\n**Batching:** Configured with `batchSize: 2`, allowing up to 2 notifications per worker invocation.\n\n---\n\n## Enqueueing Jobs\n\nOther modules interact with the job queue via `getBoss()`. The typical pattern:\n\n```typescript\nimport { getBoss } from \"./jobs/boss.js\";\n\nconst boss = getBoss();\n\n// Enqueue a process-run job\nawait boss.send(\"process-run\", {\n runId: 123,\n projectSlug: \"my-project\",\n results: [\n { title: \"login test\", status: \"passed\", durationMs: 1500 },\n { title: \"checkout test\", status: \"failed\", durationMs: 3000, errorMessage: \"Timeout\" },\n ],\n});\n\n// Enqueue a notification job\nawait boss.send(\"notify-teams\", {\n runId: 123,\n projectName: \"My Project\",\n branch: \"main\",\n total: 10,\n passed: 8,\n failed: 2,\n topFailures: [{ title: \"checkout test\", error: \"Timeout\" }],\n});\n```\n\n## Error Handling\n\n- **pgBoss error listener** — The boss instance has an `on(\"error\", ...)` handler that logs errors to console. In production, this should integrate with your monitoring/alerting system.\n- **Job retry** — Failed jobs (throwing errors) are automatically retried up to `retryLimit` (3) times with `retryDelay` (30 seconds) between attempts.\n- **Job expiration** — Jobs that expire without completion are handled according to pgBoss's expiration policy (archived after 24h, deleted after 7 days).\n\n## Database Schema Requirements\n\npgBoss creates its own tables in a dedicated schema (configurable via `config.PGBOSS_SCHEMA`, defaults to `pgboss`). Ensure:\n\n1. The database user has permission to create tables in the target schema\n2. The schema is included in the `search_path` if queries need to reference pgBoss tables directly","cli-core":"# CLI Core\n\n# Test Forge CLI (`@test-forge/cli`)\n\nThe Test Forge CLI (`forge`) is a command-line tool that ingests test results from various testing frameworks and pushes them to the Test Forge reporting portal. It supports Playwright (E2E/API/integration tests) and k6 (performance tests), providing a unified interface for reporting test runs.\n\n## Overview\n\nThe CLI serves as the bridge between CI pipelines and the Test Forge API:\n\n```\n┌─────────────┐ ┌─────────────┐ ┌─────────────┐\n│ CI Pipeline│ ──▶ │ forge │ ──▶ │ Test Forge │\n│ (Playwright │ │ (CLI) │ │ API │\n│ or k6) │ └─────────────┘ └─────────────┘\n└─────────────┘\n```\n\n## Commands\n\n### `push` — Push Test Results\n\nThe primary command for ingesting test results into the reporting portal.\n\n```bash\nforge push \\\n --api-url https://forge.example.com \\\n --results ./playwright-report.json \\\n --project fe-e2e-unified \\\n --type e2e \\\n --branch main \\\n --commit abc123 \\\n --pipeline 12345 \\\n --environment staging\n```\n\n**Required Options:**\n\n| Option | Description |\n|--------|-------------|\n| `--api-url <url>` | Test Forge API base URL |\n| `--results <path>` | Path to test results JSON file |\n| `--project <slug>` | Project slug (e.g., `fe-e2e-unified`, `crypto-desktop`) |\n\n**Optional Options:**\n\n| Option | Default | Description |\n|--------|---------|-------------|\n| `--type` | `e2e` | Test type: `e2e`, `api`, `integration`, `performance`, `seo` |\n| `--partner <name>` | — | Partner name (extracted from project slug if omitted) |\n| `--pipeline <id>` | — | CI pipeline ID |\n| `--branch <name>` | — | Git branch name |\n| `--commit <sha>` | — | Git commit SHA |\n| `--environment` | — | Environment: `staging`, `production`, `preprod` |\n| `--s3-prefix <prefix>` | — | S3 prefix for artifact URLs |\n| `--format` | auto | Result format: `playwright` or `k6` (auto-detected if omitted) |\n\n**Exit Codes:**\n- `0` — Results pushed successfully\n- `1` — Error (parse failure, API error, or unsupported format)\n\n---\n\n### `health` — Check API Health\n\nVerifies connectivity to the Test Forge API.\n\n```bash\nforge health --api-url https://forge.example.com\n```\n\n**Output:**\n```json\n[forge] API health: {\n \"status\": \"healthy\",\n \"version\": \"1.2.3\",\n \"timestamp\": \"2024-01-15T10:30:00Z\"\n}\n```\n\n---\n\n### `gate` — Evaluate Quality Gates\n\nEvaluates a test run against configurable quality gate rules to determine if the run passes or fails.\n\n```bash\nforge gate \\\n --api-url https://forge.example.com \\\n --run-id 12345 \\\n --min-pass-rate 95 \\\n --max-failures 10 \\\n --max-duration 300000\n```\n\n**Options:**\n\n| Option | Type | Description |\n|--------|------|-------------|\n| `--api-url <url>` | required | Test Forge API base URL |\n| `--run-id <id>` | required | Run ID to evaluate |\n| `--min-pass-rate <pct>` | optional | Minimum pass rate (0-100) |\n| `--max-failures <n>` | optional | Maximum failures allowed |\n| `--max-duration <ms>` | optional | Maximum total duration in ms |\n| `--max-flaky <n>` | optional | Maximum flaky tests allowed |\n| `--max-p95 <ms>` | optional | Maximum p95 latency in ms (performance) |\n| `--require-all-thresholds` | flag | Require all perf thresholds to pass |\n| `--json` | flag | Output machine-readable JSON |\n\n**Output Example:**\n```\n[forge gate] Run #12345 — fe-e2e-unified\n ✓ minPassRate: 97.2% (threshold: 95%)\n ✓ maxFailures: 3 (threshold: 10)\n ✓ maxDurationMs: 145000 (threshold: 300000)\n\n ✓ Quality gate passed\n```\n\n**Exit Codes:**\n- `0` — All gate rules passed\n- `1` — One or more rules failed\n\n---\n\n## Architecture\n\n### Module Structure\n\n```\npackages/cli/src/\n├── index.ts # CLI entry point (Commander.js)\n├── parse.ts # Format detection and parsing\n├── push.ts # API communication\n├── extract.ts # Metadata extraction utilities\n└── adapters/ # Framework-specific parsers\n ├── playwright.ts\n └── k6.ts\n```\n\n### Execution Flow\n\n```mermaid\nflowchart TD\n A[User runs<br/>forge push] --> B[parseResults]\n B --> C{Auto-detect<br/>format?}\n C -->|playwright| D[parsePlaywrightReport]\n C -->|k6| E[parseK6Summary]\n C -->|unknown| F[Error: Could not<br/>detect format]\n \n D --> G[Build PushRunPayload]\n E --> G\n \n G --> H[pushResults]\n H --> I[POST to<br/>/api/runs]\n I --> J{Success?}\n J -->|Yes| K[Print run ID]\n J -->|No| L[Error: API error]\n \n F --> M[Exit 1]\n L --> M\n K --> N[Exit 0]\n```\n\n---\n\n## How It Works\n\n### Format Detection & Parsing\n\nThe `parseResults()` function in `parse.ts` handles format detection:\n\n1. **Explicit format**: If `--format` is provided, skip detection\n2. **Auto-detect**: Parse the JSON and inspect top-level keys:\n - Playwright: has `config` and `suites` keys\n - k6: has `metrics` and `root_group` keys\n3. **Error**: Return `{ format: \"unknown\", error: ... }` if detection fails\n\nThe actual parsing is delegated to adapter modules:\n- `parsePlaywrightReport()` — Converts Playwright JSON to `UniversalResult[]`\n- `parseK6Summary()` — Converts k6 summary to `K6ParseResult`\n\n### Payload Construction\n\nFor Playwright results, the CLI computes aggregate statistics:\n\n```typescript\nconst passed = r.filter((t) => t.status === \"passed\").length;\nconst failed = r.filter(\n (t) => t.status === \"failed\" || t.status === \"timedOut\",\n).length;\nconst skipped = r.filter((t) => t.status === \"skipped\").length;\nconst totalDuration = r.reduce((sum, t) => sum + t.durationMs, 0);\n```\n\nFor k6 results, performance metrics are extracted and included as `perfMetrics`.\n\n### Metadata Extraction\n\nThe `extract.ts` module provides utilities for extracting metadata from test data:\n\n| Function | Input | Output |\n|----------|-------|--------|\n| `extractTicket()` | `\"[CCP-66] Login\"` | `\"CCP-66\"` |\n| `extractTags()` | `\"@fast_smoke @regression\"` | `[\"fast_smoke\", \"regression\"]` |\n| `extractArea()` | `\"tests/desktop/auth/login.test.ts\"` | `\"auth\"` |\n| `extractDevice()` | file path or project name | `\"desktop\"` or `\"mobile\"` |\n| `extractPartner()` | `\"crypto-desktop\"` | `\"crypto\"` |\n| `extractMeta()` | Combines all above | `ExtractedMeta` object |\n\nThese utilities are used by the Playwright adapter to enrich test results with metadata extracted from test titles and file paths.\n\n---\n\n## Key Interfaces\n\n### `PushRunPayload`\n\nThe data structure sent to the Test Forge API:\n\n```typescript\ninterface PushRunPayload {\n projectSlug: string; // e.g., \"fe-e2e-unified\"\n projectType: string; // \"e2e\" | \"api\" | \"integration\" | \"performance\" | \"seo\"\n pipelineId?: number; // CI pipeline ID\n branch?: string; // Git branch\n commitSha?: string; // Git commit\n partner?: string; // Partner name\n environment?: string; // \"staging\" | \"production\" | \"preprod\"\n total: number; // Total test count\n passed: number; // Passed tests\n failed: number; // Failed tests\n skipped: number; // Skipped tests\n durationMs: number; // Total duration\n s3Prefix?: string; // S3 prefix for artifacts\n results: unknown[]; // Individual test results\n perfMetrics?: unknown[]; // k6 performance metrics\n}\n```\n\n### `ParsedResults`\n\nUnion type returned by `parseResults()`:\n\n```typescript\ntype ParsedResults =\n | { format: \"playwright\"; results: UniversalResult[] }\n | { format: \"k6\"; data: K6ParseResult }\n | { format: \"unknown\"; error: string };\n```\n\n---\n\n## Integration\n\n### CI Pipeline Example (GitHub Actions)\n\n```yaml\n- name: Run Playwright tests\n run: npx playwright test --reporter=json > playwright-report.json\n\n- name: Push results to Test Forge\n run: |\n npx forge push \\\n --api-url ${{ secrets.FORGE_URL }} \\\n --results playwright-report.json \\\n --project ${{ github.repository }} \\\n --type e2e \\\n --branch ${{ github.ref_name }} \\\n --commit ${{ github.sha }} \\\n --pipeline ${{ github.run_id }} \\\n --environment staging\n```\n\n### Quality Gate in CI\n\n```yaml\n- name: Evaluate quality gate\n run: |\n npx forge gate \\\n --api-url ${{ secrets.FORGE_URL }} \\\n --run-id ${{ steps.forge.outputs.run-id }} \\\n --min-pass-rate 95 \\\n --max-failures 5\n```\n\n---\n\n## Error Handling\n\nAll errors are logged with the `[forge]` prefix and cause the process to exit with code `1`:\n\n```\n[forge] ✗ Could not detect format. Use --format flag.\n[forge] ✗ API error 500: Internal server error\n[forge] ✗ Failed to parse file: Unexpected token } in JSON\n```\n\n---\n\n## Development\n\n### Building\n\n```bash\ncd packages/cli\nnpm run build\n```\n\nOutput is written to `packages/cli/dist/`.\n\n### Adding a New Test Format\n\nTo support a new testing framework:\n\n1. Create a new adapter in `src/adapters/<framework>.ts`\n2. Export a parse function that returns a standardized result type\n3. Update `parseResults()` in `parse.ts` to detect the new format\n4. Update the payload construction in `index.ts` to handle the new format","database-layer":"# Database Layer\n\n# Database Layer Module\n\n## Overview\n\nThe Database Layer (`packages/api/src/db/`) provides the persistence infrastructure for the Test Forge platform. It uses **Drizzle ORM** with **PostgreSQL** to store test execution data, performance metrics, and configuration.\n\nThis module handles:\n- Database connection pooling and lifecycle management\n- Schema definitions for all entities (projects, runs, tests, results, metrics)\n- Database migrations for schema evolution\n\n---\n\n## Architecture\n\n```mermaid\nerDiagram\n projects ||--o{ runs : \"has many\"\n projects ||--o{ tests : \"has many\"\n runs ||--o{ testResults : \"contains\"\n runs ||--o{ perfMetrics : \"contains\"\n tests ||--o{ testResults : \"recorded in\"\n tests ||--o{ mutedTests : \"can be muted\"\n testResults ||--o{ defectLabels : \"categorized by\"\n\n projects {\n serial id PK\n varchar slug UK\n text name\n varchar type\n timestamp createdAt\n }\n\n runs {\n serial id PK\n integer projectId FK\n integer pipelineId\n varchar branch\n varchar commitSha\n varchar status\n integer total\n integer passed\n integer failed\n }\n\n tests {\n serial id PK\n varchar titleHash UK\n integer projectId FK\n text title\n varchar area\n varchar ticket\n }\n\n testResults {\n serial id PK\n integer runId FK\n integer testId FK\n varchar status\n integer durationMs\n }\n\n perfMetrics {\n serial id PK\n integer runId FK\n varchar name\n text endpoint\n real p90\n real p95\n real p99\n }\n```\n\n---\n\n## Key Components\n\n### 1. Connection Management (`connection.ts`)\n\nEstablishes a PostgreSQL connection pool using `pg.Pool` and wraps it with Drizzle ORM.\n\n```typescript\nconst pool = new pg.Pool({\n connectionString: process.env.DATABASE_URL ?? \"postgresql://forge:forge@localhost:5432/test_forge\",\n max: 10,\n});\n\nexport const db = drizzle(pool, { schema });\n```\n\n**Key points:**\n- Exports both the Drizzle instance (`db`) and the raw pool (`pool`) for different use cases\n- Default connection targets `localhost:5432/test_forge` for local development\n- Pool size is capped at 10 connections to prevent resource exhaustion\n\n### 2. Schema Definitions (`schema.ts`)\n\nDefines eight tables using Drizzle's type-safe schema builder. Each table includes:\n- Column definitions with types and constraints\n- Indexes for query performance\n- Foreign key relationships\n\n### 3. Migration System (`migrate.ts`)\n\nOne-time script that applies schema changes to a fresh database:\n\n```typescript\nawait migrate(db, { migrationsFolder: \"./drizzle\" });\n```\n\nRun via: `npx tsx src/db/migrate.ts`\n\n---\n\n## Schema Reference\n\n### `projects`\n\nTest repositories (e.g., `fe-e2e-unified`, `webapi-tests`, `k6-perf`).\n\n| Column | Type | Description |\n|--------|------|-------------|\n| `id` | serial | Primary key |\n| `slug` | varchar(100) | Unique identifier (e.g., `fe-e2e-unified`) |\n| `name` | text | Display name |\n| `type` | varchar(30) | Test type: `e2e`, `api`, `integration`, `performance`, `seo` |\n| `repoUrl` | text | Git repository URL |\n\n### `runs`\n\nA single pipeline execution. One run may contain multiple shards merged together.\n\n| Column | Type | Description |\n|--------|------|-------------|\n| `id` | serial | Primary key |\n| `projectId` | integer | FK → `projects.id` |\n| `pipelineId` | integer | CI/CD pipeline identifier |\n| `branch` | varchar(255) | Git branch name |\n| `commitSha` | varchar(40) | Git commit SHA |\n| `partner` | varchar(50) | Partner identifier (`crypto`, `ua14`, `allwin`, etc.) |\n| `environment` | varchar(50) | `staging`, `production`, `preprod` |\n| `triggerType` | varchar(30) | `push`, `schedule`, `manual`, `merge_request` |\n| `total` | integer | Total test count |\n| `passed` | integer | Passed count |\n| `failed` | integer | Failed count |\n| `skipped` | integer | Skipped count |\n| `flaky` | integer | Flaky test count |\n| `durationMs` | integer | Total execution time |\n| `status` | varchar(20) | `completed`, `running`, `failed` |\n\n**Indexes:** `projectId`, `partner`, `createdAt`, `pipelineId`\n\n### `tests`\n\nDeduplicated test definitions. Uniqueness is determined by a SHA-256 hash of `project_slug + title`.\n\n| Column | Type | Description |\n|--------|------|-------------|\n| `id` | serial | Primary key |\n| `titleHash` | varchar(64) | SHA-256 hash for deduplication |\n| `projectId` | integer | FK → `projects.id` |\n| `title` | text | Full test title |\n| `suite` | text | Parent describe block |\n| `file` | text | Relative file path |\n| `partner` | varchar(50) | Partner scope |\n| `area` | varchar(100) | Test area: `auth`, `casino`, `sport`, `payments`, etc. |\n| `device` | varchar(20) | `desktop` or `mobile` |\n| `ticket` | varchar(50) | Associated ticket (e.g., `CCP-66`) |\n| `tags` | text[] | Array of tags (`@fast_smoke`, `@regression`) |\n\n**Indexes:** `titleHash` (unique), `projectId`, `partner`, `ticket`\n\n### `testResults`\n\nOne row per test per run — the core fact table for test execution data.\n\n| Column | Type | Description |\n|--------|------|-------------|\n| `id` | serial | Primary key |\n| `runId` | integer | FK → `runs.id` (cascade delete) |\n| `testId` | integer | FK → `tests.id` |\n| `status` | varchar(20) | `passed`, `failed`, `skipped`, `timedOut`, `interrupted` |\n| `durationMs` | integer | Execution time |\n| `errorMessage` | text | Failure message |\n| `errorStack` | text | Stack trace |\n| `traceUrl` | text | Jaeger/Trace link |\n| `videoUrl` | text | S3 video artifact |\n| `screenshotUrl` | text | S3 screenshot artifact |\n| `retryCount` | integer | Number of retries |\n\n**Indexes:** `runId`, `testId`, `status`, `createdAt`\n\n### `perfMetrics`\n\nk6-style performance metrics. One row per metric per endpoint per run.\n\n| Column | Type | Description |\n|--------|------|-------------|\n| `id` | serial | Primary key |\n| `runId` | integer | FK → `runs.id` (cascade delete) |\n| `name` | varchar(200) | Metric name (`http_req_duration`, `http_reqs`, `vus`) |\n| `endpoint` | text | API endpoint (e.g., `/api/login`) |\n| `method` | varchar(10) | HTTP method |\n| `count` | integer | Sample count |\n| `avg`, `min`, `max` | real | Aggregates |\n| `med` | real | Median (p50) |\n| `p90`, `p95`, `p99` | real | Percentiles |\n| `thresholdPassed` | integer | `1` = passed, `0` = failed, `null` = no threshold |\n| `unit` | varchar(20) | Unit type (`ms`, `count`, `bytes`) |\n\n**Indexes:** `runId`, `name`\n\n### `retentionConfig`\n\nPer-project data retention settings.\n\n| Column | Type | Description |\n|--------|------|-------------|\n| `id` | serial | Primary key |\n| `projectSlug` | varchar(100) | FK → `projects.slug` (unique) |\n| `keepDays` | integer | Retention period (default: 90) |\n\n### `mutedTests`\n\nSuppressed tests — excluded from flaky alert calculations.\n\n| Column | Type | Description |\n|--------|------|-------------|\n| `id` | serial | Primary key |\n| `testId` | integer | FK → `tests.id` (cascade delete) |\n| `projectSlug` | varchar(100) | Project scope |\n| `reason` | text | Mute justification |\n| `mutedBy` | varchar(100) | User who muted |\n| `expiresAt` | timestamp | Optional expiration |\n| `active` | integer | `1` = active, `0` = inactive |\n\n**Unique constraint:** `(testId, projectSlug)`\n\n### `defectLabels`\n\nTriage categorization for test failures (Allure-style).\n\n| Column | Type | Description |\n|--------|------|-------------|\n| `id` | serial | Primary key |\n| `testResultId` | integer | FK → `testResults.id` (cascade delete) |\n| `testId` | integer | FK → `tests.id` (cascade delete) |\n| `runId` | integer | FK → `runs.id` (cascade delete) |\n| `category` | varchar(50) | `product_bug`, `automation_bug`, `system_issue`, `to_investigate`, `no_defect` |\n| `comment` | text | Additional notes |\n| `labeledBy` | varchar(100) | User who labeled |\n\n**Unique constraint:** `(testResultId)`\n\n---\n\n## Usage\n\n### Importing the Database Instance\n\n```typescript\nimport { db } from \"./db/connection.js\";\nimport { runs, testResults } from \"./db/schema.js\";\n\n// Query runs for a project\nconst recentRuns = await db\n .select()\n .from(runs)\n .where(eq(runs.projectId, projectId))\n .orderBy(desc(runs.createdAt))\n .limit(10);\n```\n\n### Running Migrations\n\n```bash\n# Ensure DATABASE_URL is set\nexport DATABASE_URL=\"postgresql://user:pass@host:5432/test_forge\"\n\n# Run migrations\nnpx tsx packages/api/src/db/migrate.ts\n```\n\n---\n\n## Configuration\n\n| Environment Variable | Description | Default |\n|---------------------|-------------|---------|\n| `DATABASE_URL` | PostgreSQL connection string | `postgresql://forge:forge@localhost:5432/test_forge` |\n\nThe connection pool is configured with `max: 10` connections. Adjust this in `connection.ts` if your workload requires higher concurrency.\n\n---\n\n## Integration Notes\n\nThis module is a foundational dependency — other modules import `db` and schema exports to perform queries. The call graph shows no incoming calls because this module is imported by higher-level handlers and services (not shown in this scope).\n\nWhen adding new tables:\n1. Define the table in `schema.ts`\n2. Create a migration in `./drizzle` (see [Drizzle Kit](https://orm.drizzle.team/kit-docs/overview))\n3. Export the table from `schema.ts`\n4. Import in consuming modules as needed","design-system":"# Design System\n\n# Design System — Test Forge\n\nThis document defines the visual language, component patterns, and styling conventions for the Test Forge Smart Home/IoT Dashboard project. It serves as the single source of truth for UI implementation across the application.\n\n---\n\n## Overview\n\nThe Test Forge Design System is a **specification-driven approach** to UI development. Rather than embedding styles directly in components, this system defines reusable design tokens (colors, spacing, shadows) and component patterns that enforce visual consistency throughout the application.\n\n**Project Context:** Smart Home/IoT Dashboard \n**Visual Mood:** Dashboard, data, analytics, code, technical, precise \n**Primary Style:** Dark Mode (OLED) — optimized for night-mode usage, coding platforms, and OLED displays\n\n---\n\n## Design Tokens\n\nDesign tokens are the atomic values that all components reference. Using tokens ensures consistency and makes global theme changes possible by updating values in one place.\n\n### Color Palette\n\n| Role | Hex | CSS Variable | Usage |\n|------|-----|--------------|-------|\n| Primary | `#1E40AF` | `--color-primary` | Main actions, headers, brand elements |\n| Secondary | `#3B82F6` | `--color-secondary` | Supporting elements, links |\n| CTA/Accent | `#F59E0B` | `--color-cta` | Call-to-action buttons, highlights |\n| Background | `#F8FAFC` | `--color-background` | Page backgrounds (light mode base) |\n| Text | `#1E3A8A` | `--color-text` | Primary text content |\n\n**Implementation Note:** The color palette uses blue as the primary data color with amber accents for emphasis—a combination that works well for technical dashboards where data clarity is paramount.\n\n### Typography\n\n| Element | Font | Weights |\n|---------|------|---------|\n| Headings | Fira Code | 400, 500, 600, 700 |\n| Body | Fira Sans | 300, 400, 500, 600, 700 |\n\n**CSS Import:**\n```css\n@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500;600;700&family=Fira+Sans:wght@300;400;500;600;700&display=swap');\n```\n\n**Usage Guidelines:**\n- Use Fira Code for headings to reinforce the technical/dashboard aesthetic\n- Use Fira Sans for body text to ensure readability at various sizes\n- The monospace heading font differentiates this dashboard from conventional consumer apps\n\n### Spacing Scale\n\n| Token | Value | Use Case |\n|-------|-------|----------|\n| `--space-xs` | 4px (0.25rem) | Tight gaps, icon margins |\n| `--space-sm` | 8px (0.5rem) | Inline spacing, small gaps |\n| `--space-md` | 16px (1rem) | Standard padding, component gaps |\n| `--space-lg` | 24px (1.5rem) | Section padding |\n| `--space-xl` | 32px (2rem) | Large gaps between sections |\n| `--space-2xl` | 48px (3rem) | Section margins |\n| `--space-3xl` | 64px (4rem) | Hero padding, major sections |\n\n### Shadow Depths\n\n| Token | Value | Application |\n|-------|-------|-------------|\n| `--shadow-sm` | `0 1px 2px rgba(0,0,0,0.05)` | Subtle elevation, inline elements |\n| `--shadow-md` | `0 4px 6px rgba(0,0,0,0.1)` | Cards, buttons, standard components |\n| `--shadow-lg` | `0 10px 15px rgba(0,0,0,0.1)` | Modals, dropdowns, overlays |\n| `--shadow-xl` | `0 20px 25px rgba(0,0,0,0.15)` | Hero images, featured content |\n\n---\n\n## Component Specifications\n\n### Buttons\n\n**Primary Button** — Used for main CTAs and primary actions:\n```css\n.btn-primary {\n background: #F59E0B;\n color: white;\n padding: 12px 24px;\n border-radius: 8px;\n font-weight: 600;\n transition: all 200ms ease;\n cursor: pointer;\n}\n\n.btn-primary:hover {\n opacity: 0.9;\n transform: translateY(-1px);\n}\n```\n\n**Secondary Button** — Used for secondary actions, navigation:\n```css\n.btn-secondary {\n background: transparent;\n color: #1E40AF;\n border: 2px solid #1E40AF;\n padding: 12px 24px;\n border-radius: 8px;\n font-weight: 600;\n transition: all 200ms ease;\n cursor: pointer;\n}\n```\n\n**Key Patterns:**\n- Both buttons use 200ms transitions for smooth hover effects\n- Primary button lifts slightly on hover (`translateY(-1px)`)\n- Secondary button uses a transparent background with border\n- Both require `cursor: pointer` for interactivity feedback\n\n### Cards\n\n```css\n.card {\n background: #F8FAFC;\n border-radius: 12px;\n padding: 24px;\n box-shadow: var(--shadow-md);\n transition: all 200ms ease;\n cursor: pointer;\n}\n\n.card:hover {\n box-shadow: var(--shadow-lg);\n transform: translateY(-2px);\n}\n```\n\n**Usage:** Cards are the primary content container for dashboard widgets, device listings, and data summaries. The hover elevation provides visual feedback for interactive cards.\n\n### Inputs\n\n```css\n.input {\n padding: 12px 16px;\n border: 1px solid #E2E8F0;\n border-radius: 8px;\n font-size: 16px;\n transition: border-color 200ms ease;\n}\n\n.input:focus {\n border-color: #1E40AF;\n outline: none;\n box-shadow: 0 0 0 3px #1E40AF20;\n}\n```\n\n**Key Patterns:**\n- Focus state uses a subtle blue ring (`#1E40AF20` = 12.5% opacity)\n- Border color change provides clear focus indication\n- 16px font size prevents mobile zoom on iOS\n\n### Modals\n\n```css\n.modal-overlay {\n background: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n}\n\n.modal {\n background: white;\n border-radius: 16px;\n padding: 32px;\n box-shadow: var(--shadow-xl);\n max-width: 500px;\n width: 90%;\n}\n```\n\n**Usage:** Confirmation dialogs, forms, and detailed views that require focused attention. The backdrop blur separates the modal from underlying content.\n\n---\n\n## Style Guidelines\n\n### Dark Mode (OLED)\n\nThe default visual style is **Dark Mode optimized for OLED displays**:\n\n- **Background:** Deep black (`#000000` or near-black) for power efficiency on OLED screens\n- **Text:** High contrast white/light gray for readability\n- **Accents:** Minimal glow effects using `text-shadow: 0 0 10px` for emphasis\n- **Transitions:** Dark-to-light gradients for depth perception\n- **Focus:** High visibility focus states for accessibility\n\n**Keywords:** Dark theme, low light, high contrast, deep black, midnight blue, eye-friendly, OLED, night mode, power efficient\n\n**Best For:** Night-mode apps, coding platforms, entertainment, eye-strain prevention, OLED devices, low-light environments\n\n### Page Pattern: Enterprise Gateway\n\nWhen building landing or marketing pages, use the **Enterprise Gateway** pattern:\n\n1. **Hero Section** — Video or mission statement\n2. **Solutions by Industry** — Tab-switched content\n3. **Solutions by Role** — Path selection (\"I am a...\")\n4. **Client Logos** — Trust signals\n5. **Contact Sales** — Final CTA section\n\n**CTA Strategy:**\n- Primary: \"Contact Sales\"\n- Secondary: \"Login\"\n\n---\n\n## Override System\n\nThe design system supports **page-specific overrides**. When building a specific page:\n\n1. Check if a page-specific file exists at `design-system/pages/[page-name].md`\n2. If it exists, its rules **override** this Master file for that page\n3. If not, use the Master file rules strictly\n\nThis allows flexibility for unique page requirements while maintaining consistency as the default.\n\n---\n\n## Anti-Patterns\n\nThe following patterns are explicitly forbidden:\n\n| Anti-Pattern | Reason |\n|--------------|--------|\n| ❌ Emojis as icons | Use SVG icons (Heroicons, Lucide, Simple Icons) |\n| ❌ Missing `cursor:pointer` | All clickable elements must indicate interactivity |\n| ❌ Layout-shifting hovers | Avoid scale transforms that shift surrounding content |\n| ❌ Low contrast text | Maintain 4.5:1 minimum contrast ratio |\n| ❌ Instant state changes | Always use transitions (150-300ms) |\n| ❌ Invisible focus states | Focus states must be visible for keyboard accessibility |\n\n---\n\n## Pre-Delivery Checklist\n\nBefore delivering any UI code, verify all of the following:\n\n- [ ] No emojis used as icons (use SVG instead)\n- [ ] All icons from consistent icon set (Heroicons/Lucide)\n- [ ] `cursor: pointer` on all clickable elements\n- [ ] Hover states with smooth transitions (150-300ms)\n- [ ] Light mode: text contrast 4.5:1 minimum\n- [ ] Focus states visible for keyboard navigation\n- [ ] `prefers-reduced-motion` respected\n- [ ] Responsive: 375px, 768px, 1024px, 1440px breakpoints\n- [ ] No content hidden behind fixed navbars\n- [ ] No horizontal scroll on mobile\n\n---\n\n## Integration\n\nThis design system is a **specification document** that should be referenced by:\n\n1. **Component libraries** — Implementing the token-based styles\n2. **Page templates** — Applying the Enterprise Gateway pattern\n3. **Code generators** — Ensuring output matches design specs\n4. **Code reviewers** — Validating implementation against the system\n\nThe system does not execute directly—it defines the rules that other code should follow. When implementing new components or pages, use this document as the authoritative reference for styling decisions.","documentation":"# Documentation\n\n# Documentation Module\n\nThis module contains the project's documentation files that provide context, instructions, and guidance for both human developers and AI assistants working on Test Forge.\n\n## Overview\n\nThe Documentation module serves as the **single source of truth** for project context, AI agent instructions, and development workflows. Unlike traditional code documentation, these files are actively used by the GitNexus code intelligence system and OpenSpec framework to guide development decisions.\n\n## File Inventory\n\n| File | Purpose | Audience |\n|------|---------|----------|\n| `README.md` | Project landing page | Human developers, GitLab visitors |\n| `CLAUDE.md` | Detailed project context | AI assistants (Claude Code) |\n| `AGENTS.md` | GitNexus code intelligence instructions | AI assistants using GitNexus |\n| `openspec/AGENTS.md` | OpenSpec workflow instructions | AI assistants using OpenSpec |\n| `openspec/project.md` | Project conventions template | AI assistants |\n\n## How It Works\n\n### Documentation Hierarchy\n\n```mermaid\ngraph TB\n subgraph \"Documentation Module\"\n README[\"README.md<br/>Landing page\"]\n CLAUDE[\"CLAUDE.md<br/>Project context\"]\n AGENTS[\"AGENTS.md<br/>GitNexus instructions\"]\n OPENSPEC[\"openspec/AGENTS.md<br/>OpenSpec instructions\"]\n PROJECT[\"openspec/project.md<br/>Conventions template\"]\n end\n \n subgraph \"Used By\"\n GITHUB[\"GitNexus<br/>Code Intelligence\"]\n OPENSPC[\"OpenSpec<br/>Spec-Driven Dev\"]\n HUMANS[\"Human<br/>Developers\"]\n end\n \n README --> HUMANS\n CLAUDE --> GITHUB\n AGENTS --> GITHUB\n OPENSPEC --> OPENSPC\n PROJECT --> OPENSPC\n```\n\n### Key Relationships\n\n1. **CLAUDE.md** feeds context to Claude Code AI assistants\n2. **AGENTS.md** provides GitNexus tool usage rules (impact analysis, rename, detect changes)\n3. **openspec/AGENTS.md** governs the spec-driven development workflow\n4. **openspec/project.md** is a template that should be filled with project-specific conventions\n\n## Key Components\n\n### README.md\nStandard GitLab project README. Currently contains template content—should be updated with:\n- Actual project description\n- Installation instructions\n- Tech stack details\n- Links to deployed instance\n\n### CLAUDE.md\nThe most critical documentation file. Contains:\n- **What Is This**: Project purpose (Test Forge - lightweight test reporting)\n- **Current State**: Git branch, what's done, what's pending\n- **Tech Stack**: Runtime, API, ORM, Jobs, CLI, UI, Docker\n- **Monorepo Structure**: Full directory layout\n- **Database Schema**: 6 tables with relationships\n- **API Routes**: All REST endpoints\n- **Conventions**: Import rules, no enums, color usage\n- **Running Locally**: Both dev and Docker modes\n- **How to Execute Work**: Subagent delegation rules\n\n### AGENTS.md\nGitNexus-specific instructions:\n- **MUST** run impact analysis before editing\n- **MUST** run detect_changes before committing\n- **MUST** warn on HIGH/CRITICAL risk\n- Tool reference: query, context, impact, detect_changes, rename, cypher\n- Risk level definitions (d=1, d=2, d=3)\n- Index freshness maintenance\n\n### openspec/AGENTS.md\nOpenSpec workflow governance:\n- Three-stage workflow: Creating → Implementing → Archiving changes\n- Proposal structure requirements\n- Spec delta format (ADDED/MODIFIED/REMOVED)\n- Scenario formatting rules\n- Validation commands\n\n### openspec/project.md\nTemplate file (currently empty) for:\n- Project purpose\n- Tech stack\n- Code style conventions\n- Architecture patterns\n- Testing strategy\n- Git workflow\n- Domain context\n\n## Integration Points\n\n### With GitNexus\nThe documentation module is indexed by GitNexus as **test-forge**. The AGENTS.md file contains the rules that GitNexus tools enforce:\n- Impact analysis before edits\n- Pre-commit scope verification\n- Safe renaming via graph understanding\n\n### With OpenSpec\nThe openspec/ directory contains:\n- `AGENTS.md` - Workflow instructions\n- `project.md` - Project conventions\n- `specs/` - Capability specifications (what IS built)\n- `changes/` - Change proposals (what SHOULD change)\n\n### With Claude Code\nCLAUDE.md is read automatically when Claude Code starts working in the project. It provides:\n- Complete project context\n- Current branch state\n- Pending tasks\n- Delegation rules (Opus orchestrates, subagents execute)\n\n## Usage Guidelines\n\n### For Human Developers\n1. Start with README.md for project overview\n2. Check CLAUDE.md for current development state\n3. Follow monorepo structure conventions\n\n### For AI Assistants\n1. **MUST** read CLAUDE.md on project start\n2. **MUST** read relevant AGENTS.md before code changes\n3. **MUST** use OpenSpec for proposals and spec changes\n4. **MUST** run GitNexus impact analysis before editing\n5. **MUST** run detect_changes before committing\n\n## Maintenance\n\nWhen updating this module:\n- Keep CLAUDE.md current with branch state\n- Update AGENTS.md if GitNexus workflow changes\n- Fill in openspec/project.md with actual conventions\n- Keep README.md in sync with actual project state\n\n## Notes\n\n- The call graph shows no relationships because these are documentation files, not executable code\n- These files are gitignored in the project's Git rules (NEVER commit .agents/, .claude/, AGENTS.md, CLAUDE.md)\n- The documentation is designed to be read by AI systems, not just humans","frontend-core":"# Frontend Core\n\n# Frontend Core Module (`@test-forge/ui`)\n\n## Overview\n\nThe Frontend Core module provides the main Vue 3 application shell for Test Forge, a test automation dashboard. It handles the application layout (navigation, sidebar, content area), routing configuration, and serves as the foundation for all page-level components.\n\n## Architecture\n\nThe module follows a standard Vue 3 SPA (Single Page Application) architecture:\n\n```mermaid\ngraph TB\n subgraph \"Entry Points\"\n HTML[index.html]\n TS[main.ts]\n end\n\n subgraph \"App Shell\"\n APP[App.vue]\n NAV[Top Navigation]\n SIDEBAR[Left Sidebar]\n MAIN[Main Content]\n MOBILE[Mobile Drawer]\n end\n\n subgraph \"Routing\"\n ROUTER[Vue Router]\n PAGES[Page Components]\n end\n\n subgraph \"Theming\"\n CAT[catppuccin.css]\n FORGE[forge-ui.css]\n end\n\n HTML --> TS\n TS --> APP\n APP --> NAV\n APP --> SIDEBAR\n APP --> MAIN\n APP --> MOBILE\n TS --> ROUTER\n ROUTER --> PAGES\n TS --> CAT\n TS --> FORGE\n```\n\n## Key Components\n\n### App.vue — Application Shell\n\nThe root component (`packages/ui/src/App.vue`) implements a three-zone layout:\n\n| Zone | Position | Width | Purpose |\n|------|----------|-------|---------|\n| **Top Navigation** | Fixed top | 100% × 48px | Global actions, branding, user controls |\n| **Left Sidebar** | Fixed left | 240px | Project navigation, search, quick actions |\n| **Main Content** | Fixed right | Remaining | Page-specific content, scrollable |\n\n#### Top Navigation Bar\n\n- **Branding**: Logo circle (\"TF\") + \"Test Forge\" wordmark\n- **Product switcher**: Links to \"Automate\" product area\n- **Actions**: Notification bell (with badge), help button, user avatar\n- **Mobile**: Hamburger menu button appears at ≤900px viewport\n\n#### Left Sidebar\n\n- **Search**: Input field with keyboard shortcut hint (`/`)\n- **Quick Start**: Progress indicator showing onboarding completion\n- **Top-level nav**: Overview, Dashboards, Reports\n- **Projects section**: Collapsible section with:\n - Project selector dropdown\n - Project-specific links: Build Runs, Tests Health, Unique Errors, Testing Trends, Test Cases, Settings\n\n#### Main Content Area\n\n- Hosts the `<router-view>` component\n- Includes a loading bar that animates during route transitions\n- Scrollable independently from the sidebar\n\n#### Mobile Drawer\n\n- Full-width sidebar overlay at ≤900px\n- Contains duplicated navigation structure\n- Closes on route change or overlay click\n\n### State Management\n\nThe component uses Vue's Composition API with `ref` and `computed`:\n\n```typescript\nconst projectsOpen = ref(true) // Sidebar section toggle\nconst mobileMenuOpen = ref(false) // Mobile drawer visibility\nconst globalDays = ref(14) // Shared date range filter\nconst showLoadingBar = ref(false) // Route transition indicator\n```\n\n### Router Integration\n\nNavigation guards manage the loading bar:\n\n```typescript\nrouter.beforeEach(() => { showLoadingBar.value = true })\nrouter.afterEach(() => { \n setTimeout(() => { showLoadingBar.value = false }, 550) \n})\n```\n\n## Routing\n\nThe router is configured in `main.ts` with 14 routes:\n\n| Path | Component | Purpose |\n|------|-----------|---------|\n| `/` | `Dashboard.vue` | Main overview |\n| `/runs` | `RunList.vue` | Build runs list |\n| `/runs/:id` | `RunDetail.vue` | Single run details |\n| `/tests/:id` | `TestHistory.vue` | Test case history |\n| `/flaky` | `FlakyBoard.vue` | Flaky test tracking |\n| `/errors` | `ErrorClusters.vue` | Error grouping |\n| `/perf` | `Performance.vue` | Performance metrics |\n| `/filters` | `Filters.vue` | Filter configuration |\n| `/compare` | `Compare.vue` | Build comparison |\n| `/projects` | `Projects.vue` | Project management |\n| `/muted` | `MutedTests.vue` | Muted test list |\n| `/trends` | `Trends.vue` | Testing trends |\n| `/defects` | `Defects.vue` | Defect triage |\n| `/test-cases` | `TestCases.vue` | Test case library |\n\nAll components use lazy loading via dynamic imports for code splitting.\n\n## Styling & Theming\n\n### CSS Architecture\n\nThe application uses a layered CSS approach:\n\n1. **Reset**: Global `html`/`body` reset in `<style scoped>`\n2. **Theme imports**: `catppuccin.css` + `forge-ui.css` in `main.ts`\n3. **Component styles**: Scoped CSS in `App.vue`\n\n### CSS Variables\n\nThe component references these custom properties (defined in theme files):\n\n```css\n--bg-canvas /* Main background */\n--border-default /* Default border color */\n--border-strong /* Stronger border */\n--text-primary /* Primary text */\n--text-secondary /* Secondary text */\n--text-muted /* Muted text */\n--color-primary /* Brand orange: #F97316 */\n--surface0 /* Surface layer 0 */\n```\n\n### Responsive Breakpoints\n\n- **Desktop**: ≥901px — Full sidebar visible\n- **Tablet/Mobile**: ≤900px — Sidebar hidden, hamburger shown\n\n### Accessibility\n\n- ARIA labels on all interactive elements\n- `aria-expanded` on collapsible sections\n- Focus-visible outlines on buttons\n- Reduced motion media query support\n\n## Build Configuration\n\n### Vite (`vite.config.ts`)\n\n```typescript\n// Key configurations\n{\n resolve: { alias: { \"@\": \"./src\" } },\n server: { proxy: { \"/api\": \"http://localhost:3000\" } },\n build: { outDir: \"../api/public\" }\n}\n```\n\n- **Alias**: `@/` maps to `src/` for cleaner imports\n- **Proxy**: API requests to `/api` forward to backend on port 3000\n- **Output**: Built assets go to `../api/public` for server-side serving\n\n### Dependencies\n\n| Package | Version | Purpose |\n|---------|---------|---------|\n| `vue` | ^3.5.0 | Framework |\n| `vue-router` | ^4.4.0 | Routing |\n| `chart.js` | ^4.4.0 | Charts (used by pages) |\n| `vue-chartjs` | ^5.3.0 | Vue chart wrappers |\n\n### Scripts\n\n```bash\nnpm run dev # Start dev server\nnpm run build # Type-check + production build\nnpm run preview # Preview production build\nnpm run typecheck# TypeScript validation only\n```\n\n## How It Connects to the Rest of the Codebase\n\n### Backend Integration\n\nThe frontend communicates with the backend via the API proxy:\n\n```\nBrowser → Vite Dev Server → Proxy (/api/*) → http://localhost:3000\n```\n\nIn production, built assets in `api/public` are served by the backend server.\n\n### Page Components\n\nThe `App.vue` shell renders page components via `<router-view>`. Each page (e.g., `Dashboard.vue`, `RunList.vue`) is a separate component that receives data from the API and renders its own content.\n\n### Shared State\n\n- `globalDays` (ref) — Passed as prop to `<router-view>`, allowing pages to share a date range filter\n- Router state — Current route determines active navigation states\n\n## TypeScript Configuration\n\nThe `tsconfig.json` extends the monorepo's base config with:\n\n```json\n{\n \"module\": \"ESNext\",\n \"moduleResolution\": \"Bundler\",\n \"paths\": { \"@/*\": [\"./src/*\"] }\n}\n```\n\nThe `Bundler` resolution enables modern import syntax and works with Vite's build pipeline.\n\n## Development Workflow\n\n1. **Run dev server**: `npm run dev` — Starts Vite with HMR\n2. **Navigate**: Open `http://localhost:5173` (default Vite port)\n3. **API calls**: Proxied to `http://localhost:3000`\n4. **Build**: `npm run build` — Produces optimized assets in `api/public`\n\n## Key Implementation Details\n\n### Fixed Positioning Layout\n\nThe app uses fixed positioning for the shell structure:\n\n```css\n.top-nav { position: fixed; top: 0; left: 0; right: 0; height: 48px; }\n.project-nav{ position: fixed; left: 0; top: 48px; width: 240px; height: calc(100vh - 48px); }\n.main-content{ position: fixed; left: 240px; top: 48px; right: 0; bottom: 0; overflow-y: auto; }\n```\n\nThis ensures the navigation and sidebar stay visible while only the content area scrolls.\n\n### Route Transition Animation\n\nThe loading bar animates during navigation:\n\n```vue\n<Transition name=\"nav-loading-bar\" appear>\n <div v-if=\"showLoadingBar\" class=\"nav-loading-bar\"></div>\n</Transition>\n```\n\n### Active Link Styling\n\nNavigation uses Vue Router's `active-class` and `exact-active-class`:\n\n- `exact-active-class=\"pnav-item--active\"` — Exact path match\n- `active-class=\"pnav-project-link--active\"` — Prefix match for project links\n\n### Theme Enforcement\n\nThe component forces light theme on mount:\n\n```typescript\ndocument.documentElement.setAttribute('data-theme', 'light')\n```\n\nThis ensures consistent styling regardless of system preferences.","overview":"# test-forge — Wiki\n\n# Test Forge — Test Analytics Platform\n\nWelcome to **Test Forge**, a comprehensive test analytics and quality monitoring platform for modern CI/CD pipelines. Test Forge aggregates test results from multiple testing frameworks, provides real-time analytics, and enables teams to track test health, detect flaky tests, and monitor performance trends across their entire testing ecosystem.\n\n## What It Does\n\nTest Forge bridges the gap between raw test execution and actionable insights. Teams run Playwright for E2E and API tests, k6 for performance testing, and countless other tools — but getting a unified view of quality across all of these is notoriously difficult. Test Forge solves this by providing a CLI that ingests results from any testing framework, normalizes them into a consistent format, and presents them through a modern dashboard.\n\nThe platform handles the full test lifecycle: collecting results from CI pipelines via the CLI, storing them in PostgreSQL, processing analytics in background jobs, and serving interactive dashboards through the UI.\n\n## Architecture Overview\n\n```mermaid\nflowchart LR\n subgraph \"CI / External\"\n CI[CI Pipelines]\n Playwright[Playwright]\n K6[k6]\n end\n\n subgraph \"test-forge\"\n CLI[CLI Core]\n Adapters[Test Result<br/>Adapters]\n \n subgraph \"API Server\"\n Routes[API Routes]\n Jobs[Background Jobs]\n DB[Database<br/>Layer]\n end\n \n Static[Static Assets<br/>(Frontend SPA)]\n \n subgraph \"UI\"\n Pages[Pages]\n Components[Components]\n Theme[Theme]\n end\n end\n\n CI -->|forge push| CLI\n Playwright -->|results| CLI\n K6 -->|results| CLI\n CLI --> Adapters\n Adapters --> Routes\n Routes --> DB\n Routes --> Static\n Jobs --> DB\n Jobs --> Static\n Static --> Pages\n Pages --> Components\n Components --> Theme\n```\n\n## Key Modules\n\n### CLI & Ingestion\n\nThe **[CLI Core](cli-core.md)** (`forge`) is your primary integration point with CI pipelines. It accepts test results from Playwright and k6, transforms them through **[Test Result Adapters](test-result-adapters.md)**, and pushes them to the API. The adapters normalize disparate formats into a unified schema, so downstream code works with any testing tool identically.\n\n### API Server\n\nThe **[API Server](api-server.md)** built on Hono handles all HTTP traffic. It serves the REST API for test run management, analytics endpoints, and — in production — delivers the compiled frontend itself. The **[Database Layer](database-layer.md)** uses Drizzle ORM with PostgreSQL to store projects, runs, test results, and metrics. For long-running operations like trend calculations and notification dispatch, **[Background Jobs](background-jobs.md)** powered by pgBoss process work asynchronously.\n\n### Frontend\n\nThe **[Frontend Core](frontend-core.md)** is a Vue 3 SPA that provides the visual interface. **[UI Pages](ui-pages.md)** render dashboards, run details, and analytics views by consuming the API through the **[UI API Client](ui-api-client.md)**. Visual consistency comes from the **[UI Theme](ui-theme.md)** (design tokens and component styles) and reusable **[UI Components](ui-components.md)**. The frontend is compiled and served by the **[API Static Assets](api-static-assets.md)** module.\n\n## Development Setup\n\nThis is a monorepo using npm workspaces. After cloning:\n\n```bash\n# Install dependencies\nnpm install\n\n# Generate database schema (required before running)\nnpm run db:generate\nnpm run db:migrate\n\n# Seed with sample data for development\nnpm run db:seed\n\n# Start development servers\nnpm run dev # API only\nnpm run dev:ui # Frontend only\nnpm run dev:all # Both\n```\n\nThe available scripts include `build`, `typecheck`, and database utilities (`db:generate`, `db:migrate`, `db:seed`). For containerized deployment, the project includes Docker configuration for running the full stack.\n\n## Where to Go Next\n\n- **[API Server](api-server.md)** — Backend architecture, routing, and integrations\n- **[CLI Core](cli-core.md)** — CLI usage and configuration\n- **[Frontend Core](frontend-core.md)** — Vue 3 application structure\n- **[UI Theme](ui-theme.md)** — Design system and styling conventions","project-configuration":"# Project Configuration\n\n# Project Configuration Module\n\nThis module contains the foundational configuration files that define the test-forge monorepo structure, build processes, and containerized deployment setup. These files establish how the application is structured, built, and run in both development and production environments.\n\n## Overview\n\nThe test-forge project is a **Node.js monorepo** using npm workspaces. It consists of three packages:\n\n- **packages/api** — Backend API server\n- **packages/ui** — Frontend user interface\n- **packages/cli** — Command-line tools\n\nThe configuration module orchestrates type checking, building, database migrations, and containerized deployment through Docker.\n\n## Project Structure\n\n```mermaid\ngraph TB\n subgraph \"Root Configuration\"\n A[package.json<br/>Monorepo config]\n B[tsconfig.base.json<br/>Shared TS settings]\n C[Dockerfile<br/>Multi-stage build]\n D[docker-compose.yml<br/>Service orchestration]\n E[docker-entrypoint.sh<br/>Startup script]\n end\n\n subgraph \"Packages\"\n F[packages/api]\n G[packages/ui]\n H[packages/cli]\n end\n\n A --> F\n A --> G\n A --> H\n B --> F\n B --> G\n B --> H\n```\n\n## Key Components\n\n### Root package.json\n\nThe root `package.json` defines the monorepo structure and npm scripts:\n\n```json\n{\n \"name\": \"test-forge\",\n \"type\": \"module\",\n \"workspaces\": [\"packages/*\"]\n}\n```\n\n**Key scripts:**\n\n| Script | Purpose |\n|--------|---------|\n| `dev` | Run API in development mode |\n| `dev:ui` | Run UI in development mode |\n| `dev:all` | Run both API and UI concurrently |\n| `build` | Build all packages |\n| `build:api` / `build:ui` / `build:cli` | Build specific packages |\n| `typecheck` | Run TypeScript type checking on all packages |\n| `db:generate` | Generate Drizzle ORM migrations |\n| `db:migrate` | Run database migrations |\n| `db:seed` | Seed the database with initial data |\n| `clean` | Remove build artifacts |\n\nThe `engines` field requires **Node.js >= 22.0.0**, which is significant because the project uses modern Node.js features like the URL import assertions (implied by `\"type\": \"module\"`).\n\n---\n\n### tsconfig.base.json\n\nThis file provides shared TypeScript configuration inherited by all packages:\n\n```json\n{\n \"compilerOptions\": {\n \"target\": \"ES2023\",\n \"module\": \"NodeNext\",\n \"moduleResolution\": \"NodeNext\",\n \"strict\": true,\n \"declaration\": true,\n \"declarationMap\": true,\n \"sourceMap\": true,\n \"esModuleInterop\": true,\n \"isolatedModules\": true\n }\n}\n```\n\n**Key settings:**\n\n- **`target: ES2023`** — Compiles to the latest ECMAScript standard\n- **`module: NodeNext`** — Uses Node.js native ES module support\n- **`strict: true`** — Enables all strict type-checking options\n- **`isolatedModules: true`** — Ensures each file can be transpiled independently (required for bundlers like Vite)\n\nEach package (`packages/api`, `packages/ui`, `packages/cli`) extends this base configuration with package-specific overrides in their own `tsconfig.json` files.\n\n---\n\n### Dockerfile\n\nThe Dockerfile uses a **multi-stage build** strategy to produce a lean production image:\n\n```dockerfile\n# Stage 1: Build UI\nFROM node:22-alpine AS ui-build\n# → Installs dependencies and builds UI assets\n\n# Stage 2: Build API\nFROM node:22-alpine AS api-build\n# → Installs dependencies and compiles TypeScript\n\n# Stage 3: Production runtime\nFROM node:22-alpine\n# → Copies only built artifacts and runtime dependencies\n```\n\n**Build stages:**\n\n1. **ui-build** — Installs `packages/ui` dependencies and runs its build script, producing static assets in `packages/ui/dist`\n2. **api-build** — Installs `packages/api` dependencies, compiles TypeScript to JavaScript in `packages/api/dist`\n3. **Production** — Combines both builds, installs only production dependencies (`--omit=dev`), and includes `drizzle-kit` and `tsx` for runtime database operations\n\nThe final image:\n- Exposes port **3000**\n- Includes PostgreSQL client tools (`postgresql-client`)\n- Uses a custom entrypoint script for database initialization\n\n---\n\n### docker-compose.yml\n\nDefines two services:\n\n| Service | Description |\n|---------|-------------|\n| **app** | The main application container |\n| **postgres** | PostgreSQL 16 database |\n\n**app service configuration:**\n- Builds from the Dockerfile in the current directory\n- Exposes port 3000 (configurable via `PORT` environment variable)\n- Depends on `postgres` being healthy before starting\n- Auto-restarts unless stopped\n\n**postgres service configuration:**\n- Uses `postgres:16-alpine` image\n- Creates database `test_forge` with credentials `forge:forge`\n- Includes a health check using `pg_isready`\n- Persists data to a named volume `pgdata`\n\n**Environment variables:**\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `PORT` | 3000 | HTTP server port |\n| `HOST` | 0.0.0.0 | Bind address |\n| `DATABASE_URL` | (required) | PostgreSQL connection string |\n| `SEED_DATA` | not set | Set to \"true\" to seed database on startup |\n\n---\n\n### docker-entrypoint.sh\n\nThis shell script runs automatically when the container starts. It handles:\n\n1. **Database readiness** — Waits for PostgreSQL to be available by parsing `DATABASE_URL` and running `pg_isready` in a loop\n2. **Schema migration** — Runs `drizzle-kit push` to synchronize the database schema\n3. **Data seeding** — If `SEED_DATA=true`, runs the seed script to populate initial data\n4. **Server startup** — Executes the built API server\n\n```bash\n# Key flow\nwait_for_postgres → push_schema → (optional) seed_data → start_server\n```\n\nThe script uses `set -e` to exit immediately on any error, ensuring failures are caught early.\n\n---\n\n## Development Workflow\n\n### Local Development\n\n```bash\n# Start both services with Docker\ndocker-compose up --build\n\n# Or run without Docker for API development\nnpm run dev # API only\nnpm run dev:ui # UI only\nnpm run dev:all # Both concurrently\n```\n\n### Database Operations\n\n```bash\n# Generate migration files\nnpm run db:generate\n\n# Apply migrations\nnpm run db:migrate\n\n# Seed the database\nnpm run db:seed\n```\n\n### Building\n\n```bash\n# Build all packages\nnpm run build\n\n# Build for production Docker image\ndocker-compose build\n```\n\n---\n\n## Docker Architecture\n\n```mermaid\nsequenceDiagram\n participant Container as app container\n participant Postgres as postgres container\n participant DB as PostgreSQL\n\n Container->>Postgres: pg_isready (wait loop)\n Postgres->>DB: Health check\n DB-->>Postgres: healthy\n Postgres-->>Container: pg_isready returns success\n\n Container->>Container: drizzle-kit push\n Container->>DB: Apply schema\n\n alt SEED_DATA=true\n Container->>Container: node scripts/seed.ts\n Container->>DB: Insert initial data\n end\n\n Container->>Container: node packages/api/dist/server.js\n Container-->>Client: HTTP on port 3000\n```\n\n---\n\n## Configuration Dependencies\n\nThe configuration files reference each other in specific ways:\n\n1. **`package.json`** → References `packages/*` for workspace membership\n2. **`tsconfig.base.json`** → Referenced by each package's `tsconfig.json` via `extends`\n3. **`Dockerfile`** → Copies `tsconfig.base.json` into build contexts for each package\n4. **`docker-compose.yml`** → References `Dockerfile` for building the app service\n5. **`docker-entrypoint.sh`** → References `drizzle.config.ts` and `scripts/seed.ts` from the API package\n\nThis creates a dependency chain where the root configuration flows down to individual packages, and the Docker build assembles everything into a deployable image.\n\n---\n\n## Notes for Contributors\n\n- When adding a new package, add it to the `workspaces` array in root `package.json`\n- Each package should extend `tsconfig.base.json` in its own `tsconfig.json`\n- Database configuration lives in `packages/api/drizzle.config.ts` — not in this module\n- The entrypoint script assumes the API package structure; any structural changes to `packages/api` may require updates to `docker-entrypoint.sh`","test-result-adapters":"# Test Result Adapters\n\n# Test Result Adapters Module\n\nThis module provides adapter functions that transform tool-specific test result formats into a unified, universal format. This abstraction allows the CLI to consume results from different testing tools (k6, Playwright) through a consistent interface.\n\n## Purpose\n\nWhen integrating multiple testing tools into a reporting pipeline, each tool has its own JSON output structure. Rather than building tool-specific handling throughout the codebase, this module centralizes the parsing logic and exposes a consistent data shape that downstream consumers can work with.\n\n## Architecture\n\nThe module follows the **Adapter Pattern** — each adapter converts a specific tool's output format into the universal format:\n\n```\n┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐\n│ k6 JSON │ │ │ │ │\n│ Summary │───▶│ k6.ts adapter │───▶│ Universal types │\n├─────────────────┤ │ │ │ │\n│ Playwright │ │ playwright.ts │───▶│ │\n│ JSON Report │ │ adapter │ │ │\n└─────────────────┘ └──────────────────┘ └─────────────────┘\n```\n\n---\n\n## k6 Adapter (`k6.ts`)\n\nThe k6 adapter parses performance test summary output from the `k6 handleSummary()` hook, which produces a JSON file containing metrics and check results.\n\n### Exported Interfaces\n\n```typescript\ninterface PerfMetricResult {\n name: string;\n endpoint: string | null;\n method: string | null;\n count: number;\n avg: number | null;\n min: number | null;\n max: number | null;\n med: number | null;\n p90: number | null;\n p95: number | null;\n p99: number | null;\n thresholdPassed: number | null;\n unit: string;\n}\n\ninterface K6CheckResult {\n title: string;\n status: string;\n durationMs: number;\n passes: number;\n fails: number;\n}\n\ninterface K6ParseResult {\n metrics: PerfMetricResult[];\n checks: K6CheckResult[];\n totalChecks: number;\n passedChecks: number;\n failedChecks: number;\n}\n```\n\n### Main Function\n\n**`parseK6Summary(filePath: string): K6ParseResult`**\n\nReads a k6 JSON summary file and returns performance metrics and check results in universal format.\n\n**What it does:**\n\n1. **Reads and parses** the JSON file from the given path\n2. **Extracts metrics** — iterates over all metrics in `summary.metrics`, converting values like `p(90)`, `p(95)`, `p(99)` to camelCase properties. Determines the unit based on metric type and name.\n3. **Processes thresholds** — if a metric has threshold definitions, sets `thresholdPassed` to `1` if all thresholds pass, `0` otherwise.\n4. **Walks check groups** — recursively traverses the `root_group` hierarchy (via internal `walkGroups` function) to collect all checks and aggregate pass/fail counts.\n\n**Note:** k6's summary output doesn't include per-endpoint breakdown or per-check duration. These fields are set to `null` or `0` accordingly.\n\n---\n\n## Playwright Adapter (`playwright.ts`)\n\nThe Playwright adapter parses JSON reports generated by Playwright's `--reporter json` option. It extracts test results, metadata, and artifact URLs.\n\n### Exported Interfaces\n\n```typescript\ninterface UniversalResult {\n title: string;\n suite: string | null;\n file: string | null;\n status: string;\n durationMs: number;\n errorMessage: string | null;\n errorStack: string | null;\n traceUrl: string | null;\n videoUrl: string | null;\n screenshotUrl: string | null;\n retryCount: number;\n partner: string | null;\n area: string | null;\n device: string | null;\n ticket: string | null;\n tags: string[];\n metadata: Record<string, unknown> | null;\n}\n```\n\n### Main Function\n\n**`parsePlaywrightReport(filePath: string, s3Prefix?: string): UniversalResult[]`**\n\nReads a Playwright JSON report and returns an array of test results in universal format.\n\n**What it does:**\n\n1. **Reads and parses** the JSON report file\n2. **Walks the suite hierarchy** — recursively traverses nested suites (via internal `walkSuites` function) to find all specs and tests\n3. **Extracts test results** — for each test, takes the final retry result (last entry in `results` array)\n4. **Builds artifact URLs** — if `s3Prefix` is provided, constructs full URLs for trace, video, and screenshot attachments by prepending the S3 bucket path\n5. **Extracts metadata** — calls `extractMeta()` from `../extract.js` to parse partner, area, device, ticket, and tags from test titles and file paths\n\n**Note:** The `metadata` field captures additional context like `projectName` and `timeout` that don't have dedicated universal fields.\n\n---\n\n## Integration with the CLI\n\nThese adapters are called from `cli/src/parse.ts` (the `parseResults` function), which acts as the orchestration layer. The parse module determines which adapter to use based on input configuration and delegates parsing to the appropriate adapter.\n\n```mermaid\nflowchart LR\n A[CLI Entry] --> B[parseResults]\n B --> C{Test type?}\n C -->|k6| D[parseK6Summary]\n C -->|Playwright| E[parsePlaywrightReport]\n D --> F[Universal format]\n E --> F\n F --> G[ downstream processing]\n```\n\n---\n\n## Key Design Decisions\n\n| Decision | Rationale |\n|----------|-----------|\n| Separate adapters per tool | Allows independent evolution of parsing logic; adding a new tool only requires a new adapter file |\n| Universal result types | Downstream consumers (reporting, aggregation) work with one shape regardless of source |\n| Recursive traversal for nested structures | Both k6 (groups) and Playwright (suites) use hierarchical data; recursion handles arbitrary depth |\n| Optional S3 prefix for artifacts | Artifact URLs are only meaningful when artifacts are uploaded; the prefix enables flexible storage backends |\n\n---\n\n## Adding a New Adapter\n\nTo add support for another testing tool:\n\n1. Create a new file in `packages/cli/src/adapters/` (e.g., `cypress.ts`)\n2. Define interfaces for the tool's native JSON format\n3. Create a parse function that returns `UniversalResult[]` (or a new universal type if the tool produces fundamentally different data)\n4. Update `parseResults` in `cli/src/parse.ts` to route to the new adapter","ui-api-client":"# UI API Client\n\n# UI API Client\n\nThe `ui-api-client` module provides a typed interface for the Test Forge UI to communicate with its backend API. It handles all HTTP communication, enforces TypeScript contracts on request/response shapes, and centralizes API error handling.\n\n## Overview\n\nThis module lives in `packages/ui/src/api/client.ts` and serves as the **single source of truth** for API interactions throughout the UI application. It exports:\n\n1. A generic `request` function wrapping `fetch`\n2. TypeScript interfaces for every API entity\n3. An `api` object with methods for each endpoint\n\n```mermaid\nflowchart TD\n subgraph client[\"API Client Module\"]\n request[\"request<T>()<br>HTTP wrapper\"]\n types[\"TypeScript<br>Interfaces\"]\n methods[\"api<br>methods\"]\n end\n \n request --> types\n methods --> request\n \n consumer1[Dashboard<br>Component]\n consumer2[Runs<br>Page]\n consumer3[Compare<br>View]\n \n consumer1 --> methods\n consumer2 --> methods\n consumer3 --> methods\n```\n\n## Core Components\n\n### The `request` Function\n\nThe private `request<T>` function is the foundation of all API communication:\n\n```typescript\nasync function request<T>(path: string, init?: RequestInit): Promise<T> {\n const res = await fetch(`${BASE}${path}`, {\n headers: { \"Content-Type\": \"application/json\", ...init?.headers },\n ...init,\n });\n if (!res.ok) throw new Error(`API ${res.status}: ${res.statusText}`);\n return res.json() as Promise<T>;\n}\n```\n\n**Behavior:**\n- Prefixes all paths with `/api` (the `BASE` constant)\n- Always sends `Content-Type: application/json`\n- Throws an `Error` with status text when the response is not OK\n- Deserializes JSON responses into the expected type `T`\n\nThis is a thin wrapper around `fetch` that standardizes error handling and content type negotiation across all API calls.\n\n### Type Definitions\n\nThe module exports ~30 interfaces organized by feature domain. These types mirror the backend API response shapes exactly, ensuring type safety throughout the UI.\n\n| Domain | Key Types |\n|--------|-----------|\n| Core entities | `Project`, `Run`, `TestResult`, `Test` |\n| Dashboard | `DashboardResponse`, `DashboardSummary` |\n| Runs | `RunListResponse`, `RunDetailResponse` |\n| Flaky tests | `FlakyResponse`, `FlakyTest` |\n| Errors | `ErrorsResponse`, `ErrorCluster` |\n| Performance | `PerfResponse`, `PerfTrendResponse` |\n| Compare | `CompareResponse`, `CompareTestDiff` |\n| Mute | `MuteResponse`, `MuteRequest` |\n| Defects | `DefectsResponse`, `DefectLabelRequest` |\n\n### The `api` Object\n\nThe `api` export consolidates all endpoint methods:\n\n```typescript\nexport const api = {\n getDashboard, // GET /dashboard\n getRuns, // GET /runs\n getRun, // GET /runs/:id\n getTestTrend, // GET /tests/:id/trend\n getFlaky, // GET /flaky\n getErrors, // GET /errors\n getPerf, // GET /perf\n getPerfTrend, // GET /perf/trend\n getCompare, // GET /compare\n evaluateGate, // POST /gate/evaluate\n getHealth, // GET /health\n getProjects, // GET /projects\n getInsights, // GET /insights\n getMuted, // GET /mute\n muteTest, // POST /mute\n unmuteTest, // DELETE /mute/:testId\n getTrends, // GET /trends\n getDefects, // GET /defects\n getRunDefects, // GET /defects/run/:runId\n labelDefect, // POST /defects\n};\n```\n\nEach method:\n- Accepts optional query parameters as `{ key: string }` records (converted to URLSearchParams)\n- Returns a typed Promise resolving to the response shape\n- Handles the HTTP method internally (GET for reads, POST/DELETE for mutations)\n\n## Usage Patterns\n\n### Query Parameters\n\nMethods accepting filters use a consistent pattern:\n\n```typescript\n// GET /runs?project=my-project&status=failed\nconst runs = await api.getRuns({ project: \"my-project\", status: \"failed\" });\n```\n\nThe `params` object is serialized via `URLSearchParams`, so values are automatically encoded.\n\n### Path Parameters\n\nEndpoints with path variables take direct arguments:\n\n```typescript\n// GET /runs/123\nconst run = await api.getRun(\"123\");\n\n// GET /tests/456/trend?days=30\nconst trend = await api.getTestTrend(\"456\", { days: \"30\" });\n```\n\n### POST Requests\n\nMutation methods accept a request body:\n\n```typescript\n// POST /mute\nawait api.muteTest({\n testId: 123,\n projectSlug: \"my-project\",\n reason: \"Flaky in CI\",\n mutedBy: \"john@example.com\"\n});\n```\n\n## Error Handling\n\nThe `request` function throws on any non-2xx response:\n\n```typescript\ntry {\n const run = await api.getRun(\"123\");\n} catch (e) {\n // e.message => \"API 404: Not Found\"\n console.error(e);\n}\n```\n\nConsumers should wrap API calls in try/catch or use React Query / SWR for automatic error state management.\n\n## Integration with the UI\n\nThis module is typically consumed by:\n\n- **React components** — calling `api.get*` in `useEffect` or data hooks\n- **Custom hooks** — encapsulating API calls with loading/error states\n- **Data libraries** — React Query, SWR, or similar caching layers sit on top of this client\n\nExample integration:\n\n```typescript\nimport { api } from \"./api/client\";\n\nfunction useDashboard() {\n const [data, setData] = useState<DashboardResponse | null>(null);\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n api.getDashboard()\n .then(setData)\n .catch(setError);\n }, []);\n\n return { data, error, loading: !data && !error };\n}\n```\n\n## Extending the Client\n\nWhen the backend adds a new endpoint:\n\n1. Add the response type to the type definitions section\n2. Add a method to the `api` object\n3. The method follows the same pattern as existing ones:\n\n```typescript\n// Example extension pattern\ngetNewFeature: (params?: Record<string, string>) => {\n const qs = params ? \"?\" + new URLSearchParams(params).toString() : \"\";\n return request<NewFeatureResponse>(`/new-feature${qs}`);\n},\n```\n\n## Notes\n\n- **Base URL**: Currently hardcoded as `/api`. In production, this would typically come from an environment variable or configuration.\n- **Authentication**: Not currently implemented. If needed, add an `Authorization` header to the `request` function.\n- **Request/response interception**: The current design is minimal. For logging, retry logic, or auth refresh, consider wrapping `request` or using a library like `ky`.","ui-components":"# UI Components\n\n# UI Components Module\n\n## Overview\n\nThis module provides a set of reusable Vue 3 components for displaying test results and artifacts in a dashboard or reporting interface. The components are designed to work together to present test execution data with consistent styling and behavior.\n\nThe components use Vue's Composition API with `<script setup>` and TypeScript for type safety. They rely on CSS custom properties (CSS variables) for theming, allowing integration with different design systems.\n\n---\n\n## Component Catalog\n\n### ArtifactViewer\n\nA modal overlay component for viewing test artifacts—screenshots, videos, and Playwright trace files.\n\n```vue\n<ArtifactViewer\n :artifact=\"{\n type: 'screenshot' | 'video' | 'trace',\n url: string,\n testTitle: string\n }\"\n @close=\"handler\"\n/>\n```\n\n**Props:**\n\n| Prop | Type | Required | Default | Description |\n|------|------|----------|---------|-------------|\n| `artifact` | `{type, url, testTitle}` | Yes | — | The artifact to display. Pass `null` to hide the viewer. |\n| `type` | `'screenshot' \\| 'video' \\| 'trace'` | Yes | — | Determines content rendering |\n| `url` | `string` | Yes | — | URL to the artifact file |\n| `testTitle` | `string` | Yes | — | Title displayed in the header |\n\n**Events:**\n\n| Event | Payload | Description |\n|-------|---------|-------------|\n| `close` | — | Emitted when user clicks the close button or presses Escape |\n\n**Behavior:**\n\n- Uses Vue's `<Teleport>` to render at `document.body`, ensuring it appears above other content\n- Adds a keyboard listener for the Escape key to close the modal\n- Renders different content based on artifact type:\n - **screenshot**: Displays the image in an `<img>` element\n - **video**: Renders an HTML5 `<video>` player with controls\n - **trace**: Shows a link to open the trace in the online Playwright Trace Viewer, plus a copyable URL\n\n**Implementation Details:**\n\nThe component manages a `copied` ref to show feedback when the trace URL is copied to the clipboard. The keyboard listener is added on mount and removed on unmount to prevent memory leaks.\n\n---\n\n### PassRateBar\n\nA horizontal progress bar that displays a percentage with color-coded styling.\n\n```vue\n<PassRateBar :rate=\"85.5\" />\n<PassRateBar :rate=\"45\" :show-label=\"false\" :height=\"'4px'\" />\n```\n\n**Props:**\n\n| Prop | Type | Required | Default | Description |\n|------|------|----------|---------|-------------|\n| `rate` | `number` | Yes | — | Percentage value (0-100) |\n| `showLabel` | `boolean` | No | `true` | Whether to display the percentage label |\n| `height` | `string` | `\"8px\"` | — | CSS height value for the bar track |\n\n**Color Logic:**\n\nThe component automatically selects a color based on the rate value:\n\n- **Green** (`var(--green)`): rate ≥ 90%\n- **Yellow** (`var(--yellow)`): rate ≥ 70%\n- **Red** (`var(--red)`): rate < 70%\n\n**Styling:**\n\nThe bar uses `var(--surface0)` for the track background and applies a smooth transition when the width changes.\n\n---\n\n### StatCard\n\nA card component for displaying a statistic with an icon and label.\n\n```vue\n<StatCard icon=\"✓\" label=\"Passed\" :value=\"42\" />\n<StatCard icon=\"✕\" label=\"Failed\" :value=\"3\" color=\"var(--red)\" />\n```\n\n**Props:**\n\n| Prop | Type | Required | Default | Description |\n|------|------|----------|---------|-------------|\n| `icon` | `string` | Yes | — | Emoji or icon character to display |\n| `label` | `string` | Yes | — | Descriptive label below the value |\n| `value` | `string \\| number` | Yes | — | The statistic value to display |\n| `color` | `string` | No | `var(--text)` | CSS color for the value text |\n\n**Layout:**\n\nThe icon appears on the left, with the value and label stacked on the right. Both elements use `flex-direction: column` for vertical layout.\n\n---\n\n### StatusBadge\n\nA pill-shaped badge for displaying test status with semantic colors.\n\n```vue\n<StatusBadge status=\"passed\" />\n<StatusBadge status=\"failed\" size=\"sm\" />\n```\n\n**Props:**\n\n| Prop | Type | Required | Default | Description |\n|------|------|----------|---------|-------------|\n| `status` | `string` | Yes | — | Status identifier (passed, failed, etc.) |\n| `size` | `\"sm\" \\| \"md\"` | No | `\"md\"` | Badge size variant |\n\n**Supported Statuses:**\n\n| Status | Label | Color |\n|--------|-------|-------|\n| `passed` | \"Passed\" | green |\n| `completed` | \"Completed\" | green |\n| `failed` | \"Failed\" | red |\n| `running` | \"Running\" | mauve |\n| `skipped` | \"Skipped\" | overlay0 |\n| `flaky` | \"Flaky\" | yellow |\n| *(other)* | Capitalized first letter | — |\n\n**Styling:**\n\nUses `color-mix()` for semi-transparent backgrounds that adapt to light/dark themes via the CSS custom properties.\n\n---\n\n### StatusDots\n\nA compact row of colored dots representing a sequence of test results.\n\n```vue\n<StatusDots :statuses=\"['passed', 'passed', 'failed', 'skipped']\" />\n<StatusDots :statuses=\"results\" :dot-size=\"8\" />\n```\n\n**Props:**\n\n| Prop | Type | Required | Default | Description |\n|------|------|----------|---------|-------------|\n| `statuses` | `string[]` | Yes | — | Array of status strings |\n| `dotSize` | `number` | No | `10` | Diameter of each dot in pixels |\n\n**Color Mapping:**\n\n| Status | Color |\n|--------|-------|\n| `passed` | `var(--green)` |\n| `failed` | `var(--red)` |\n| `skipped` | `var(--overlay0)` |\n| *(other)* | `var(--surface2)` |\n\nEach dot includes the status as its `title` attribute, showing a tooltip on hover.\n\n---\n\n### TimeAgo\n\nA relative time component that displays how long ago a date occurred.\n\n```vue\n<TimeAgo date=\"2024-01-15T10:30:00Z\" />\n```\n\n**Props:**\n\n| Prop | Type | Required | Default | Description |\n|------|------|----------|---------|-------------|\n| `date` | `string` | Yes | — | ISO date string to display relatively |\n\n**Display Logic:**\n\n| Range | Format |\n|-------|--------|\n| < 60 seconds | \"just now\" |\n| < 60 minutes | \"Xm ago\" |\n| < 24 hours | \"Xh ago\" |\n| < 30 days | \"Xd ago\" |\n| ≥ 30 days | \"Xmo ago\" |\n\nThe full date (e.g., \"Jan 15, 2024 10:30\") appears as a `title` attribute, showing on hover.\n\n---\n\n## Usage Patterns\n\nThese components are designed to compose together in parent views. Typical patterns include:\n\n### Test Summary Card\n\nCombining `StatCard`, `PassRateBar`, and `StatusBadge`:\n\n```vue\n<div class=\"summary\">\n <StatCard icon=\"✓\" label=\"Passed\" :value=\"passedCount\" />\n <StatCard icon=\"✕\" label=\"Failed\" :value=\"failedCount\" color=\"var(--red)\" />\n <PassRateBar :rate=\"passRate\" />\n <StatusBadge :status=\"overallStatus\" />\n</div>\n```\n\n### Test List Item\n\nUsing `StatusBadge` and `StatusDots` for list rows:\n\n```vue\n<div v-for=\"test in tests\" :key=\"test.id\" class=\"test-row\">\n <StatusBadge :status=\"test.status\" size=\"sm\" />\n <span>{{ test.name }}</span>\n <StatusDots :statuses=\"test.retryStatuses\" :dot-size=\"6\" />\n <TimeAgo :date=\"test.startedAt\" />\n</div>\n```\n\n### Artifact Display\n\nOpening an `ArtifactViewer` when a user clicks a thumbnail:\n\n```vue\n<template>\n <img \n :src=\"thumbnailUrl\" \n @click=\"openArtifact({ type: 'screenshot', url: fullUrl, testTitle })\"\n />\n <ArtifactViewer :artifact=\"selectedArtifact\" @close=\"selectedArtifact = null\" />\n</template>\n```\n\n---\n\n## Dependencies\n\nThe module has no external runtime dependencies beyond Vue 3. It assumes the following CSS custom properties are defined in the application's global styles:\n\n```css\n:root {\n --green: #...;\n --yellow: #...;\n --red: #...;\n --mauve: #...;\n --overlay0: #...;\n --surface0: #...;\n --surface2: #...;\n --text: #...;\n}\n```\n\n---\n\n## File Structure\n\n```\npackages/ui/src/components/\n├── ArtifactViewer.vue # Modal for viewing test artifacts\n├── PassRateBar.vue # Color-coded progress bar\n├── StatCard.vue # Icon + value + label card\n├── StatusBadge.vue # Status pill badge\n├── StatusDots.vue # Row of status dots\n└── TimeAgo.vue # Relative timestamp\n```","ui-composables":"# UI Composables\n\n# useCountUp\n\nA Vue Composition API composable that creates an animated counter that counts up to a target value over a specified duration.\n\n## Overview\n\n`useCountUp` provides a smooth, animated numeric counter that transitions from its current value to a target value. It uses `requestAnimationFrame` for 60fps animations and applies an ease-out cubic easing function for natural deceleration.\n\n## Function Signature\n\n```typescript\nfunction useCountUp(target: () => number, duration?: number): Ref<number>\n```\n\n## Parameters\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `target` | `() => number` | — | A getter function that returns the target number to count up to |\n| `duration` | `number` | `700` | Animation duration in milliseconds |\n\n## Return Value\n\nReturns a Vue `Ref<number>` containing the current animated value. This ref is reactive and can be used directly in templates or composed with other Vue features.\n\n## How It Works\n\n### Animation Algorithm\n\nThe composable uses a custom animation loop with the following characteristics:\n\n1. **Frame-based animation**: Uses `requestAnimationFrame` for smooth, browser-optimized animations\n2. **Ease-out cubic easing**: The formula `1 - Math.pow(1 - progress, 3)` creates a natural deceleration effect — fast at the start, slowing as it approaches the target\n3. **Linear interpolation**: Values are interpolated linearly between start and end, then eased\n\n### Reactivity\n\n- **On mount**: Automatically animates to the initial target value\n- **On target change**: Watches the `target` getter and re-triggers animation whenever the target value changes\n\nThis makes it ideal for dashboards where numbers update dynamically — the counter smoothly transitions to each new value.\n\n## Usage Examples\n\n### Basic Usage\n\n```vue\n<script setup>\nimport { useCountUp } from '@packages/ui/composables'\n\n// Target is a getter function\nconst score = useCountUp(() => 1500)\n\nsetTimeout(() => {\n // Target changes — counter automatically re-animates\n score.value = 2500\n}, 3000)\n<\/script>\n\n<template>\n <div>Score: {{ score }}</div>\n</template>\n```\n\n### With a Reactive Source\n\n```vue\n<script setup>\nimport { ref } from 'vue'\nimport { useCountUp } from '@packages/ui/composables'\n\nconst apiData = ref({ count: 0 })\n\n// Fetch data, then animate to the new value\nconst displayCount = useCountUp(() => apiData.value.count, 1000)\n\nasync function refreshData() {\n const result = await fetch('/api/count')\n apiData.value = await result.json()\n // Counter animates from current value to new apiData.count\n}\n<\/script>\n\n<template>\n <div class=\"stat-card\">\n <h3>Total Users</h3>\n <p class=\"count\">{{ displayCount }}</p>\n <button @click=\"refreshData\">Refresh</button>\n </div>\n</template>\n```\n\n### Custom Duration\n\n```vue\n<script setup>\nimport { useCountUp } from '@packages/ui/composables'\n\n// Slower animation for larger numbers\nconst largeNumber = useCountUp(() => 100000, 2000)\n<\/script>\n```\n\n## Integration Notes\n\n### When to Use This Composable\n\n- Dashboard metrics that update periodically\n- Statistics displays that need visual polish\n- Any numeric value that benefits from animated transitions\n\n### Behavior on Rapid Updates\n\nIf the target changes before the animation completes, the composable:\n1. Interrupts the current animation (no cleanup needed — RAF handles this)\n2. Starts a new animation from the current interpolated value to the new target\n\nThis provides a natural \"catch up\" behavior without jarring resets.\n\n### Server-Side Rendering (SSR)\n\nThis composable uses `onMounted`, so it only runs on the client. The initial render will show `0` (the ref's initial value). Consider showing a loading state or skeleton if the target value should be visible immediately on page load.","ui-pages-ui":"# UI Pages — ui\n\n# UI Pages Module Documentation\n\n## Overview\n\nThe **UI Pages** module (`packages/ui/src/pages/`) contains Vue.js page components that form the user interface of the Test Forge application. These components represent distinct routes in the application, each providing a specific view into test results, comparisons, and quality metrics.\n\nThe module uses:\n- **Vue 3 Composition API** with `<script setup>` syntax\n- **TypeScript** for type safety\n- **Vue Router** for navigation\n- **Chart.js** (via vue-chartjs) for data visualization\n- A shared **API client** for backend communication\n\n---\n\n## Architecture\n\nThe pages module follows a consistent pattern: each component manages its own loading state, data fetching, and rendering. Pages receive data from a shared API client (`../api/client`) and render it using scoped styles.\n\n```mermaid\nflowchart TB\n subgraph Pages[\"pages/\"]\n Dashboard[\"Dashboard.vue\"]\n Compare[\"Compare.vue\"]\n FlakyBoard[\"FlakyBoard.vue\"]\n Defects[\"Defects.vue\"]\n ErrorClusters[\"ErrorClusters.vue\"]\n Filters[\"Filters.vue\"]\n end\n \n subgraph Shared[\"Shared Dependencies\"]\n API[\"api/client\"]\n Router[\"vue-router\"]\n ChartJS[\"vue-chartjs\"]\n Composables[\"composables/\"]\n end\n \n Pages --> API\n Pages --> Router\n Pages --> ChartJS\n Dashboard --> Composables\n \n style Pages fill:#e1f5fe,stroke:#01579b\n style Shared fill:#f3e5f5,stroke:#4a148c\n```\n\n---\n\n## Page Components\n\n### Dashboard.vue\n\n**Purpose**: The main landing page providing an at-a-glance view of test health and trends.\n\n**Route**: `/` (default)\n\n**Key Features**:\n- **Stat Cards**: Four KPI cards showing Total Runs, Total Tests, Avg Pass Rate, and Avg Duration\n- **Insights Row**: BrowserStack-style key takeaways showing Stability Score, Always Failing tests, and New Failures\n- **Widget Grid**: Contains:\n - **Overall Statistics**: Doughnut chart showing passed/failed/skipped distribution\n - **Launch Statistics**: Stacked bar chart showing daily test trends\n - **Jump Back In**: Quick-access list of recent runs\n - **Component Health Check**: Table of projects with pass rates and status badges\n\n**State Management**:\n- `loading`: Controls skeleton display during fetch\n- `data`: Stores `DashboardResponse` from API\n- `insights`: Stores `InsightsResponse` for KPI row\n- Uses `useCountUp` composable for animated number transitions\n\n**User Interactions**:\n- Clicking Refresh re-fetches dashboard and insights data\n- Clicking donut chart segments navigates to filtered runs view\n- Clicking recent run rows navigates to run detail\n- Clicking project names navigates to filtered runs\n\n**Props**:\n```typescript\nprops: { days?: number } // defaults to 14, controls data range\n```\n\n---\n\n### Compare.vue\n\n**Purpose**: Allows side-by-side comparison of two build runs to identify what changed.\n\n**Route**: `/compare` (with `?base=<id>&head=<id>` query params)\n\n**Key Features**:\n- **Picker Mode**: When no params present, shows input fields for Base and Head run IDs\n- **Run Cards**: Two cards showing run metadata (ID, project, branch, commit, pass rate, duration)\n- **Delta Badges**: Visual indicators showing pass rate and duration changes\n- **Stats Bar**: Summary chips showing New Failures, Fixed, Still Failing, New Tests, Removed\n- **Tabbed Diff Table**: Categorized view of test differences with filtering tabs\n\n**State Management**:\n- `pickerBase` / `pickerHead`: Input values for run IDs\n- `hasParams`: Computed check for presence of URL query params\n- `activeTab`: Currently selected diff category\n- `activeRows`: Computed rows based on active tab\n\n**User Interactions**:\n- Entering run IDs and clicking Compare navigates to comparison view\n- Clicking tab buttons switches diff category\n- Clicking test rows navigates to individual test detail\n\n**Auto-selection Logic**: When loading a comparison, defaults to the first non-empty category (newFailures → fixed → stillFailing → newTests → removed)\n\n---\n\n### FlakyBoard.vue\n\n**Purpose**: Centralized view for identifying and managing flaky tests.\n\n**Route**: `/flaky`\n\n**Key Features**:\n- **Filter Bar**: Controls for period, partner, and stability threshold\n- **Flaky Tests Table**: Lists tests with flaky rate, pass rate, and failure count\n- **Batch Actions**: Select and mute multiple flaky tests\n- **Mute/Unmute**: Toggle muted status per test with optional duration\n- **Stability Indicators**: Visual badges showing test stability\n\n**State Management**:\n- `filters`: Reactive object with days, partner, filter query\n- `selectedIds`: Set of selected test IDs for batch operations\n- `batchMuting`: Loading state during batch operations\n\n**User Interactions**:\n- Changing filters triggers data refetch\n- Clicking test rows navigates to test detail\n- Mute button toggles test muted state\n- Batch mute button mutes all selected tests\n\n---\n\n### Defects.vue\n\n**Purpose**: Defect triage interface for categorizing test failure root causes.\n\n**Route**: `/defects`\n\n**Key Features**:\n- **KPI Cards**: Five category cards (Product Bug, Automation Bug, System Issue, To Investigate, No Defect) with counts and percentages\n- **Distribution Chart**: Doughnut chart showing defect category breakdown\n- **Recent Labeled Tests Table**: List of recently categorized failures with test title, project, area, category, labeler, and timestamp\n- **Project Filter**: Dropdown to filter by project\n\n**Category Configuration**:\n```typescript\nconst DEFECT_CATS = [\n { value: 'product_bug', label: 'Product Bug', color: '#EF4444' },\n { value: 'automation_bug', label: 'Automation Bug', color: '#F59E0B' },\n { value: 'system_issue', label: 'System Issue', color: '#8B5CF6' },\n { value: 'to_investigate', label: 'To Investigate', color: '#64748B' },\n { value: 'no_defect', label: 'No Defect', color: '#22C55E' },\n]\n```\n\n**State Management**:\n- `defectsData`: Stores defect distribution and recent items\n- `projects`: Available projects for filtering\n- `selectedProject`: Current project filter value\n\n---\n\n### ErrorClusters.vue\n\n**Purpose**: Groups similar test failures by error message to identify systemic issues.\n\n**Route**: `/errors`\n\n**Key Features**:\n- **Filter Bar**: Project slug and period filters\n- **Stat Cards**: Total Failures, Unique Error Patterns, Affected Runs\n- **Cluster Cards**: Expandable cards showing:\n - Error message (truncated in header, full in body)\n - Count of occurrences\n - Affected runs and tests count\n - First/last seen timestamps\n - Top areas and partners\n - Example test title with link to run\n\n**User Interactions**:\n- Clicking cluster header expands/collapses detail view\n- Filter inputs update on Enter key or button click\n\n**Empty State**: Shows success message when no failures found\n\n---\n\n### Filters.vue (Settings)\n\n**Purpose**: User preferences for default filters and display options.\n\n**Route**: `/settings`\n\n**Key Features**:\n- **Default Filters Section**:\n - Default time range (7/14/30/90 days)\n - Default page size (10/20/50/100 rows)\n - Default group-by in Run Detail\n\n- **Display Preferences Section**:\n - Show flaky rate badge toggle\n - Show retry chains expanded toggle\n - Show muted tests in Flaky Board toggle\n\n- **Quality Gate Defaults Section**:\n - Min pass rate\n - Max failures\n\n**State Management**:\n- Persists to `localStorage` under key `test-forge:settings`\n- Tracks dirty state to enable/disable Save button\n- Shows toast confirmation on save\n\n---\n\n## Shared Patterns\n\n### Loading States\n\nAll pages implement a consistent loading pattern:\n\n```vue\n<template v-if=\"loading\">\n <!-- Skeleton UI -->\n</template>\n<template v-else-if=\"error\">\n <!-- Error message -->\n</template>\n<template v-else-if=\"data\">\n <!-- Actual content -->\n</template>\n```\n\nSkeletons use CSS animations (`shimmer` or `skeleton-pulse`) and match the layout of loaded content to prevent layout shift.\n\n### Error Handling\n\nErrors are stored in a `ref<string | null>` and displayed in a consistent error box:\n\n```vue\n<div v-else-if=\"error\" class=\"error-box\" role=\"alert\">\n <svg class=\"error-icon\">...</svg>\n <span>{{ error }}</span>\n</div>\n```\n\n### API Integration\n\nAll pages import the shared API client:\n\n```typescript\nimport { api } from '../api/client'\n```\n\nThe client provides typed methods like:\n- `api.getDashboard(params)`\n- `api.getCompare(base, head)`\n- `api.getFlaky(params)`\n- `api.getDefects(params)`\n- `api.getErrors(params)`\n- `api.getInsights(params)`\n\n### Responsive Design\n\nAll pages include responsive breakpoints:\n\n```css\n@media (max-width: 1024px) { /* tablet landscape */ }\n@media (max-width: 768px) { /* tablet portrait / large phone */ }\n@media (max-width: 375px) { /* small phone */ }\n```\n\n### Reduced Motion\n\nAccessibility is supported via `prefers-reduced-motion` media queries that disable transitions and animations for users who prefer reduced motion.\n\n---\n\n## Type Definitions\n\nThe pages consume types from the API client:\n\n```typescript\nimport type {\n DashboardResponse,\n DashboardTrendPoint,\n InsightsResponse,\n CompareResponse,\n DefectsResponse,\n DefectCategory,\n ProjectStats,\n ErrorsResponse,\n FlakyResponse\n} from '../api/client'\n```\n\n---\n\n## Adding a New Page\n\nTo add a new page to the UI:\n\n1. **Create the component** in `packages/ui/src/pages/`:\n ```vue\n <script setup lang=\"ts\">\n // Imports, props, state\n <\/script>\n <template>\n <!-- Component template -->\n </template>\n <style scoped>\n /* Scoped styles */\n </style>\n ```\n\n2. **Register the route** in your router configuration:\n ```typescript\n { path: '/new-page', component: () => import('./pages/NewPage.vue') }\n ```\n\n3. **Add API method** if needed in `packages/ui/src/api/client.ts`\n\n4. **Follow existing patterns**:\n - Use the loading/error/data template pattern\n - Include skeleton loading states\n - Add responsive styles\n - Support reduced motion\n\n---\n\n## Dependencies\n\n| Package | Purpose |\n|---------|---------|\n| `vue` | Core framework |\n| `vue-router` | Navigation |\n| `vue-chartjs` | Chart.js Vue wrapper |\n| `chart.js` | Charting library |\n| `@vueuse/core` | Utility composables (useCountUp) |\n\n---\n\n## Key Design Decisions\n\n1. **Local state**: Each page manages its own state rather than using a global store, reducing complexity for these relatively simple use cases.\n\n2. **Scoped styles**: All styles are scoped to prevent conflicts between pages.\n\n3. **CSS custom properties**: Pages reference CSS variables (`--color-primary`, `--bg-card`, etc.) for theming consistency.\n\n4. **Component co-location**: Templates, logic, and styles are in the same file for easier maintenance and code review.\n\n5. **Skeleton-first loading**: Skeleton states match loaded layout to minimize perceived loading time.","ui-pages":"# UI Pages\n\n# UI Pages Module\n\nThe **UI Pages** module (`packages/ui/src/pages/`) provides the route-level components that form the user-facing application. Each page component represents a distinct URL route and orchestrates data fetching, state management, and visualization for its specific domain.\n\n## Purpose\n\nThis module serves as the presentation layer of the Test Forge application, translating backend test data into interactive, visual interfaces. Pages consume the shared [API Client](../api/client.md) to retrieve data, then render it using Vue components and Chart.js visualizations.\n\n## Sub-Modules\n\nThe module is organized around key application views:\n\n- **[Dashboard](dashboard.md)** — Overview of test execution status, recent runs, and key quality metrics\n- **[Test Results](test-results.md)** — Detailed view of individual test executions with logs and failure analysis\n- **[Comparison](comparison.md)** — Side-by-side diff view for comparing test outputs across runs or configurations\n- **[Metrics](metrics.md)** — Trend analysis and quality metrics visualized through Chart.js charts\n\n## Architecture\n\nAll pages follow a consistent pattern using Vue 3 Composition API with `<script setup>`:\n\n1. **Loading state** — Each page manages its own loading indicators during data fetch\n2. **Data fetching** — Pages call the shared API client to retrieve domain-specific data\n3. **Rendering** — Pages compose smaller components to display results, charts, or comparisons\n\nPages are registered with Vue Router and map directly to application routes. The shared API client ensures consistent request handling, authentication, and error management across all views.\n\n## Key Workflows\n\n**Navigation flow**: Users typically begin at the Dashboard, drill into specific test results, and may invoke comparison views to analyze changes. The Metrics page provides historical context for quality trends.\n\n**Data flow**: All pages follow `route param → API call → state update → render` — no cross-page state sharing occurs; each page is self-contained.\n\n```mermaid\nflowchart LR\n A[Dashboard] --> B[Test Results]\n A --> D[Metrics]\n B --> C[Comparison]\n D --> C\n```\n\n## Dependencies\n\n- **Vue 3** — Component framework\n- **Vue Router** — Route handling\n- **vue-chartjs** — Chart rendering\n- **API Client** — Backend communication (shared module)","ui-theme":"# UI Theme\n\n# UI Theme Module\n\nThe UI Theme module provides Test Forge's design system — a comprehensive set of CSS design tokens and component styles that establish the visual language for the entire application. This module follows a B2B analytics dashboard aesthetic inspired by BrowserStack, optimized for real-time monitoring interfaces.\n\n## Overview\n\nThe theme module consists of two CSS files:\n\n| File | Purpose |\n|------|---------|\n| `catppuccin.css` | Design tokens — CSS custom properties for colors, spacing, typography, shadows, and other foundational values |\n| `forge-ui.css` | Component library — pre-built UI primitives (buttons, cards, forms, tables, badges) |\n\nThese files are imported globally and provide styling for all pages in the application. The design system supports both light and dark themes via the `[data-theme=\"dark\"]` attribute selector.\n\n---\n\n## Design Tokens (`catppuccin.css`)\n\nThe token file defines a comprehensive design system using CSS custom properties. These tokens are the single source of truth for all visual styling.\n\n### Color System\n\nThe color palette is organized into semantic groups:\n\n```css\n/* Brand / Primary — Orange (#F97316) */\n--color-primary: #F97316;\n--color-primary-dark: #EA6C10;\n--color-primary-hover: #FB923C;\n\n/* Accent blue (data / links) */\n--color-blue: #3B82F6;\n--color-blue-dark: #2563EB;\n\n/* Status colors for test results */\n--color-passed: #22C55E;\n--color-failed: #EF4444;\n--color-flaky: #F59E0B;\n--color-skipped: #94A3B8;\n--color-running: #8B5CF6;\n--color-blocked: #64748B;\n```\n\nEach status color has associated background, border, and text variants:\n\n```css\n--color-passed-bg: rgba(34, 197, 94, 0.09);\n--color-passed-border: rgba(34, 197, 94, 0.3);\n--color-passed-text: #15803D;\n```\n\n### Surface Colors\n\nSurfaces define the background layers of the UI:\n\n```css\n--bg-canvas: #F8FAFC; /* page background */\n--bg-card: #FFFFFF; /* card / panel */\n--bg-sidebar: #FFFFFF; /* secondary nav */\n--bg-nav: #0F172A; /* top nav */\n--bg-input: #F8FAFC;\n```\n\n### Typography\n\nThe system uses **Inter** as the primary typeface with a carefully calibrated scale:\n\n```css\n--font-size-xs: 11px;\n--font-size-sm: 12px;\n--font-size-base: 13px;\n--font-size-md: 14px;\n--font-size-lg: 16px;\n--font-size-xl: 18px;\n--font-size-2xl: 20px;\n--font-size-3xl: 24px;\n--font-size-4xl: 30px;\n\n--fw-light: 300;\n--fw-normal: 400;\n--fw-medium: 500;\n--fw-semibold: 600;\n--fw-bold: 700;\n```\n\n### Spacing Scale\n\nA 4px base grid with consistent multipliers:\n\n```css\n--space-1: 4px;\n--space-2: 8px;\n--space-3: 12px;\n--space-4: 16px;\n--space-5: 20px;\n--space-6: 24px;\n--space-8: 32px;\n/* ... through --space-20: 80px */\n```\n\n### Component Heights\n\nStandardized heights ensure visual consistency:\n\n```css\n--h-btn-sm: 28px;\n--h-btn: 32px;\n--h-btn-lg: 40px;\n--h-input: 32px;\n--h-nav: 48px;\n--h-sidebar: 64px;\n--h-table-th: 36px;\n--h-table-tr: 42px;\n```\n\n### Z-Index Scale\n\nA deliberate layering system prevents z-index conflicts:\n\n```css\n--z-base: 1;\n--z-above: 10;\n--z-dropdown: 200;\n--z-sticky: 300;\n--z-nav: 400;\n--z-modal: 500;\n--z-toast: 600;\n```\n\n### Shadows\n\nThree elevation levels for different contexts:\n\n```css\n--shadow-card: 0 1px 3px rgba(0,0,0,0.07), 0 1px 2px rgba(0,0,0,0.04);\n--shadow-card-hover: 0 4px 12px rgba(0,0,0,0.11), 0 2px 4px rgba(0,0,0,0.06);\n--shadow-dropdown: 0 8px 24px rgba(0,0,0,0.12), 0 2px 6px rgba(0,0,0,0.06);\n--shadow-modal: 0 20px 60px rgba(0,0,0,0.18);\n```\n\n### Transitions\n\nConsistent timing functions for smooth interactions:\n\n```css\n--transition-fast: 0.12s ease;\n--transition-base: 0.18s ease;\n--transition-slow: 0.3s ease;\n--transition-spring: 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);\n```\n\n---\n\n## Component Library (`forge-ui.css`)\n\nThe component file provides ready-to-use UI primitives. These classes are available globally so pages don't need to redefine common patterns.\n\n### Buttons\n\nFour button variants cover common use cases:\n\n```html\n<button class=\"btn-primary\">Primary Action</button>\n<button class=\"btn-secondary\">Secondary</button>\n<button class=\"btn-danger\">Delete</button>\n<button class=\"btn-ghost\">Ghost</button>\n```\n\n| Class | Use Case |\n|-------|----------|\n| `.btn-primary` | Main CTAs, submit actions |\n| `.btn-secondary` | Alternative actions, forms |\n| `.btn-danger` | Destructive actions with confirmation |\n| `.btn-ghost` | Tertiary actions, toolbar items |\n\nSmall variant: `.btn-sm` for compact contexts.\n\n### Forms\n\nStandardized form inputs with consistent styling:\n\n```html\n<input class=\"tf-input\" type=\"text\" placeholder=\"Search...\">\n<select class=\"tf-select\">...</select>\n<textarea class=\"tf-textarea\">...</textarea>\n```\n\nThe filter bar component combines inputs with an apply button:\n\n```html\n<div class=\"filter-bar\">\n <div class=\"filter-group\">\n <label class=\"filter-label\">Status</label>\n <select class=\"filter-input filter-select\">...</select>\n </div>\n <button class=\"apply-btn\">Apply</button>\n</div>\n```\n\n### Status Indicators\n\n**Badges** — For inline status labels:\n\n```html\n<span class=\"badge badge-passed\">Passed</span>\n<span class=\"badge badge-failed\">Failed</span>\n<span class=\"badge badge-flaky\">Flaky</span>\n<span class=\"badge badge-skipped\">Skipped</span>\n<span class=\"badge badge-running\">Running</span>\n<span class=\"badge badge-blocked\">Blocked</span>\n```\n\n**Status dots** — For compact indicators in tables and lists:\n\n```html\n<span class=\"status-dot status-dot--passed\"></span>\n<span class=\"status-dot status-dot--failed\"></span>\n<span class=\"status-dot status-dot--running\"></span>\n```\n\nThe running state includes a pulsing animation:\n\n```css\n.status-dot--running {\n animation: dot-pulse 1.8s ease-in-out infinite;\n}\n```\n\n### Cards\n\nGeneric container with optional hover state:\n\n```html\n<div class=\"card\">Content</div>\n<div class=\"card card--xl\">Large card</div>\n<div class=\"card card--hoverable\">Hoverable card</div>\n```\n\n### Tables\n\nStandardized table styling via `.tf-table`:\n\n```html\n<table class=\"tf-table\">\n <thead>\n <tr><th>Test</th><th>Status</th></tr>\n </thead>\n <tbody>\n <tr><td>Login</td><td><span class=\"badge badge-passed\">Passed</span></td></tr>\n </tbody>\n</table>\n```\n\n### Page Layout\n\nHeader components for page-level titles:\n\n```html\n<div class=\"page-header\">\n <div class=\"page-header-left\">\n <h1 class=\"page-title\">Test Runs</h1>\n <span class=\"total-badge\">1,234 runs</span>\n </div>\n <div class=\"page-header-right\">\n <button class=\"btn-primary\">New Run</button>\n </div>\n</div>\n```\n\n### Loading States\n\n**Skeleton loader** — For content that loads asynchronously:\n\n```html\n<div class=\"skeleton\" style=\"width: 200px; height: 20px;\"></div>\n```\n\n**Loading text** — Simple centered loading message:\n\n```html\n<div class=\"loading\">Loading tests...</div>\n```\n\n### Empty States\n\nConsistent empty state presentation:\n\n```html\n<div class=\"empty-state\">\n <svg class=\"empty-icon\">...</svg>\n <h3 class=\"empty-title\">No tests found</h3>\n <p class=\"empty-desc\">Create your first test to get started.</p>\n</div>\n```\n\n### Error Display\n\nStyled error boxes for form validation and API errors:\n\n```html\n<div class=\"error-box\">\n <svg class=\"error-icon\">⚠</svg>\n <span>Failed to load test results</span>\n</div>\n```\n\n---\n\n## Dark Mode\n\nThe theme supports dark mode through attribute-based styling. Apply `data-theme=\"dark\"` to the `<html>` or root element:\n\n```html\n<html data-theme=\"dark\">\n```\n\nDark mode redefines all tokens within the `[data-theme=\"dark\"]` selector. Key transformations:\n\n- **Surfaces** shift to dark backgrounds (`#0B0E17`, `#141824`)\n- **Text** lightens for contrast (`#F1F5F9` primary, `#94A3B8` secondary)\n- **Status backgrounds** increase opacity for visibility\n- **Borders** become subtle (`rgba(255,255,255,0.07)`)\n\nThe implementation preserves the same token names — components automatically adapt by referencing the same CSS custom properties, which resolve to different values based on the theme attribute.\n\n---\n\n## Accessibility\n\nThe design system includes several accessibility considerations:\n\n### Focus Management\n\nAll interactive elements include visible focus states:\n\n```css\n--focus-ring: 0 0 0 3px rgba(249, 115, 22, 0.22);\n```\n\nButtons and inputs use this token for `:focus-visible` states.\n\n### Reduced Motion\n\nUsers who prefer reduced motion receive simplified animations:\n\n```css\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: 0.01ms !important;\n transition-duration: 0.01ms !important;\n }\n}\n```\n\n### Screen Reader Support\n\nThe `.sr-only` class provides visually hidden but screen-reader-accessible content:\n\n```css\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n margin: -1px;\n overflow: hidden;\n clip: rect(0,0,0,0);\n white-space: nowrap;\n border: 0;\n}\n```\n\n### Semantic HTML\n\nThe component classes work with native HTML elements (`<button>`, `<input>`, `<table>`, etc.) rather than replacing them, preserving semantic meaning and browser defaults.\n\n---\n\n## Animations\n\nThe module provides several reusable animation utilities:\n\n### Page Transitions\n\n```css\n.page-enter-active { transition: opacity 0.18s ease, transform 0.18s ease; }\n.page-enter-from { opacity: 0; transform: translateY(10px); }\n```\n\n### Staggered Entrance\n\nGrid children can stagger their entrance:\n\n```html\n<div class=\"stagger\">\n <div>Item 1</div>\n <div>Item 2</div>\n <div>Item 3</div>\n</div>\n```\n\n### Progress Bars\n\nAnimated progress indicators:\n\n```html\n<div class=\"progress-track\">\n <div class=\"progress-fill progress-fill--passed\" style=\"width: 75%;\"></div>\n</div>\n```\n\n---\n\n## Usage Guidelines\n\n### Applying Tokens\n\nReference tokens directly in component stylesheets:\n\n```css\n.my-component {\n background: var(--bg-card);\n border: 1px solid var(--border-default);\n padding: var(--space-4);\n border-radius: var(--radius-lg);\n}\n```\n\n### Using Components\n\nAdd component classes to HTML elements. Combine with tokens for custom variations:\n\n```html\n<div class=\"card\" style=\"padding: var(--space-6);\">\n <h2 class=\"page-title\">Dashboard</h2>\n</div>\n```\n\n### Theme Switching\n\nToggle dark mode by setting the `data-theme` attribute:\n\n```javascript\n// Enable dark mode\ndocument.documentElement.setAttribute('data-theme', 'dark');\n\n// Enable light mode\ndocument.documentElement.setAttribute('data-theme', 'light');\n```\n\n---\n\n## Legacy Aliases\n\nThe token file includes backward-compatible aliases for older code:\n\n```css\n--base: var(--bg-canvas);\n--mantle: var(--bg-sidebar);\n--surface0: #F1F3F5;\n--green: var(--color-passed);\n--red: var(--color-failed);\n--yellow: var(--color-flaky);\n```\n\nThese aliases allow gradual migration of older components without requiring immediate rewrites."};
var TREE = [{"name":"Project Configuration","slug":"project-configuration","files":["package.json","tsconfig.base.json","Dockerfile","docker-compose.yml","docker-entrypoint.sh"]},{"name":"Documentation","slug":"documentation","files":["README.md","AGENTS.md","CLAUDE.md","openspec/AGENTS.md","openspec/project.md"]},{"name":"Design System","slug":"design-system","files":["design-system/test-forge/MASTER.md"]},{"name":"API Server","slug":"api-server","files":["packages/api/src/server.ts","packages/api/src/lib/config.ts","packages/api/drizzle.config.ts","packages/api/tsconfig.json","packages/api/package.json"]},{"name":"Database Layer","slug":"database-layer","files":["packages/api/src/db/connection.ts","packages/api/src/db/migrate.ts","packages/api/src/db/schema.ts"]},{"name":"Background Jobs","slug":"background-jobs","files":["packages/api/src/jobs/boss.ts","packages/api/src/jobs/cleanup.ts","packages/api/src/jobs/notify-teams.ts","packages/api/src/jobs/process-run.ts","packages/api/src/jobs/refresh-views.ts"]},{"name":"API Routes","slug":"api-routes","files":["packages/api/src/routes/compare.ts","packages/api/src/routes/dashboard.ts","packages/api/src/routes/defects.ts","packages/api/src/routes/errors.ts","packages/api/src/routes/flaky.ts","packages/api/src/routes/gate.ts","packages/api/src/routes/health.ts","packages/api/src/routes/insights.ts","packages/api/src/routes/mute.ts","packages/api/src/routes/perf.ts","packages/api/src/routes/projects.ts","packages/api/src/routes/runs.ts","packages/api/src/routes/tests.ts","packages/api/src/routes/trends.ts"]},{"name":"API Scripts","slug":"api-scripts","files":["packages/api/scripts/seed.ts","packages/api/scripts/smoke-test.ts"]},{"name":"API Static Assets","slug":"api-static-assets","files":[],"children":[{"name":"API Static Assets — api","slug":"api-static-assets-api","files":["packages/api/public/index.html","packages/api/public/assets/Compare-CbhWSnL8.js","packages/api/public/assets/Compare-DAn--CuK.css","packages/api/public/assets/Dashboard-B0ZGbeJa.css","packages/api/public/assets/Dashboard-sEYnoQhy.js","packages/api/public/assets/Defects-CPgQea7Q.js","packages/api/public/assets/Defects-Chw-njhK.css","packages/api/public/assets/ErrorClusters-CFhhyPrN.css","packages/api/public/assets/ErrorClusters-Dn9tgi2e.js","packages/api/public/assets/Filters-1ffhhDIj.js","packages/api/public/assets/Filters-7ZOJWjxe.css","packages/api/public/assets/FlakyBoard-BU2DqdNV.js","packages/api/public/assets/FlakyBoard-ClzYxg5z.css","packages/api/public/assets/MutedTests--4SJ_KsG.css","packages/api/public/assets/MutedTests-BriOG4w7.js","packages/api/public/assets/Performance-B8VnGrEq.css","packages/api/public/assets/Performance-DSzQYkZv.js","packages/api/public/assets/Projects-DygOODRl.css","packages/api/public/assets/Projects-I3BySr80.js","packages/api/public/assets/RunDetail-Bh8KLgVl.css","packages/api/public/assets/RunDetail-CLNK9km-.js","packages/api/public/assets/RunList-BbD_ezAK.css","packages/api/public/assets/RunList-caF7vWNh.js","packages/api/public/assets/TestCases-2SyRJw3_.js","packages/api/public/assets/TestCases-eY7nE7pZ.css","packages/api/public/assets/TestHistory-BnQSU-Ku.css","packages/api/public/assets/TestHistory-Q27SZqw5.js","packages/api/public/assets/Trends-BqQhH-iL.js","packages/api/public/assets/Trends-OoDkPmHM.css","packages/api/public/assets/client-BtmuWjQn.js","packages/api/public/assets/index-BUpJ6Hkz.js","packages/api/public/assets/index-DjJN5Y-u.js","packages/api/public/assets/index-eKnenK2P.css"]}]},{"name":"CLI Core","slug":"cli-core","files":["packages/cli/src/index.ts","packages/cli/src/extract.ts","packages/cli/src/parse.ts","packages/cli/src/push.ts","packages/cli/tsconfig.json","packages/cli/package.json"]},{"name":"Test Result Adapters","slug":"test-result-adapters","files":["packages/cli/src/adapters/k6.ts","packages/cli/src/adapters/playwright.ts"]},{"name":"Frontend Core","slug":"frontend-core","files":["packages/ui/src/App.vue","packages/ui/src/main.ts","packages/ui/index.html","packages/ui/package.json","packages/ui/tsconfig.json","packages/ui/vite.config.ts"]},{"name":"UI API Client","slug":"ui-api-client","files":["packages/ui/src/api/client.ts"]},{"name":"UI Components","slug":"ui-components","files":["packages/ui/src/components/ArtifactViewer.vue","packages/ui/src/components/PassRateBar.vue","packages/ui/src/components/StatCard.vue","packages/ui/src/components/StatusBadge.vue","packages/ui/src/components/StatusDots.vue","packages/ui/src/components/TimeAgo.vue"]},{"name":"UI Composables","slug":"ui-composables","files":["packages/ui/src/composables/useCountUp.ts"]},{"name":"UI Pages","slug":"ui-pages","files":[],"children":[{"name":"UI Pages — ui","slug":"ui-pages-ui","files":["packages/ui/src/pages/Compare.vue","packages/ui/src/pages/Dashboard.vue","packages/ui/src/pages/Defects.vue","packages/ui/src/pages/ErrorClusters.vue","packages/ui/src/pages/Filters.vue","packages/ui/src/pages/FlakyBoard.vue","packages/ui/src/pages/MutedTests.vue","packages/ui/src/pages/Performance.vue","packages/ui/src/pages/Projects.vue","packages/ui/src/pages/RunDetail.vue","packages/ui/src/pages/RunList.vue","packages/ui/src/pages/TestCases.vue","packages/ui/src/pages/TestHistory.vue","packages/ui/src/pages/Trends.vue"]}]},{"name":"UI Theme","slug":"ui-theme","files":["packages/ui/src/theme/catppuccin.css","packages/ui/src/theme/forge-ui.css"]}];
var META = {"fromCommit":"d751fecefb80111a48cf239f482beae4eb163c2e","generatedAt":"2026-03-12T19:25:36.102Z","model":"minimax/minimax-m2.5","moduleFiles":{"Project Configuration":["package.json","tsconfig.base.json","Dockerfile","docker-compose.yml","docker-entrypoint.sh"],"Documentation":["README.md","AGENTS.md","CLAUDE.md","openspec/AGENTS.md","openspec/project.md"],"Design System":["design-system/test-forge/MASTER.md"],"API Server":["packages/api/src/server.ts","packages/api/src/lib/config.ts","packages/api/drizzle.config.ts","packages/api/tsconfig.json","packages/api/package.json"],"Database Layer":["packages/api/src/db/connection.ts","packages/api/src/db/migrate.ts","packages/api/src/db/schema.ts"],"Background Jobs":["packages/api/src/jobs/boss.ts","packages/api/src/jobs/cleanup.ts","packages/api/src/jobs/notify-teams.ts","packages/api/src/jobs/process-run.ts","packages/api/src/jobs/refresh-views.ts"],"API Routes":["packages/api/src/routes/compare.ts","packages/api/src/routes/dashboard.ts","packages/api/src/routes/defects.ts","packages/api/src/routes/errors.ts","packages/api/src/routes/flaky.ts","packages/api/src/routes/gate.ts","packages/api/src/routes/health.ts","packages/api/src/routes/insights.ts","packages/api/src/routes/mute.ts","packages/api/src/routes/perf.ts","packages/api/src/routes/projects.ts","packages/api/src/routes/runs.ts","packages/api/src/routes/tests.ts","packages/api/src/routes/trends.ts"],"API Scripts":["packages/api/scripts/seed.ts","packages/api/scripts/smoke-test.ts"],"API Static Assets":["packages/api/public/index.html","packages/api/public/assets/Compare-CbhWSnL8.js","packages/api/public/assets/Compare-DAn--CuK.css","packages/api/public/assets/Dashboard-B0ZGbeJa.css","packages/api/public/assets/Dashboard-sEYnoQhy.js","packages/api/public/assets/Defects-CPgQea7Q.js","packages/api/public/assets/Defects-Chw-njhK.css","packages/api/public/assets/ErrorClusters-CFhhyPrN.css","packages/api/public/assets/ErrorClusters-Dn9tgi2e.js","packages/api/public/assets/Filters-1ffhhDIj.js","packages/api/public/assets/Filters-7ZOJWjxe.css","packages/api/public/assets/FlakyBoard-BU2DqdNV.js","packages/api/public/assets/FlakyBoard-ClzYxg5z.css","packages/api/public/assets/MutedTests--4SJ_KsG.css","packages/api/public/assets/MutedTests-BriOG4w7.js","packages/api/public/assets/Performance-B8VnGrEq.css","packages/api/public/assets/Performance-DSzQYkZv.js","packages/api/public/assets/Projects-DygOODRl.css","packages/api/public/assets/Projects-I3BySr80.js","packages/api/public/assets/RunDetail-Bh8KLgVl.css","packages/api/public/assets/RunDetail-CLNK9km-.js","packages/api/public/assets/RunList-BbD_ezAK.css","packages/api/public/assets/RunList-caF7vWNh.js","packages/api/public/assets/TestCases-2SyRJw3_.js","packages/api/public/assets/TestCases-eY7nE7pZ.css","packages/api/public/assets/TestHistory-BnQSU-Ku.css","packages/api/public/assets/TestHistory-Q27SZqw5.js","packages/api/public/assets/Trends-BqQhH-iL.js","packages/api/public/assets/Trends-OoDkPmHM.css","packages/api/public/assets/client-BtmuWjQn.js","packages/api/public/assets/index-BUpJ6Hkz.js","packages/api/public/assets/index-DjJN5Y-u.js","packages/api/public/assets/index-eKnenK2P.css"],"API Static Assets — api":["packages/api/public/index.html","packages/api/public/assets/Compare-CbhWSnL8.js","packages/api/public/assets/Compare-DAn--CuK.css","packages/api/public/assets/Dashboard-B0ZGbeJa.css","packages/api/public/assets/Dashboard-sEYnoQhy.js","packages/api/public/assets/Defects-CPgQea7Q.js","packages/api/public/assets/Defects-Chw-njhK.css","packages/api/public/assets/ErrorClusters-CFhhyPrN.css","packages/api/public/assets/ErrorClusters-Dn9tgi2e.js","packages/api/public/assets/Filters-1ffhhDIj.js","packages/api/public/assets/Filters-7ZOJWjxe.css","packages/api/public/assets/FlakyBoard-BU2DqdNV.js","packages/api/public/assets/FlakyBoard-ClzYxg5z.css","packages/api/public/assets/MutedTests--4SJ_KsG.css","packages/api/public/assets/MutedTests-BriOG4w7.js","packages/api/public/assets/Performance-B8VnGrEq.css","packages/api/public/assets/Performance-DSzQYkZv.js","packages/api/public/assets/Projects-DygOODRl.css","packages/api/public/assets/Projects-I3BySr80.js","packages/api/public/assets/RunDetail-Bh8KLgVl.css","packages/api/public/assets/RunDetail-CLNK9km-.js","packages/api/public/assets/RunList-BbD_ezAK.css","packages/api/public/assets/RunList-caF7vWNh.js","packages/api/public/assets/TestCases-2SyRJw3_.js","packages/api/public/assets/TestCases-eY7nE7pZ.css","packages/api/public/assets/TestHistory-BnQSU-Ku.css","packages/api/public/assets/TestHistory-Q27SZqw5.js","packages/api/public/assets/Trends-BqQhH-iL.js","packages/api/public/assets/Trends-OoDkPmHM.css","packages/api/public/assets/client-BtmuWjQn.js","packages/api/public/assets/index-BUpJ6Hkz.js","packages/api/public/assets/index-DjJN5Y-u.js","packages/api/public/assets/index-eKnenK2P.css"],"CLI Core":["packages/cli/src/index.ts","packages/cli/src/extract.ts","packages/cli/src/parse.ts","packages/cli/src/push.ts","packages/cli/tsconfig.json","packages/cli/package.json"],"Test Result Adapters":["packages/cli/src/adapters/k6.ts","packages/cli/src/adapters/playwright.ts"],"Frontend Core":["packages/ui/src/App.vue","packages/ui/src/main.ts","packages/ui/index.html","packages/ui/package.json","packages/ui/tsconfig.json","packages/ui/vite.config.ts"],"UI API Client":["packages/ui/src/api/client.ts"],"UI Components":["packages/ui/src/components/ArtifactViewer.vue","packages/ui/src/components/PassRateBar.vue","packages/ui/src/components/StatCard.vue","packages/ui/src/components/StatusBadge.vue","packages/ui/src/components/StatusDots.vue","packages/ui/src/components/TimeAgo.vue"],"UI Composables":["packages/ui/src/composables/useCountUp.ts"],"UI Pages":["packages/ui/src/pages/Compare.vue","packages/ui/src/pages/Dashboard.vue","packages/ui/src/pages/Defects.vue","packages/ui/src/pages/ErrorClusters.vue","packages/ui/src/pages/Filters.vue","packages/ui/src/pages/FlakyBoard.vue","packages/ui/src/pages/MutedTests.vue","packages/ui/src/pages/Performance.vue","packages/ui/src/pages/Projects.vue","packages/ui/src/pages/RunDetail.vue","packages/ui/src/pages/RunList.vue","packages/ui/src/pages/TestCases.vue","packages/ui/src/pages/TestHistory.vue","packages/ui/src/pages/Trends.vue"],"UI Pages — ui":["packages/ui/src/pages/Compare.vue","packages/ui/src/pages/Dashboard.vue","packages/ui/src/pages/Defects.vue","packages/ui/src/pages/ErrorClusters.vue","packages/ui/src/pages/Filters.vue","packages/ui/src/pages/FlakyBoard.vue","packages/ui/src/pages/MutedTests.vue","packages/ui/src/pages/Performance.vue","packages/ui/src/pages/Projects.vue","packages/ui/src/pages/RunDetail.vue","packages/ui/src/pages/RunList.vue","packages/ui/src/pages/TestCases.vue","packages/ui/src/pages/TestHistory.vue","packages/ui/src/pages/Trends.vue"],"UI Theme":["packages/ui/src/theme/catppuccin.css","packages/ui/src/theme/forge-ui.css"]},"moduleTree":[{"name":"Project Configuration","slug":"project-configuration","files":["package.json","tsconfig.base.json","Dockerfile","docker-compose.yml","docker-entrypoint.sh"]},{"name":"Documentation","slug":"documentation","files":["README.md","AGENTS.md","CLAUDE.md","openspec/AGENTS.md","openspec/project.md"]},{"name":"Design System","slug":"design-system","files":["design-system/test-forge/MASTER.md"]},{"name":"API Server","slug":"api-server","files":["packages/api/src/server.ts","packages/api/src/lib/config.ts","packages/api/drizzle.config.ts","packages/api/tsconfig.json","packages/api/package.json"]},{"name":"Database Layer","slug":"database-layer","files":["packages/api/src/db/connection.ts","packages/api/src/db/migrate.ts","packages/api/src/db/schema.ts"]},{"name":"Background Jobs","slug":"background-jobs","files":["packages/api/src/jobs/boss.ts","packages/api/src/jobs/cleanup.ts","packages/api/src/jobs/notify-teams.ts","packages/api/src/jobs/process-run.ts","packages/api/src/jobs/refresh-views.ts"]},{"name":"API Routes","slug":"api-routes","files":["packages/api/src/routes/compare.ts","packages/api/src/routes/dashboard.ts","packages/api/src/routes/defects.ts","packages/api/src/routes/errors.ts","packages/api/src/routes/flaky.ts","packages/api/src/routes/gate.ts","packages/api/src/routes/health.ts","packages/api/src/routes/insights.ts","packages/api/src/routes/mute.ts","packages/api/src/routes/perf.ts","packages/api/src/routes/projects.ts","packages/api/src/routes/runs.ts","packages/api/src/routes/tests.ts","packages/api/src/routes/trends.ts"]},{"name":"API Scripts","slug":"api-scripts","files":["packages/api/scripts/seed.ts","packages/api/scripts/smoke-test.ts"]},{"name":"API Static Assets","slug":"api-static-assets","files":[],"children":[{"name":"API Static Assets — api","slug":"api-static-assets-api","files":["packages/api/public/index.html","packages/api/public/assets/Compare-CbhWSnL8.js","packages/api/public/assets/Compare-DAn--CuK.css","packages/api/public/assets/Dashboard-B0ZGbeJa.css","packages/api/public/assets/Dashboard-sEYnoQhy.js","packages/api/public/assets/Defects-CPgQea7Q.js","packages/api/public/assets/Defects-Chw-njhK.css","packages/api/public/assets/ErrorClusters-CFhhyPrN.css","packages/api/public/assets/ErrorClusters-Dn9tgi2e.js","packages/api/public/assets/Filters-1ffhhDIj.js","packages/api/public/assets/Filters-7ZOJWjxe.css","packages/api/public/assets/FlakyBoard-BU2DqdNV.js","packages/api/public/assets/FlakyBoard-ClzYxg5z.css","packages/api/public/assets/MutedTests--4SJ_KsG.css","packages/api/public/assets/MutedTests-BriOG4w7.js","packages/api/public/assets/Performance-B8VnGrEq.css","packages/api/public/assets/Performance-DSzQYkZv.js","packages/api/public/assets/Projects-DygOODRl.css","packages/api/public/assets/Projects-I3BySr80.js","packages/api/public/assets/RunDetail-Bh8KLgVl.css","packages/api/public/assets/RunDetail-CLNK9km-.js","packages/api/public/assets/RunList-BbD_ezAK.css","packages/api/public/assets/RunList-caF7vWNh.js","packages/api/public/assets/TestCases-2SyRJw3_.js","packages/api/public/assets/TestCases-eY7nE7pZ.css","packages/api/public/assets/TestHistory-BnQSU-Ku.css","packages/api/public/assets/TestHistory-Q27SZqw5.js","packages/api/public/assets/Trends-BqQhH-iL.js","packages/api/public/assets/Trends-OoDkPmHM.css","packages/api/public/assets/client-BtmuWjQn.js","packages/api/public/assets/index-BUpJ6Hkz.js","packages/api/public/assets/index-DjJN5Y-u.js","packages/api/public/assets/index-eKnenK2P.css"]}]},{"name":"CLI Core","slug":"cli-core","files":["packages/cli/src/index.ts","packages/cli/src/extract.ts","packages/cli/src/parse.ts","packages/cli/src/push.ts","packages/cli/tsconfig.json","packages/cli/package.json"]},{"name":"Test Result Adapters","slug":"test-result-adapters","files":["packages/cli/src/adapters/k6.ts","packages/cli/src/adapters/playwright.ts"]},{"name":"Frontend Core","slug":"frontend-core","files":["packages/ui/src/App.vue","packages/ui/src/main.ts","packages/ui/index.html","packages/ui/package.json","packages/ui/tsconfig.json","packages/ui/vite.config.ts"]},{"name":"UI API Client","slug":"ui-api-client","files":["packages/ui/src/api/client.ts"]},{"name":"UI Components","slug":"ui-components","files":["packages/ui/src/components/ArtifactViewer.vue","packages/ui/src/components/PassRateBar.vue","packages/ui/src/components/StatCard.vue","packages/ui/src/components/StatusBadge.vue","packages/ui/src/components/StatusDots.vue","packages/ui/src/components/TimeAgo.vue"]},{"name":"UI Composables","slug":"ui-composables","files":["packages/ui/src/composables/useCountUp.ts"]},{"name":"UI Pages","slug":"ui-pages","files":[],"children":[{"name":"UI Pages — ui","slug":"ui-pages-ui","files":["packages/ui/src/pages/Compare.vue","packages/ui/src/pages/Dashboard.vue","packages/ui/src/pages/Defects.vue","packages/ui/src/pages/ErrorClusters.vue","packages/ui/src/pages/Filters.vue","packages/ui/src/pages/FlakyBoard.vue","packages/ui/src/pages/MutedTests.vue","packages/ui/src/pages/Performance.vue","packages/ui/src/pages/Projects.vue","packages/ui/src/pages/RunDetail.vue","packages/ui/src/pages/RunList.vue","packages/ui/src/pages/TestCases.vue","packages/ui/src/pages/TestHistory.vue","packages/ui/src/pages/Trends.vue"]}]},{"name":"UI Theme","slug":"ui-theme","files":["packages/ui/src/theme/catppuccin.css","packages/ui/src/theme/forge-ui.css"]}]};
(function() {
var activePage = 'overview';
document.addEventListener('DOMContentLoaded', function() {
mermaid.initialize({ startOnLoad: false, theme: 'neutral', securityLevel: 'loose' });
renderMeta();
renderNav();
document.getElementById('menu-toggle').addEventListener('click', function() {
document.getElementById('sidebar').classList.toggle('open');
});
if (location.hash && location.hash.length > 1) {
activePage = decodeURIComponent(location.hash.slice(1));
}
navigateTo(activePage);
});
function renderMeta() {
if (!META) return;
var el = document.getElementById('meta-info');
var parts = [];
if (META.generatedAt) {
parts.push(new Date(META.generatedAt).toLocaleDateString());
}
if (META.model) parts.push(META.model);
if (META.fromCommit) parts.push(META.fromCommit.slice(0, 8));
el.textContent = parts.join(' \u00b7 ');
}
function renderNav() {
var container = document.getElementById('nav-tree');
var html = '<div class="nav-section">';
html += '<a class="nav-item overview" data-page="overview" href="#overview">Overview</a>';
html += '</div>';
if (TREE.length > 0) {
html += '<div class="nav-group-label">Modules</div>';
html += buildNavTree(TREE);
}
container.innerHTML = html;
container.addEventListener('click', function(e) {
var target = e.target;
while (target && !target.dataset.page) { target = target.parentElement; }
if (target && target.dataset.page) {
e.preventDefault();
navigateTo(target.dataset.page);
}
});
}
function buildNavTree(nodes) {
var html = '';
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
html += '<div class="nav-section">';
html += '<a class="nav-item" data-page="' + escH(node.slug) + '" href="#' + encodeURIComponent(node.slug) + '">' + escH(node.name) + '</a>';
if (node.children && node.children.length > 0) {
html += '<div class="nav-children">' + buildNavTree(node.children) + '</div>';
}
html += '</div>';
}
return html;
}
function escH(s) {
var d = document.createElement('div');
d.textContent = s;
return d.innerHTML;
}
function navigateTo(page) {
activePage = page;
location.hash = encodeURIComponent(page);
var items = document.querySelectorAll('.nav-item');
for (var i = 0; i < items.length; i++) {
if (items[i].dataset.page === page) {
items[i].classList.add('active');
} else {
items[i].classList.remove('active');
}
}
var contentEl = document.getElementById('content');
var md = PAGES[page];
if (!md) {
contentEl.innerHTML = '<div class="empty-state"><h2>Page not found</h2><p>' + escH(page) + '.md does not exist.</p></div>';
return;
}
contentEl.innerHTML = marked.parse(md);
// Rewrite .md links to hash navigation
var links = contentEl.querySelectorAll('a[href]');
for (var i = 0; i < links.length; i++) {
var href = links[i].getAttribute('href');
if (href && href.endsWith('.md') && href.indexOf('://') === -1) {
var slug = href.replace(/\.md$/, '');
links[i].setAttribute('href', '#' + encodeURIComponent(slug));
(function(s) {
links[i].addEventListener('click', function(e) {
e.preventDefault();
navigateTo(s);
});
})(slug);
}
}
// Convert mermaid code blocks into mermaid divs
var mermaidBlocks = contentEl.querySelectorAll('pre code.language-mermaid');
for (var i = 0; i < mermaidBlocks.length; i++) {
var pre = mermaidBlocks[i].parentElement;
var div = document.createElement('div');
div.className = 'mermaid';
div.textContent = mermaidBlocks[i].textContent;
pre.parentNode.replaceChild(div, pre);
}
try { mermaid.run({ querySelector: '.mermaid' }); } catch(e) {}
window.scrollTo(0, 0);
document.getElementById('sidebar').classList.remove('open');
}
})();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment