This file provides guidance to AI agents when working in this repository.
It is the canonical source for agent workflows, responsibilities, and repo-specific operating procedures; use docs/product/README.md for evergreen product and architecture guidance.
Evergreen product and architecture guidance lives in docs/product/. If guidance in this file overlaps with docs/product/, follow the evergreen docs and do not restate or infer product intent from older plan documents.
Read docs/product/README.md in full first, then use your judgment to open only the additional evergreen doc or docs that match the task.
Use docs/plans/ for dated feature plans, redesign proposals, implementation plans, and historical context.
When an agent starts work on a Linear ticket in this repo:
- Move the ticket to
In Progresswhen the work is picked up. - Do not manually move the ticket to
In RevieworDone. - ALWAYS do ticket work on a new git worktree
- Open the pull request and let the GitHub integration move the ticket to
In Reviewwhen appropriate. - Let the GitHub integration move the ticket to
Doneafter the PR is merged or otherwise completes the configured workflow.
pnpm dev # Start Next.js dev server (Convex dev server should run separately: npx convex dev)
pnpm build # Production build
pnpm lint # ESLint
pnpm test # Vitest
pnpm test:e2e # Playwright end-to-end tests
pnpm typecheck # TypeScript type checking (tsc --noEmit)This repo uses Vitest for unit-style tests and Playwright for end-to-end tests. CI runs lint, typecheck, test, and build.
New Git worktrees in this repo may be missing .env.local and node_modules/ when they are first created. Before running the app, Playwright, or project scripts, agents must verify that both exist and bootstrap the worktree if needed.
Bootstrap rules:
- If
.env.localis missing, copy it from the primary checkout. Find that path withgit worktree listand copy the file from the main checkout. - If
node_modules/is missing, runpnpm installfrom the worktree root. - Do not overwrite an existing
.env.localunless the user explicitly asks for that.
Suggested commands:
git worktree list
cp [path/to/main/checkout] .env.local
pnpm installUse playwright-interactive first for UI work in this repo. It is the default because it keeps a persistent browser session alive through js_repl, which is better for iterative frontend development and repeated post-edit verification. Only fall back to playwright CLI if the interactive workflow fails, the js_repl session becomes unhealthy, or the task is intentionally a one-off CLI-style check. If you fallback, flag this to the user as an issue that needs to be fixed.
Shared rules:
- Start the app first:
pnpm devandpnpm convex dev - If the agent starts Next.js itself, read the
pnpm devoutput and capture the actual local URL/port that Next assigned - If the server is already running, verify the active local URL/port before opening Playwright; do not assume
3000 - For Playwright-based work, prefer
127.0.0.1overlocalhostwhen both are available - Playwright credentials live in
.env.localasPLAYWRIGHT_USERNAMEandPLAYWRIGHT_PASSWORD - Standard shared auth state file:
output/playwright/auth.json - Preferred auth refresh command:
pnpm test:e2e:setup - The Playwright auth setup uses Clerk's
@clerk/testinghelpers and requiresCLERK_SECRET_KEY,NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, andNEXT_PUBLIC_CLERK_FRONTEND_API_URLalongside the Playwright credentials - Try loading saved auth state first, but if the app is not running on the same origin the saved auth may not restore cleanly
- The evergreen selector contract lives in
docs/product/technical-architecture-overview.md. Follow that doc if guidance diverges from older plan documents. - App-owned Playwright specs and helper functions must use exact
getByTestId()selectors only. - Do not add new selectors that rely on visible copy, headings, labels, generic table text, CSS classes, or DOM position.
- Prefer semantic fixed ids such as
nav-trades-linkorcampaign-name-inputfor stable controls. - Prefer dynamic ids when the record id is the stable lookup key, such as
campaign-row-<id>ortrade-row-<id>. If the test must target deterministic seeded fixture content before it knows the backend id, use a normalized semantic key that the fixture controls. - Treat
data-testidvalues used by Playwright as a compatibility contract. Update helpers and specs in the same change when a hook changes. - Third-party surfaces the repo does not own, such as Clerk auth, are the only routine exception. Keep those selectors isolated in setup helpers rather than spreading them through specs.
- Shared Playwright helpers should encode selector lookups so specs do not duplicate raw test id strings.
- The default local data model is deterministic suite-level reset and seed for the dedicated Playwright user before authenticated specs run. Tests that create or mutate data should use dedicated helpers or clean up their own side effects.
tests/e2e/setup/global.setup.tsis responsible for local reset/seed before specs execute.
Use this first for UI tasks, especially when the agent expects to make code edits and re-check the UI multiple times in the same task.
- Start each new interactive workflow from a clean
js_replstate so stalebrowser,context, orpagehandles do not leak across tasks - After resetting
js_repl, rerun the Playwright bootstrap/setup cells before interacting with the app - Reuse the same live
browser,context, andpagehandles across checks instead of reopening the browser repeatedly - Set the interactive target URL from the actual running Next.js port before calling
page.goto(...) - Load
output/playwright/auth.jsoninto the browser context before rerunningpnpm test:e2e:setupor falling back to manual Clerk login - Save refreshed auth state back to
output/playwright/auth.jsonafter a manual login succeeds
var TARGET_URL = "http://127.0.0.1:3000"; // Replace with the actual detected local URL.
await context.storageState({ path: "output/playwright/auth.json" });
context = await browser.newContext({
viewport: { width: 1600, height: 900 },
storageState: "output/playwright/auth.json",
});
page = await context.newPage();
await page.goto(TARGET_URL, { waitUntil: "domcontentloaded" });Use this only if playwright-interactive fails, the js_repl session is unavailable, or the interactive browser state becomes unhealthy.
- Invoke the skill wrapper directly:
"$HOME/.codex/skills/playwright/scripts/playwright_cli.sh" - The wrapper must be executable; if direct execution fails, fix the file permissions before continuing
- Use the actual detected local URL in every CLI command; do not hardcode
3000if Next started on another port - Prefer headed mode for local debugging
- Always take a fresh
snapshotbefore using element refs likee47 - Save screenshots with
--filename; do not pass the output path as the positional argument:"$PWCLI" -s=default screenshot --filename output/playwright/example.png - Load
output/playwright/auth.jsonbefore doing a manual login if the file exists
export CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
export PWCLI="$CODEX_HOME/skills/playwright/scripts/playwright_cli.sh"
export APP_URL="http://127.0.0.1:3000" # Replace with the actual detected local URL.
source .env.local
"$PWCLI" open "$APP_URL" --headed
"$PWCLI" state-load output/playwright/auth.json
"$PWCLI" goto "$APP_URL"
"$PWCLI" snapshotIf the saved auth file is missing or no longer valid, prefer refreshing it with:
pnpm test:e2e:setupNotes:
playwrightCLI andplaywright-interactivecan share the sameoutput/playwright/auth.jsonfile- The saved auth state is sensitive; keep it local and do not commit it
- If auth restoration behaves unexpectedly, confirm the agent is using the correct detected origin because cookies are origin-specific
This project uses Convex as its backend.
When working on Convex code, always read convex/_generated/ai/guidelines.md first for important guidelines on how to correctly use Convex APIs and patterns. The file contains rules that override what you may have learned about Convex from training data.
Convex agent skills for common tasks can be installed by running npx convex ai-files install.