apps/web/src/
├── lib/ # Backend domain modules + singletons
│ ├── runtime/ # Run lifecycle and execution
│ │ ├── test-runner.ts # Shared execution engine
│ │ ├── android-runtime-helpers.ts # Android device/ADB helpers
│ │ ├── assertion-verifier.ts # Quoted string verification
│ │ ├── assertion-shortcuts.ts # Assertion pattern detection
│ │ ├── execution-files.ts # Temp file materialization
│ │ ├── playwright-code-execution.ts # Playwright code step runner
│ │ ├── network-guard-summary.ts # Network guard log emission
│ │ ├── local-browser-runner.ts # Browser runtime dispatch target
│ │ ├── local-browser-runner-parsers.ts # Snapshot/config parsers
│ │ └── usage.ts # API usage tracking
│ ├── runners/ # Runner orchestration + queueing services
│ ├── android/ # Android devices/emulators runtime
│ ├── core/ # Shared core modules (prisma/logger/errors)
│ ├── security/ # Authentication + security helpers
│ │ ├── auth.ts
│ │ ├── api-route-standards.ts # Typed API error/success helpers
│ │ ├── project-route-access.ts # Project route guard
│ │ ├── team-route-access.ts # Team route guard
│ │ └── test-case-route-access.ts # Test case route guard
│ ├── storage/ # Object storage adapters + helpers
│ ├── test-cases/ # Test case domain logic
│ ├── test-config/ # Test config parsing/validation/sorting
│ └── mcp/ # MCP server/tooling
│
├── app/ # Next.js App Router
│ ├── api/ # REST API endpoints
│ │ ├── projects/ # Project CRUD + project configs
│ │ ├── teams/ # Team settings, members, runners, usage
│ │ ├── runners/v1/ # Runner protocol endpoints
│ │ ├── test-cases/ # Test case CRUD + files + history + export
│ │ ├── test-runs/ # Run status, cancel, SSE events, dispatch
│ │ ├── user/ # User settings & API keys
│ │ ├── mcp/ # MCP transport endpoint
│ │ └── health/ # Live/ready/dependencies probes
│ ├── projects/ # Project list & detail pages
│ ├── teams/ # Team settings pages
│ ├── test-cases/[id]/ # Test case history views
│ └── run/ # Main test runner page
│
├── components/ # React components (feature-first)
│ ├── features/
│ │ ├── test-builder/ # Test builder + step editing
│ │ ├── test-configurations/ # Test-level config composition
│ │ ├── project-configurations/# Project-level config management
│ │ ├── test-files/ # File upload/list widgets
│ │ ├── run-results/ # Run timeline + status + artifacts
│ │ ├── test-cases/ # Test case list/detail UI
│ │ ├── projects/ # Project list/detail UI
│ │ ├── team-runners/ # Runner inventory + troubleshooting UI
│ │ ├── team-members/ # Team membership UI
│ │ ├── team-usage/ # Team usage UI
│ │ └── team-ai/ # Team AI key/settings UI
│ ├── shared/ # Cross-feature reusable UI
│ └── layout/ # Page-level layout primitives
│
├── workers/ # Long-running maintenance/dispatch loops
│ ├── runner-maintenance.ts
│ ├── browser-runner.ts
│ └── loop-utils.ts # Shared wake/signal/shutdown utilities
│
├── types/ # TypeScript interfaces
│ └── index.ts # All type exports
│
├── config/app.ts # App configuration
└── i18n/messages.ts # i18n keys (en/zh-Hant/zh-Hans)
| Task | Start Here | Related Files |
|---|---|---|
| Fix test execution | apps/web/src/lib/runtime/test-runner.ts |
apps/web/src/lib/runtime/android-runtime-helpers.ts, apps/web/src/lib/runtime/assertion-verifier.ts, apps/web/src/lib/runtime/playwright-code-execution.ts, apps/web/src/lib/runtime/execution-files.ts, apps/web/src/lib/runtime/local-browser-runner.ts, apps/macos-runner/runner/index.ts |
| Fix browser run dispatch | apps/web/src/lib/runtime/browser-run-dispatcher.ts |
apps/web/src/workers/browser-runner.ts, apps/web/src/app/api/test-runs/dispatch/route.ts |
| Fix run scheduling/claiming | apps/web/src/lib/runners/claim-service.ts |
apps/web/src/app/api/runners/v1/jobs/claim/route.ts |
| Fix runner event ingestion | apps/web/src/lib/runners/event-service.ts |
apps/web/src/app/api/runners/v1/jobs/[id]/events/route.ts |
| Fix SSE/real-time updates | apps/web/src/app/api/test-runs/[id]/events/route.ts |
apps/web/src/components/features/run-results/ui/ResultViewer.tsx |
| Fix test case CRUD | apps/web/src/app/api/test-cases/ |
apps/web/src/types/test.ts, apps/web/src/lib/test-cases/ |
| Fix project CRUD/configs | apps/web/src/app/api/projects/ |
apps/web/src/lib/core/prisma.ts |
| Fix team runners/members/usage | apps/web/src/app/api/teams/ |
apps/web/src/components/features/team-runners/, apps/web/src/components/features/team-members/, apps/web/src/components/features/team-usage/ |
| Fix authentication | apps/web/src/lib/security/auth.ts |
apps/web/src/app/api/, apps/web/src/lib/runners/auth.ts |
| Fix API route auth/access guards | apps/web/src/lib/security/team-route-access.ts |
apps/web/src/lib/security/project-route-access.ts, apps/web/src/lib/security/test-case-route-access.ts, apps/web/src/lib/security/api-route-standards.ts |
| Fix MCP tooling | apps/web/src/lib/mcp/server-registry.ts |
apps/web/src/lib/mcp/server-tools.ts, apps/web/src/lib/mcp/server-schemas.ts, apps/web/src/lib/mcp/server-auth.ts, apps/web/src/lib/mcp/server-response.ts, apps/web/src/lib/mcp/test-case-mutation-tools.ts, apps/web/src/app/api/mcp/route.ts |
| Change DB schema | apps/web/prisma/schema.prisma |
apps/web/src/types/, apps/web/src/lib/core/prisma.ts |
- Next.js 16 (App Router), React 19, TailwindCSS 4
- Prisma + PostgreSQL, Server-Sent Events
- Playwright 1.57, Midscene.js
docs/README.md- Documentation index and audience splitinfra/README.md- Local infra topology and shared deployment dependenciesdocs/maintainers/coding-agent-maintenance-guide.md- Runtime invariants and common footgunsdocs/maintainers/android-runtime-maintenance.md- Android runtime behavior and isolation modeldocs/maintainers/runner-queue-diagnostics.md- Queue debugging and failure tracingdocs/maintainers/frontend-runtime-debugging.md- Frontend/runtime integration debuggingdocs/maintainers/mcp-server-tooling.md- MCP tool contracts for registered toolsdocs/maintainers/test-case-excel-format.md- Import/export format contract
If changing operator-facing runtime behavior, also read:
docs/operators/local-development.mddocs/operators/macos-runner-environment.mddocs/operators/macos-android-runner-guide.mddocs/operators/android-runtime-deployment-checklist.md
docs/operators/- self-hosting setup and runbooks for operatorsdocs/maintainers/- technical maintenance references for developers/coding agentsinfra/- local service topology and infrastructure references kept in sync with docs
infra/docker/docker-compose.local.ymlis the source of truth for local development services.- Shared deployment orchestration is maintained in a private infrastructure repository.
- Do not expose internal deployment repository names or URLs in committed docs or code comments.
- No
any- All types inapps/web/src/types/index.ts - Singletons only - Use
apps/web/src/lib/core/prisma.ts, never create new Prisma instances - No hardcoding - Use
apps/web/src/config/app.ts - Minimal diffs - Change only what's necessary
- Match existing style - No reformatting unrelated code
- No destructive git operations without explicit confirmation
- Do not run
git restore,git checkout -- <file>,git reset --hard,git clean,git rebase, or force-push without the user's permission. - Before any scope cleanup, create a safety snapshot via
git stash push -u -m "wip backup"or a WIP commit.
- Do not run
- Align on intent and success criteria before coding.
- For non-trivial changes, capture design notes in a focused doc under
docs/maintainers/. - For multi-step work, keep a task-by-task implementation checklist in PR/branch notes.
- When changing runtime behavior (runner queueing, Android lifecycle, import/export, dispatch), update the relevant docs in
docs/operators/,docs/maintainers/, andinfra/in the same change series. - Prefer test-first for new behavior; reproduce and trace root causes before fixes.
- Self-review spec compliance first, then code quality; verify before completion claims.
- Run
npm run verifybefore committing (lint, TypeScript compile, and dependency audit).
Code as Documentation: Write self-explanatory code. Avoid comments unless absolutely necessary.
- Good variable/function names eliminate need for comments
- Only add comments for non-obvious "why" (not "what")
- Never comment obvious code like
// loop through itemsor// validate input
make bootstrap- Install dependencies, start local services, and apply schemamake dev- Start local control plane with maintenance and browser worker loopsmake verify- Run repository verification checksnpm run dev- Start dev server directlynpm run lint- Run ESLint and TypeScript compile checksnpm run audit- Audit lockfile dependencies for moderate/high/critical vulnerabilitiesnpm run verify- Run lint and audit checksnpx prisma studio --schema apps/web/prisma/schema.prisma- Open DB GUInpx prisma migrate deploy --schema apps/web/prisma/schema.prisma- Apply committed migrations
import { NextResponse } from 'next/server';
import { prisma } from '@/lib/core/prisma';
import { verifyAuth } from '@/lib/security/auth';
export async function GET(
request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const authPayload = await verifyAuth(request);
if (!authPayload) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { id } = await params;
try {
const resource = await prisma.testCase.findUnique({
where: { id },
include: { project: { select: { userId: true } } }
});
if (!resource) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
if (resource.project.userId !== authPayload.userId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}
return NextResponse.json(resource);
} catch (error) {
console.error('Failed:', error);
return NextResponse.json({ error: 'Failed' }, { status: 500 });
}
}// testRun -> testCase -> project -> user
const testRun = await prisma.testRun.findUnique({
where: { id },
include: {
testCase: {
include: { project: { select: { userId: true } } }
}
}
});
if (!testRun) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
if (testRun.testCase.project.userId !== authPayload.userId) {
return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
}const url = new URL(request.url);
const page = Math.max(1, parseInt(url.searchParams.get('page') || '1'));
const limit = Math.min(100, Math.max(1, parseInt(url.searchParams.get('limit') || '20')));
const skip = (page - 1) * limit;
const [data, total] = await Promise.all([
prisma.testRun.findMany({ where, orderBy, skip, take: limit }),
prisma.testRun.count({ where })
]);
return NextResponse.json({
data,
pagination: { page, limit, total, totalPages: Math.ceil(total / limit) }
});- Edit
apps/web/prisma/schema.prisma - Run
npx prisma migrate dev --schema apps/web/prisma/schema.prisma - Update types in
apps/web/src/types/if needed - Re-export from
apps/web/src/types/index.ts
-
verifyAuth(request)called at route start - Ownership verified via
project.userId === authPayload.userId - Input validated before database operations
- Sensitive fields not exposed in responses
| Type | Location |
|---|---|
| API endpoint | apps/web/src/app/api/<resource>/route.ts |
| Page | apps/web/src/app/<path>/page.tsx |
| Feature component | apps/web/src/components/features/<feature>/ui/<Name>.tsx |
| Feature hooks/model | apps/web/src/components/features/<feature>/{hooks,model}/<module>.ts |
| Shared/Layout component | apps/web/src/components/{shared,layout}/<Name>.tsx |
| Shared logic | apps/web/src/lib/<domain>/<module>.ts |
| Types | apps/web/src/types/<category>.ts + re-export in index.ts |
| Worker | apps/web/src/workers/<worker>.ts |
| Config | apps/web/src/config/app.ts |
| i18n messages | apps/web/src/i18n/messages.ts (all three locales: en, zh-Hant, zh-Hans) |
- All user-facing text must use i18n keys via
t('key.path') - Add keys to all three locales in
apps/web/src/i18n/messages.ts - Keep translations concise; avoid duplicate keys for minor variations
- Use interpolation for dynamic values:
t('key', { name: value })
- Don't create new Prisma or queue instances
- Don't add
anytypes - Don't hardcode values (use config)
- Don't refactor unrelated code
- Don't skip authentication on API routes
- Don't create duplicate i18n keys for minor text variations