8.5 KiB
8.5 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
A FastAPI-based guided demo platform that automates browser interactions with Raindrop using Playwright. The app executes data-driven actions (stored in ActionRegistry) on behalf of personas that target configured browser hosts (CDP or headless). All configuration is externalized via YAML files and environment overrides.
Entry Point: python -m guide (runs src/guide/main.py → guide.app.main:app)
Python Version: 3.12+
Key Dependencies: FastAPI, Playwright, Pydantic v2, PyYAML, httpx
Essential Commands
# Install dependencies
uv sync
# Type checking (required before commits)
basedpyright src
# Compile sanity check
python -m compileall src/guide
# Run development server (default: localhost:8000)
python -m guide
# or with custom host/port:
HOST=127.0.0.1 PORT=9000 python -m guide
# View API docs
# Navigate to http://localhost:8000/docs
# Key endpoints:
# GET /healthz # liveness check
# GET /actions # list action metadata
# POST /actions/{id}/execute # execute action; returns ActionEnvelope with correlation_id
# GET /config/browser-hosts # view current default + host map
Code Structure
Root module: src/guide/app/
actions/— FastAPI-triggered demo actions; thin, declarative, action-scoped side effects. Registry wiring happens inactions/registry.py.auth/— Pluggable MFA/auth helpers (currentlyDummyMfaCodeProvider; needs real provider for production).browser/—BrowserClient+ Playwright wrappers; centralizes navigation, timeouts, error handling, tracing. Handles both CDP attach and headless launch.core/— App bootstrap:AppSettings(Pydantic v2 w/ env prefixRAINDROP_DEMO_), logging, dependency wiring, venv detection.errors/—GuideErrorhierarchy; routes normalize error responses to HTTP status + payload.raindrop/— GraphQL client + operations. Queries/mutations defined inraindrop/operations, schemas/types colocated instrings/.strings/— Centralized selectors, labels, copy, GraphQL strings (no inline literals in actions). Service enforces strict key lookup to catch UI mismatches early.models/— Domain/persona models.PersonaStoreloads from config; use Pydantic v2 with explicit types.utils/— Shared helpers. Keep <300 LoC per file; avoid circular imports.api/— FastAPI routers; map requests →ActionRegistry→BrowserClient→ responses.
Config files (git-tracked):
config/hosts.yaml— Browser host targets (id, kind: cdp|headless, host, port, browser type).config/personas.yaml— Personas (id, role, email, login_method, browser_host_id).
Config overrides (runtime only, never commit):
RAINDROP_DEMO_BROWSER_HOSTS_JSON— JSON array overrideshosts.yaml.RAINDROP_DEMO_PERSONAS_JSON— JSON array overridespersonas.yaml.RAINDROP_DEMO_RAINDROP_BASE_URL— Override defaulthttps://app.raindrop.com.
Architecture Patterns
App Initialization (main.py → create_app)
- Load
AppSettings(env + YAML). - Build
PersonaStorefrom config. - Build
ActionRegistrywith default actions (dependency-injected with persona store + Raindrop URL). - Create
BrowserClient(manages Playwright contexts/browsers, handles CDP + headless). - Stash instances on
app.statefor dependency injection in routers. - Register error handlers (GuideError → HTTP, unhandled → 500 + logging).
Action Execution Pipeline
- Request:
POST /actions/{action_id}/executewithActionRequest(persona_id, host_id, etc.). - Router resolves persona + host from config → validates persona exists.
BrowserClient.open_page()— resolves host by ID → CDP attach or headless launch → reuse existing Raindrop page.Action.run(context)— executes logic; may callensure_persona()(login flow) before starting.- Response:
ActionEnvelopewith correlation_id (fromActionContext) + status + result.
Browser Host Resolution
kind: cdp— connect to running Raindrop instance (requireshost+portin config). Errors surface asBrowserConnectionError.kind: headless— launch Playwright browser (chromium/firefox/webkit); setbrowserfield in config.- Always use
async with BrowserClient.open_page()to ensure proper cleanup.
GraphQL & Data Layer
raindrop/graphql.py— HTTP client (httpx, 10s timeout).raindrop/operations/— query/mutation definitions + response models.- Validate all responses with Pydantic models; schema mismatches →
GuideError. - Never embed tokens/URLs; pull from
AppSettings(env-driven). - Transport errors →
GraphQLTransportError; operation errors →GraphQLOperationError(includesdetailsfrom server).
Selector & String Management (strings/)
- Keep all selectors, labels, copy, GraphQL queries in
strings/submodules. - Use
strings.service(enforces domain-keyed lookups); missing keys raise immediately. - Selectors should be reusable and labeled; avoid brittle text selectors—prefer
data-testidor aria labels.
Development Workflow
- Edit code (actions, browser logic, GraphQL ops, etc.).
- Run type check:
basedpyright src(catches generic types, missing annotations). - Sanity compile:
python -m compileall src/guide(syntax check). - Smoke test:
python -m guidethen hit/docsor manual test via curl. - Review error handling: ensure
GuideErrorsubclasses are raised, not generic exceptions. - Commit with scoped, descriptive message (e.g.,
feat: add auth login action,chore: tighten typing).
Type & Linting Standards
- Python 3.12+: Use PEP 604 unions (
str | None), built-in generics (list[str],dict[str, JSONValue]). - Ban
Anyand# type: ignore: Use type guards or Protocol instead. - Pydantic v2: Explicit types, model_validate for parsing, model_copy for immutable updates.
- Type checker: Pyright (via basedpyright).
- Docstrings: Imperative style, document public APIs, include usage examples.
Error Handling & Logging
- Always raise
GuideErrorsubclasses (not genericException); routers translate to HTTP responses. - Log via
core/logging(structured, levelled). Include persona/action IDs and host targets for traceability. - For browser flows, use Playwright traces (enabled by default in
BrowserClient); disable only intentionally. - Validate external inputs early; surface schema/connection issues as
GuideError.
Testing & Quality Gates
- Minimum gate:
basedpyright src+python -m compileall src/guidebefore merge. - Add unit tests under
tests/alongside code (not yet in structure, but expected). - Mock Playwright/GraphQL in tests; avoid real network/CDP calls.
- Require deterministic fixtures; document any env vars needed in test module docstring.
MFA & Auth
- Default
DummyMfaCodeProviderraisesNotImplementedError. - For real runs, implement a provider and wire it in
core/config.pyorauth/modules. ensure_persona()in actions calls the provider; stub or override for demo/test execution.
Performance & Footprint
- Keep browser sessions short-lived; close contexts to avoid handle leaks.
- Cache expensive GraphQL lookups (per-request OK, global only if safe).
- Don't widen dependencies without justification; stick to project pins in
pyproject.toml. - Promptly close Playwright contexts/browser handles (wrapped in contextmanager; keep action code lean).
Git & PR Hygiene
- Scoped, descriptive commits (e.g.,
feat: add sourcing action,fix: handle missing persona host). - PRs should state changes, commands run, new config entries (hosts/personas).
- Link related issues; include screenshots/logs for UI or API behavior changes.
- Never commit credentials, MFA tokens, or sensitive config; use env overrides.
Quick Checklist (New Feature)
- Add/verify action in
actions/with thin logic; usestrings/for selectors/copy. - Ensure persona/host exist in
config/hosts.yaml+config/personas.yaml(or use env overrides). - Run
basedpyright src+python -m compileall src/guide. - Test via
python -m guide+/docsor manual curl. - Add GraphQL queries to
raindrop/operations/if needed; validate responses with Pydantic. - If auth flow required, implement/mock MFA provider.
- Review error handling; raise
GuideErrorsubclasses. - Commit with descriptive message.