diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 38f852f9b..d4718be56 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -36,6 +36,9 @@ jobs: deno-version: 2.9.6 cache: true + - name: Documentation + run: npm run docs:check + - name: Lint run: npm run lint diff --git a/.gitignore b/.gitignore index 25dd91035..ceb9c6836 100644 --- a/.gitignore +++ b/.gitignore @@ -66,7 +66,7 @@ humanizer.md !README.md !AGENTS.md !**/README.md -!docs/pricing-snapshots/*.md +!docs/**/*.md !plans/**/*.md .vercel .env* diff --git a/.superset/db-gate.sh b/.superset/db-gate.sh index a8fda9a46..aee389d45 100755 --- a/.superset/db-gate.sh +++ b/.superset/db-gate.sh @@ -13,11 +13,23 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$DIR/lib/lock.sh" DB_LOCK="$HOME/.superset/debatelab-db.lock" +stack_started=false + +cleanup() { + local status=$? + trap - EXIT + + if [ "$stack_started" = true ] && ! supabase stop --no-backup; then + [ "$status" -ne 0 ] || status=1 + fi + lock_release "$DB_LOCK" + exit "$status" +} echo "Waiting for exclusive access to the local Supabase stack..." lock_acquire "$DB_LOCK" 600 -trap 'lock_release "$DB_LOCK"' EXIT +trap cleanup EXIT supabase db start +stack_started=true npm run test:db -supabase stop --no-backup diff --git a/AGENTS.md b/AGENTS.md index cb7cabfb2..901429906 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,23 +1,38 @@ # DebateLab agent guide -## Runtime and install +## Start here -- Use Node 22 and npm. The lockfile is `package-lock.json`. -- Start a clean checkout with `npm ci`. -- The development server normally uses port 8080. Automated browser verification owns `127.0.0.1:18080` and never reuses an existing server. -- Local Supabase development uses port 54321. Do not point staging checks at the production project. +- The repository is DebateLab. Never infer its directory from an old repository name. +- Run `git rev-parse --show-toplevel` and stay inside that returned worktree. On Adam's VPS, `/home/codex/Projects/DebateLab` is the primary checkout, but Superset and other agents use linked worktrees elsewhere. +- Before editing, inspect the branch and working tree. Preserve unrelated changes. +- Use Node 22, npm, and the committed `package-lock.json`. Install with `npm ci`. ## Parallel Superset workspaces -Each Superset workspace is a separate git worktree. `.superset/config.json` runs `npm ci` on create and `./.superset/dev.sh` for the Run button; `dev.sh` allocates a free port for this workspace from a shared `~/.superset/debatelab-ports/allocations.json` file (range 8080-8179) so several workspaces' dev servers never collide on the hardcoded `:8080` in `vite.config.ts`, and `./.superset/teardown.sh` releases that allocation on delete. +Each Superset workspace is a separate git worktree. `.superset/config.json` runs `npm ci` when it creates a workspace and starts the app through `./.superset/dev.sh`. That script allocates a port from `8080-8179` in `~/.superset/debatelab-ports/allocations.json`; `./.superset/teardown.sh` releases it. -The local Supabase stack is not parallel-safe: `supabase/config.toml` sets no `project_id` or custom port block, so `supabase db start` always binds the CLI's default ports and container names. Run the DB gate through `./.superset/db-gate.sh` rather than calling `supabase db start` / `npm run test:db` directly whenever more than one workspace might be active - it takes an exclusive lock (`~/.superset/debatelab-db.lock`, up to 10 minutes) before starting the stack and stops it afterward. +The local Supabase stack still uses shared default ports and container names. In Superset, run the database gate through `./.superset/db-gate.sh`. It holds `~/.superset/debatelab-db.lock` while the stack runs and stops the stack afterward. -## Verification ladder +## System map -Run the smallest relevant reproduction first, then the complete gate before a PR: +- `src/` is the React 18 and TypeScript client. Routes and lazy page entry points live in `src/App.tsx`. +- `src/lib/stores/` owns browser state and Supabase hydration. `src/lib/ai/client.ts` is the client AI boundary. +- `supabase/functions/` contains Deno Edge Functions. Shared auth, CORS, research, pricing, and validation code lives in `supabase/functions/_shared/`. +- `supabase/migrations/` is the database history and schema source of truth. +- `.github/workflows/` owns CI behavior. `Dockerfile` and `nginx.conf` own production packaging. + +## Normal ports + +- Vite development server: `8080` by default outside Superset; Superset allocates `8080-8179` per worktree. +- Automated browser server: isolated `127.0.0.1:18080`, never an existing dev server. +- Local Supabase: CLI defaults beginning at `54321`. The stack is not parallel-safe across worktrees. + +## Focused verification + +Run the smallest relevant check first: ```bash +npm run docs:check npm run lint npm run typecheck npm test -- --coverage @@ -26,9 +41,15 @@ npm run build CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 npm run test:e2e ``` -`npm run verify` runs that full credential-free ladder. It starts an isolated Vite server and executes the desktop and mobile Chromium projects. Browser failures stay in ignored `test-results/` and `playwright-report/` directories. CI uploads those directories for seven days when the browser step fails. +`npm run verify` runs the complete credential-free ladder, including desktop and mobile Chromium against a fresh Vite server. -Database migrations and pgTAP policies have a separate Docker-backed gate. Supabase CLI 2.115 or newer is required so `CREATE INDEX CONCURRENTLY` runs outside the migration batch: +Database migrations and pgTAP policies use a separate Docker-backed gate. In Superset, use the locking wrapper: + +```bash +./.superset/db-gate.sh +``` + +Outside Superset, run the underlying commands directly: ```bash supabase db start @@ -36,41 +57,40 @@ npm run test:db supabase stop --no-backup ``` -WebKit is an additional release smoke gate: +Supabase CLI 2.115 or newer is required. Do not run this gate concurrently from multiple worktrees. If this task starts the stack, stop it before finishing, including after failures when safe. + +WebKit is a separate release smoke test: ```bash npx playwright install --with-deps webkit CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 npm run test:e2e:webkit ``` -## Protected staging verification - -The staging suite uses a real staging user and backend. It never uses a service-role key. Supply all three protected values together: +## Hard boundaries -- `STAGING_SUPABASE_URL` -- `STAGING_SUPABASE_ANON_KEY` -- `STAGING_DEMO_CODE` +- Start bug fixes with a failing test or repeatable browser reproduction when a suitable test layer exists. +- Stub sessions prove rendering only. Do not call them authenticated backend proof. +- Never put staging or production credentials in jobs, logs, traces, screenshots, commits, comments, or docs. +- Never substitute the production project when local or staging access is unavailable. +- Production deployments, data changes, migrations, Edge Function deployments, and secret changes require explicit authorization. +- A local pass proves only the local commit. Check the current-head GitHub run before calling CI green. +- A skipped staging job is not a pass. Report exactly which browser and staging checks ran. +- Do not weaken tests, RLS, authentication, branch protection, or required checks to make a run pass. -Then run: - -```bash -CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 \ -STAGING_SUPABASE_URL= \ -STAGING_SUPABASE_ANON_KEY= \ -STAGING_DEMO_CODE= \ -npm run test:e2e:staging -``` +## Protected staging -The suite rejects the production project, checks that the anon key belongs to the supplied staging URL, authenticates through `demo-code-login`, proves session reload, writes and reads back one unique source, makes one bounded AI request, and deletes only that source. It saves temporary auth state under `test-results/.auth/`. Staging disables traces, screenshots, and video, and its workflow uploads no browser artifacts. +Staging E2E requires all three values together: `STAGING_SUPABASE_URL`, `STAGING_SUPABASE_ANON_KEY`, and `STAGING_DEMO_CODE`. The suite rejects the production project, disables browser captures, writes only uniquely named test data, and deletes only that data. Read [docs/testing.md](docs/testing.md) before running it and [docs/operations.md](docs/operations.md) before changing staging configuration. -GitHub runs staging only for `main` push or a main-branch manual dispatch, through the protected `staging` environment, after the credential-free build job passes. Set the repository variable `STAGING_E2E_ENABLED=true` only after the protected environment has the three secrets, current staging functions, schema, provider secret, dedicated unlimited-use test code, and a main-only deployment rule. A skipped staging job is not a pass. +## Documentation router -The staging origin `http://127.0.0.1:18080` must be deployed in the CORS allowlists for `demo-code-login`, `ai`, `ai-stream`, `admin`, and `announcements` before enabling the gate. Do not deploy those functions or apply migrations to an unverified project. +Open only what the task needs: -## Working rules +- [docs/architecture.md](docs/architecture.md): runtime boundaries, request flows, state, auth, billing, observability, PWA, and iOS. +- [docs/development.md](docs/development.md): setup, environment behavior, worktrees, and common workflows. +- [docs/testing.md](docs/testing.md): what each gate proves, browser matrices, staging, artifacts, and cleanup. +- [docs/operations.md](docs/operations.md): CI, Docker, Nginx, staging, deployment, rollback, and live verification. +- [supabase/README.md](supabase/README.md): Edge Functions, shared backend modules, migrations, auth boundaries, and required secret names. +- [plans/README.md](plans/README.md): active plans. Revalidate every plan against current source before executing it. +- [docs/README.md](docs/README.md): complete documentation index and freshness labels. -- Start bug fixes with a failing test or a repeatable browser reproduction when the repository has a suitable test layer. -- Keep stub sessions limited to render checks. Do not describe them as authenticated backend proof. -- Never put staging or production secrets in PR jobs, logs, traces, screenshots, commits, or comments. -- A local pass proves only the local commit. Inspect the current-head GitHub run before calling CI green. Account billing or spending-limit failures are external exceptions, not passing checks. -- State exactly which browser and staging checks ran. If credentials, provider access, or staging synchronization are missing, stop that gate and report it instead of substituting production or mocks. +When commands, ports, workflows, or runtime boundaries change, update the owning focused document in the same change. Do not copy large source trees, schemas, dependency lists, or model catalogs into documentation. diff --git a/README.md b/README.md index ef7027a81..fd3eb2ca0 100644 --- a/README.md +++ b/README.md @@ -1,1560 +1,218 @@ # DebateLab -> The ultimate AI-powered research and preparation suite for competitive debaters. - -**🌐 Live App:** [https://debate.pi-stack.org](https://debate.pi-stack.org) - -Public DebateLab brand/entity pages: [DebateLab by Programcode](https://debate.pi-stack.org/debatelab), [DebateLab AI](https://debate.pi-stack.org/debatelab-ai), [Debate Lab](https://debate.pi-stack.org/debate-lab), and the [public GitHub entity profile](https://github.com/Programcode/debatelab-ai-profile). - -Public DebateLab guides: [AI debate practice](https://debate.pi-stack.org/ai-debate-practice), [debate case generator](https://debate.pi-stack.org/debate-case-generator), [rebuttal practice](https://debate.pi-stack.org/debate-rebuttal-practice), [AI debate coach](https://debate.pi-stack.org/ai-debate-coach), and [about DebateLab](https://debate.pi-stack.org/about-debatelab). - ---- - -## Table of Contents - -1. [Overview](#overview) -2. [Features](#features) -3. [Architecture](#architecture) -4. [Getting Started](#getting-started) -5. [Project Structure](#project-structure) -6. [Backend: Supabase Edge Functions](#backend-supabase-edge-functions) -7. [Frontend: React Application](#frontend-react-application) -8. [State Management](#state-management) -9. [Database Schema](#database-schema) -10. [Authentication](#authentication) -11. [AI Integration](#ai-integration) -12. [Security](#security) -13. [Deployment](#deployment) -14. [Development Guide](#development-guide) -15. [Testing](#testing) -16. [API Reference](#api-reference) -17. [Configuration](#configuration) -18. [Troubleshooting](#troubleshooting) -19. [Contributing](#contributing) -20. [Roadmap](#roadmap) - ---- - -## Overview - -DebateLab is a comprehensive debate preparation platform designed for competitive debaters. It combines multiple AI providers (OpenAI, Google Gemini, Anthropic) with real-time web research to help users: - -- **Generate complete debate cases** with structured contentions and evidence -- **Attack and critique** opponent arguments with logical fallacy detection -- **Research evidence** with AI-powered web search and citation verification -- **Practice with an AI coach** that remembers your research and style -- **Build a knowledge base** of sources that grounds all AI responses - -The application uses a modern tech stack with React frontend and Supabase backend, ensuring API keys are never exposed to the browser while providing a seamless user experience. - ---- - -## Features - -### 1. Case Generator - -Generate complete debate cases for tournament competition. - -| Feature | Description | -|---------|-------------| -| **Format** | 4-6-6-3 tournament format (4min constructive, 6min rebuttal, 6min cross-ex, 3min summary) | -| **Structure** | Introduction, key definitions, contentions with evidence, and conclusion | -| **Customization** | Choose judge paradigm, style, and focus areas | -| **Export** | Download as professionally formatted `.docx` Word document | - -**Input Fields:** -- Resolution (the debate topic) -- Side (Affirmative/Negative) -- Number of contentions (1-4) -- Style (formal, conversational, aggressive) -- Focus areas (logic, values, policy) -- Additional context - -**Output Structure:** -```typescript -interface GenerateOutput { - introduction: string; - caseStructure: { - keyDefinitions: string[]; - contentions: Array<{ - title: string; - claim: string; - warrant: string; - impact: string; - evidence?: string; - }>; - }; - conclusion: string; - suggestedCrossExQuestions: string[]; - potentialWeaknesses: string[]; -} -``` - ---- - -### 2. Attack Mode - -Ruthlessly critique any debate case to find weaknesses. - -| Feature | Description | -|---------|-------------| -| **Fallacy Detection** | Identifies logical fallacies (straw man, ad hominem, slippery slope, etc.) | -| **Strategic Analysis** | Provides cross-examination questions and rebuttal strategies | -| **Judge Paradigms** | Analyze from Logic-Heavy, Values-Focused, or Policy-based perspectives | -| **Severity Ratings** | Each weakness rated by impact on the case | - -**Output Structure:** -```typescript -interface AttackOutput { - overallAssessment: string; - weaknesses: Array<{ - title: string; - description: string; - severity: 'low' | 'medium' | 'high' | 'critical'; - suggestedAttack: string; - }>; - fallacies: Array<{ - name: string; - quote: string; - explanation: string; - }>; - crossExQuestions: string[]; - counterArguments: string[]; -} -``` - ---- - -### 3. Brainstorm - -Multi-mode ideation for analyzing debate motions. - -| Mode | Description | -|------|-------------| -| **Mindmap** | Hierarchical breakdown of the motion into themes and sub-points | -| **Buckets** | Organize ideas by category (economic, social, political, etc.) | -| **Top 5** | Get the 5 strongest arguments for and against the motion | - -**Output Structure:** -```typescript -interface BrainstormOutput { - mode: 'mindmap' | 'buckets' | 'topfive'; - motion: string; - nodes?: Array<{ id: string; label: string; children?: string[] }>; // mindmap - buckets?: Array<{ name: string; ideas: string[] }>; // buckets - topFive?: { pro: string[]; con: string[] }; // topfive -} -``` - ---- - -### 4. Factoids (Research) - -AI-powered evidence research with deep web search. - -| Feature | Description | -|---------|-------------| -| **Deep Research** | Multi-round search with AI-generated follow-up queries | -| **URL Verification** | Validates that cited sources actually exist | -| **Content Reading** | Extracts full text from top sources via Jina AI | -| **Citation Format** | Provides clickable links with verification status | - -**Research Process:** -1. Initial Jina search for the claim -2. AI generates 2-3 follow-up queries based on initial results -3. Execute follow-up searches -4. Read content from top 3-5 URLs -5. AI synthesizes all information into structured output -6. URL verification for cited sources - -**Output Structure:** -```typescript -interface FactoidsOutput { - claim: string; - verdict: 'True' | 'False' | 'Partially True' | 'Unverifiable'; - confidence: number; // 0-100 - evidence: Array<{ - point: string; - source?: string; - url?: string; - verified?: boolean; - }>; - context: string; - limitations: string[]; -} -``` - ---- - -### 5. Coach Chat (Ask) - -Interactive AI coaching with conversation memory. - -| Feature | Description | -|---------|-------------| -| **Context Memory** | Summarizes older messages to maintain coherent conversations | -| **Source Grounding** | Uses your library sources as factual context | -| **Voice Input** | Real-time speech-to-text for hands-free coaching | -| **File Attachments** | Upload documents for AI analysis | -| **Citation Mode** | Toggle to get responses with source citations | -| **Web Search Toggle** | Optional AI-triggered Jina search for current events and fresh evidence | - -**Message Structure:** -```typescript -interface Message { - id: string; - role: 'user' | 'assistant'; - content: string; - timestamp: number; - output?: AskOutput; // Structured response data -} -``` - ---- - -### 6. Source Library - -Build a persistent knowledge base for AI grounding. - -| Feature | Description | -|---------|-------------| -| **URL Reading** | Automatically extracts content from web pages | -| **Manual Text** | Add text snippets directly | -| **Response Saving** | Bookmark any AI response to save as source | -| **Active Toggle** | Enable/disable sources for AI context | -| **Bulk Management** | Clear or export entire library | - -**Source Structure:** -```typescript -interface Source { - id: string; - title: string; - content: string; - type: 'url' | 'file' | 'text'; - origin?: string; - createdAt: number; - isActive: boolean; -} -``` - -When sources are active, they are injected into every AI request: -``` -LOCAL KNOWLEDGE BASE (Use this information as your primary factual source): ---- SOURCE: [title] ([origin]) --- -[content] -``` - ---- +DebateLab is an AI-assisted research, case-building, and practice application for competitive debaters. The web app combines structured debate tools, source management, live practice modes, and server-side AI access behind Supabase authentication. -### 7. Word Export +Live application: [debate.pi-stack.org](https://debate.pi-stack.org) -Download generated cases as professionally formatted documents. +## What the application does -| Format | Description | -|--------|-------------| -| **Document Type** | Microsoft Word `.docx` | -| **Formatting** | Headers, bullet points, proper spacing | -| **Sections** | Introduction, definitions, contentions, conclusion | -| **Metadata** | Title, author, creation date | +DebateLab currently includes: ---- +- Case generation with structured claims, warrants, impacts, evidence, and export. +- Attack analysis for weaknesses, fallacies, cross-examination questions, and counters. +- Motion brainstorming in mind-map, bucket, and ranked-argument modes. +- Fact checking and research with source discovery and citation handling. +- Ask, an AI debate coach with conversation history and optional source grounding. +- Crossfire practice with turn-based prompts and debriefs. +- Timed sparring rounds with saved sessions and scorecards. +- A flowchart editor for argument structure. +- A source library with folders, imports, activation controls, and cloud sync. +- History, sharing, timers, settings, subscriptions, announcements, and administration. +- A Capacitor iOS shell, PWA support, offline route preloading, and installable web assets. -### 8. Debate Timer +Public marketing, guide, legal, and shared-content routes are separate from the authenticated application. The route source of truth is [`src/App.tsx`](src/App.tsx). -Specialized stopwatch for tournament speech intervals. +## Quick start -| Preset | Duration | -|--------|----------| -| Constructive | 4 minutes | -| Rebuttal | 6 minutes | -| Cross-Ex | 6 minutes | -| Summary | 3 minutes | -| Custom | User-defined | +### Requirements ---- +- Node.js 22.x +- npm and the committed `package-lock.json` +- Deno 2.x for Edge Function tests +- Docker and Supabase CLI 2.115 or newer for database tests +- Playwright browsers for browser tests -## Architecture - -### System Overview - -``` -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ CLIENT β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ React Application (Vite) β”‚ β”‚ -β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ -β”‚ β”‚ β”‚ Pages β”‚ β”‚ Components β”‚ β”‚ State (Zustand) β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ - Generate β”‚ β”‚ - Forms β”‚ β”‚ - sessionStore β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ - Attack β”‚ β”‚ - Results β”‚ β”‚ - settingsStore β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ - Ask β”‚ β”‚ - Chat β”‚ β”‚ - localStorage persist β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ - Library β”‚ β”‚ - Timer β”‚ β”‚ β”‚ β”‚ β”‚ -β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β”‚ β”‚ β”‚ -β”‚ β–Ό β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ Supabase Client SDK β”‚ β”‚ -β”‚ β”‚ - Authentication (Google OAuth) β”‚ β”‚ -β”‚ β”‚ - Edge Function invocation β”‚ β”‚ -β”‚ β”‚ - Database queries (optional sync) β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - β”‚ - β”‚ HTTPS - β–Ό -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ SUPABASE β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ Edge Function: /ai β”‚ β”‚ -β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ -β”‚ β”‚ β”‚ Request Handling β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ - JWT verification β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ - Input sanitization (prompt injection detection) β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ - Rate limiting β”‚ β”‚ β”‚ -β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ -β”‚ β”‚ β”‚ β”‚ β”‚ -β”‚ β”‚ β–Ό β”‚ β”‚ -β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ -β”‚ β”‚ β”‚ Feature Router β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ - generate: Case generation β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ - attack: Case critique β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ - brainstorm: Idea generation β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ - factoids: Deep research β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ - ask: Chat coaching β”‚ β”‚ β”‚ -β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ -β”‚ β”‚ β”‚ β”‚ β”‚ -β”‚ β”‚ β–Ό β”‚ β”‚ -β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ -β”‚ β”‚ β”‚ AI Provider Routing β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ - OpenAI (GPT-4, GPT-4o) β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ - Google (Gemini 2.0 Flash) β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ - Anthropic (Claude 3.5 Sonnet) β”‚ β”‚ β”‚ -β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ -β”‚ β”‚ β”‚ β”‚ β”‚ -β”‚ β”‚ β–Ό β”‚ β”‚ -β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ -β”‚ β”‚ β”‚ External Services β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ - Jina AI Search (s.jina.ai) β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ - Jina AI Reader (r.jina.ai) β”‚ β”‚ β”‚ -β”‚ β”‚ β”‚ - URL verification β”‚ β”‚ β”‚ -β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β”‚ β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ Supabase Auth β”‚ β”‚ -β”‚ β”‚ - Google OAuth provider β”‚ β”‚ -β”‚ β”‚ - JWT token management β”‚ β”‚ -β”‚ β”‚ - Session handling β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β”‚ β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ Supabase Database (PostgreSQL) β”‚ β”‚ -β”‚ β”‚ - User data sync (optional) β”‚ β”‚ -β”‚ β”‚ - Saved cases, chats, sources β”‚ β”‚ -β”‚ β”‚ - Row Level Security (RLS) β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β”‚ β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ Secrets (Environment Variables) β”‚ β”‚ -β”‚ β”‚ - OPENAI_API_KEY β”‚ β”‚ -β”‚ β”‚ - GOOGLE_API_KEY β”‚ β”‚ -β”‚ β”‚ - ANTHROPIC_API_KEY β”‚ β”‚ -β”‚ β”‚ - JINA_API_KEY β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - β”‚ - β”‚ AI API Calls - β–Ό -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ EXTERNAL AI PROVIDERS β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ OpenAI β”‚ β”‚ Google β”‚ β”‚ Anthropic β”‚ β”‚ -β”‚ β”‚ api.openai.com β”‚ β”‚ generativelang β”‚ β”‚ api.anthropic.com β”‚ β”‚ -β”‚ β”‚ β”‚ β”‚ .googleapis.comβ”‚ β”‚ β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ -``` - -### Technology Stack - -| Layer | Technology | -|-------|------------| -| **Frontend Framework** | React 18 with TypeScript | -| **Build Tool** | Vite 5 | -| **Styling** | TailwindCSS + shadcn/ui (45+ components) | -| **State Management** | Zustand with localStorage persistence | -| **Forms** | React Hook Form + Zod validation | -| **Routing** | React Router v6 | -| **Backend** | Supabase Edge Functions (Deno runtime) | -| **Authentication** | Supabase Auth (Google OAuth) | -| **Database** | Supabase PostgreSQL | -| **AI Providers** | OpenAI, Google Gemini, Anthropic Claude | -| **Web Search** | Jina AI (Search + Reader) | -| **Containerization** | Docker with multi-stage builds | -| **Web Server** | Nginx (production) | -| **CI/CD** | GitHub Actions | -| **Hosting** | Raspberry Pi + Cloudflare Tunnel | - ---- - -## Getting Started - -### Prerequisites - -- **Node.js** 22.x -- **npm** 9.x or higher (or `bun`) -- **Docker** (for production builds) -- **Supabase account** (for backend) - -### Quick Start +### Install and run ```bash -# 1. Clone the repository -git clone https://github.com/Programcode/debate-navigator.git -cd debate-navigator - -# 2. Install dependencies -npm install - -# 3. Start development server +git clone https://github.com/Programcode/DebateLab.git +cd DebateLab +npm ci npm run dev - -# 4. Open http://localhost:8080 ``` -### Environment Setup - -The application requires a Supabase project for authentication and edge functions. +Vite listens on port `8080` by default. Open `http://localhost:8080`. -**Supabase Configuration:** - -1. Create a new Supabase project at https://supabase.com -2. Enable Google OAuth provider in Authentication settings -3. Deploy the Edge Function (see [Deployment](#deployment)) -4. Add API keys as Supabase secrets - -### iOS and iPadOS - -The iOS/iPadOS app is a Capacitor shell around the Vite build. +If an agent or Superset created a linked worktree, stay in that worktree. Confirm it with: ```bash -npm run ios:build -npm run ios:open -``` - -The native bundle identifier is `com.adam.debatelab`, and the app URL scheme is `debatelab://`. - -OAuth setup notes: - -- In Google Cloud, keep the authorized redirect URI as the Supabase HTTPS callback, for example `https://.supabase.co/auth/v1/callback`. -- In Supabase Authentication URL Configuration, add the native callback `debatelab://auth/callback` as an allowed redirect URL. -- Do not add `debatelab://auth/callback` to Google Cloud Web OAuth redirect URIs; Google only accepts `http` or `https` there. - ---- - -## Project Structure - +git rev-parse --show-toplevel ``` -debate-navigator/ -β”œβ”€β”€ .github/ -β”‚ └── workflows/ -β”‚ └── docker-publish.yml # CI/CD pipeline -β”‚ -β”œβ”€β”€ public/ -β”‚ β”œβ”€β”€ config.js # Runtime configuration (empty, keys in backend) -β”‚ β”œβ”€β”€ favicon.ico -β”‚ └── robots.txt -β”‚ -β”œβ”€β”€ src/ -β”‚ β”œβ”€β”€ components/ -β”‚ β”‚ β”œβ”€β”€ features/ # Feature-specific components -β”‚ β”‚ β”‚ β”œβ”€β”€ ask/ # Chat coaching -β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ AskChat.tsx -β”‚ β”‚ β”‚ β”‚ └── AskInput.tsx -β”‚ β”‚ β”‚ β”œβ”€β”€ attack/ # Case critique -β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ AttackForm.tsx -β”‚ β”‚ β”‚ β”‚ └── AttackResults.tsx -β”‚ β”‚ β”‚ β”œβ”€β”€ brainstorm/ # Idea generation -β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ BrainstormForm.tsx -β”‚ β”‚ β”‚ β”‚ └── BrainstormResults.tsx -β”‚ β”‚ β”‚ β”œβ”€β”€ factoids/ # Research -β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ FactoidsForm.tsx -β”‚ β”‚ β”‚ β”‚ └── FactoidsResults.tsx -β”‚ β”‚ β”‚ └── generate/ # Case generation -β”‚ β”‚ β”‚ β”œβ”€β”€ GenerateForm.tsx -β”‚ β”‚ β”‚ └── GenerateResults.tsx -β”‚ β”‚ β”œβ”€β”€ layout/ -β”‚ β”‚ β”‚ β”œβ”€β”€ AppLayout.tsx # Main layout with nav -β”‚ β”‚ β”‚ β”œβ”€β”€ MobileNav.tsx -β”‚ β”‚ β”‚ └── Sidebar.tsx -β”‚ β”‚ └── ui/ # shadcn/ui components (45+) -β”‚ β”‚ β”œβ”€β”€ button.tsx -β”‚ β”‚ β”œβ”€β”€ card.tsx -β”‚ β”‚ β”œβ”€β”€ dialog.tsx -β”‚ β”‚ └── ... -β”‚ β”‚ -β”‚ β”œβ”€β”€ hooks/ -β”‚ β”‚ β”œβ”€β”€ use-mobile.tsx # Responsive detection -β”‚ β”‚ β”œβ”€β”€ use-speech-to-text.ts # Voice input -β”‚ β”‚ └── use-toast.ts # Notifications -β”‚ β”‚ -β”‚ β”œβ”€β”€ lib/ -β”‚ β”‚ β”œβ”€β”€ ai/ -β”‚ β”‚ β”‚ β”œβ”€β”€ client.ts # AI integration (Edge Function calls) -β”‚ β”‚ β”‚ └── schemas/ # Zod validation schemas -β”‚ β”‚ β”‚ β”œβ”€β”€ ask.ts -β”‚ β”‚ β”‚ β”œβ”€β”€ attack.ts -β”‚ β”‚ β”‚ β”œβ”€β”€ brainstorm.ts -β”‚ β”‚ β”‚ β”œβ”€β”€ factoids.ts -β”‚ β”‚ β”‚ └── generate.ts -β”‚ β”‚ β”œβ”€β”€ stores/ -β”‚ β”‚ β”‚ β”œβ”€β”€ session-store.ts # Session state (results, history) -β”‚ β”‚ β”‚ └── settings-store.ts # User settings -β”‚ β”‚ β”œβ”€β”€ supabase/ -β”‚ β”‚ β”‚ └── client.ts # Supabase client initialization -β”‚ β”‚ └── utils.ts # Utility functions -β”‚ β”‚ -β”‚ β”œβ”€β”€ pages/ -β”‚ β”‚ β”œβ”€β”€ AskPage.tsx # Chat coaching -β”‚ β”‚ β”œβ”€β”€ AttackPage.tsx # Case critique -β”‚ β”‚ β”œβ”€β”€ BrainstormPage.tsx # Idea generation -β”‚ β”‚ β”œβ”€β”€ FactoidsPage.tsx # Research -β”‚ β”‚ β”œβ”€β”€ GeneratePage.tsx # Case generation -β”‚ β”‚ β”œβ”€β”€ HistoryPage.tsx # Saved items -β”‚ β”‚ β”œβ”€β”€ LibraryPage.tsx # Source library -β”‚ β”‚ β”œβ”€β”€ LoginPage.tsx # Authentication -β”‚ β”‚ β”œβ”€β”€ SettingsPage.tsx # Configuration -β”‚ β”‚ └── TimerPage.tsx # Debate timer -β”‚ β”‚ -β”‚ β”œβ”€β”€ test/ -β”‚ β”‚ β”œβ”€β”€ security/ # Security tests -β”‚ β”‚ β”‚ β”œβ”€β”€ file-upload.test.ts -β”‚ β”‚ β”‚ β”œβ”€β”€ prompt-injection.test.ts -β”‚ β”‚ β”‚ └── url-validation.test.ts -β”‚ β”‚ └── example.test.ts -β”‚ β”‚ -β”‚ β”œβ”€β”€ App.tsx # Root component with routing -β”‚ β”œβ”€β”€ main.tsx # Entry point -β”‚ └── index.css # Global styles -β”‚ -β”œβ”€β”€ supabase/ -β”‚ └── functions/ -β”‚ └── ai/ -β”‚ └── index.ts # Edge Function (all AI logic) -β”‚ -β”œβ”€β”€ Dockerfile # Multi-stage Docker build -β”œβ”€β”€ nginx.conf # Production web server config -β”œβ”€β”€ package.json -β”œβ”€β”€ tailwind.config.ts -β”œβ”€β”€ tsconfig.json -β”œβ”€β”€ vite.config.ts -└── vitest.config.ts -``` - ---- -## Backend: Supabase Edge Functions +Do not jump to a different checkout because a document or old command names another directory. -### Edge Function Overview - -The backend is a single Supabase Edge Function (`supabase/functions/ai/index.ts`) that handles all AI requests. This design keeps API keys secure on the server side. - -**Location:** `supabase/functions/ai/index.ts` -**Runtime:** Deno -**Size:** ~545 lines - -### Request Flow - -``` -1. Client sends POST to /functions/v1/ai -2. JWT token verified from Authorization header -3. Input sanitized for prompt injection -4. Request routed by feature type -5. AI provider called with server-side keys -6. Response parsed and validated -7. JSON returned to client -``` - -### Supported Features - -| Feature | Endpoint | Description | -|---------|----------|-------------| -| `generate` | `POST /ai` | Generate debate cases | -| `attack` | `POST /ai` | Critique cases | -| `brainstorm` | `POST /ai` | Generate ideas | -| `factoids` | `POST /ai` | Research with deep search | -| `ask` | `POST /ai` | Chat coaching | - -### Request Format - -```typescript -interface AIRequest { - feature: 'generate' | 'attack' | 'brainstorm' | 'factoids' | 'ask'; - input: FeatureInput; // Varies by feature - config: { - provider: 'openai' | 'google' | 'anthropic'; - model: string; - temperature: number; - }; - sources?: Source[]; // Active library sources - history?: Message[]; // For ask feature -} -``` - -### Input Sanitization - -The Edge Function includes robust input sanitization to prevent prompt injection attacks: - -```typescript -const PROMPT_INJECTION_PATTERNS = [ - /ignore\s+(all\s+)?(previous|prior|above)\s+(instructions|prompts|text)/i, - /disregard\s+(all\s+)?(previous|prior|above)/i, - /forget\s+(all\s+)?(previous|prior|above)/i, - /you\s+are\s+now\s+(a|an)\s+/i, - /new\s+instruction[s]?\s*:/i, - /system\s*:\s*\[/i, - /\[\s*SYSTEM\s*\]/i, - /\{\s*"role"\s*:\s*"system"/i, - /<\|im_start\|>/i, - /<\|im_end\|>/i, - /###\s*instruction/i, - /\bDAN\s+mode\b/i, - /jailbreak/i, -]; -``` - -### Deep Research (Factoids) - -The factoids feature uses a multi-step research process: - -```typescript -async function deepResearch(claim, region, provider, model) { - // Step 1: Initial Jina search - const initialResults = await jinaSearch(claim); - - // Step 2: Generate follow-up queries with AI - const followUpQueries = await generateFollowUpQueries( - claim, - formatSearchResults(initialResults), - provider, - model - ); - - // Step 3: Execute follow-up searches - for (const query of followUpQueries) { - const results = await jinaSearch(query); - // Collect results... - } - - // Step 4: Read content from top URLs - for (const url of topUrls.slice(0, 5)) { - const content = await jinaRead(url); - // Store content... - } - - // Step 5: Verify URLs exist - for (const url of allUrls) { - const exists = await verifyUrl(url); - // Track verification... - } - - return researchContext; -} -``` - -### Deploying the Edge Function +## Common commands ```bash -# Install Supabase CLI -npm install -g supabase - -# Login to Supabase -supabase login - -# Link to your project -supabase link --project-ref YOUR_PROJECT_REF - -# Set secrets -supabase secrets set OPENAI_API_KEY=sk-... -supabase secrets set GOOGLE_API_KEY=AIza... -supabase secrets set ANTHROPIC_API_KEY=sk-ant-... -supabase secrets set JINA_API_KEY=jina_... -supabase secrets set AI_DEFAULT_PROVIDER=google -supabase secrets set AI_DEFAULT_MODEL=gemini-2.0-flash - -# Deploy -supabase functions deploy ai -``` - ---- - -## Frontend: React Application - -### Page Components - -| Page | Path | Description | -|------|------|-------------| -| `HomePage` | `/app` | Unified mode selector homepage | -| `GeneratePage` | `/app/generate` | Case generation form and results | -| `AttackPage` | `/app/attack` | Case critique with fallacy detection | -| `BrainstormPage` | `/app/brainstorm` | Multi-mode idea generation | -| `FactoidsPage` | `/app/factoids` | Research with deep search | -| `AskPage` | `/app/ask` | Interactive chat coaching | -| `LibraryPage` | `/app/library` | Source library management | -| `HistoryPage` | `/app/history` | Saved cases, attacks, etc. | -| `SettingsPage` | `/app/settings` | Configuration and admin | -| `TimerPage` | `/app/timer` | Debate stopwatch | -| `LoginPage` | `/login` | Authentication | - -### Component Patterns - -Each feature follows a consistent pattern: - -```tsx -// FeaturePage.tsx - Container -function FeaturePage() { - const { result, loading, setResult, setLoading } = useSessionStore(); - - const handleSubmit = async (input: FeatureInput) => { - setLoading(true); - try { - const result = await callAI('feature', input, config); - setResult(result); - } finally { - setLoading(false); - } - }; - - return ( -
- - {result && } -
- ); -} - -// FeatureForm.tsx - Input form with validation -function FeatureForm({ onSubmit, loading }) { - const form = useForm({ - resolver: zodResolver(featureInputSchema), - defaultValues: { ... } - }); - - return ( -
- - {/* Form fields */} - -
- - ); -} - -// FeatureResults.tsx - Structured output display -function FeatureResults({ result }: { result: FeatureOutput }) { - return ( - - {/* Render structured output */} - - ); -} -``` - -### Routing - -```tsx -// src/App.tsx - - - } /> - } /> - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - -``` - ---- - -## State Management - -### Zustand Stores - -The application uses two Zustand stores with localStorage persistence: - -#### Session Store (`session-store.ts`) - -Manages active results, history, and source library. - -```typescript -interface SessionState { - // Active results - generateResult: GenerateOutput | null; - generateLoading: boolean; - attackResult: AttackOutput | null; - attackLoading: boolean; - brainstormResult: BrainstormOutput | null; - brainstormLoading: boolean; - factoidsResult: FactoidsOutput | null; - factoidsLoading: boolean; - askMessages: Message[]; - askLoading: boolean; - - // Saved history - savedCases: SavedItem[]; - savedAttacks: SavedItem[]; - savedBrainstorms: SavedItem[]; - savedFactoids: SavedItem[]; - savedChats: SavedChat[]; - - // Source library - sourceLibrary: Source[]; - - // Actions - setGenerateResult: (result: GenerateOutput | null) => void; - saveCurrentCase: (title: string) => Promise; - deleteSavedCase: (id: string) => void; - loadSavedCase: (id: string) => void; - addSource: (source: Omit) => Promise; - toggleSource: (id: string) => Promise; - clearAllData: () => Promise; - // ... more actions -} -``` - -#### Settings Store (`settings-store.ts`) - -Manages user preferences and AI configuration. - -```typescript -interface SettingsState { - // Feature configurations - generateConfig: ProviderConfig; - attackConfig: ProviderConfig; - brainstormConfig: ProviderConfig; - factoidsConfig: ProviderConfig; - askConfig: ProviderConfig; - - // UI settings - theme: 'light' | 'dark' | 'system'; - language: 'en' | 'es'; - - // Admin - isAdmin: boolean; - customSystemPrompt: string; - - // Actions - setGenerateConfig: (config: Partial) => void; - setAdmin: (isAdmin: boolean) => void; - setTheme: (theme: string) => void; - // ... more actions -} - -interface ProviderConfig { - provider: 'openai' | 'google' | 'anthropic'; - model: string; - temperature: number; -} -``` - -### Persistence - -Both stores persist to localStorage using Zustand's persist middleware: - -```typescript -export const useSessionStore = create()( - persist( - (set, get) => ({ - // State and actions... - }), - { - name: 'debate-trainer-storage', - storage: createJSONStorage(() => localStorage), - } - ) -); +npm run dev # Vite development server +npm run docs:check # maintained Markdown links and retired paths +npm run lint # ESLint +npm run typecheck # TypeScript without emitting files +npm test # Vitest once +npm test -- --coverage # selected-scope coverage gate +npm run test:edge # native Deno Edge Function tests +npm run test:db # pgTAP tests with local Supabase running +npm run build # production Vite build +npm run test:e2e # desktop and mobile Chromium +npm run verify # complete credential-free gate ``` ---- - -## Database Schema - -### Supabase Tables - -The application uses Supabase PostgreSQL for optional cloud sync: - -```sql --- Users (managed by Supabase Auth) --- No custom table needed, uses auth.users - --- Saved Cases -CREATE TABLE saved_cases ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, - title TEXT NOT NULL, - output JSONB NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Saved Attacks -CREATE TABLE saved_attacks ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, - title TEXT NOT NULL, - output JSONB NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Saved Brainstorms -CREATE TABLE saved_brainstorms ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, - title TEXT NOT NULL, - output JSONB NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Saved Factoids -CREATE TABLE saved_factoids ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, - title TEXT NOT NULL, - output JSONB NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Saved Chats -CREATE TABLE saved_chats ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, - title TEXT NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Chat Messages -CREATE TABLE chat_messages ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - chat_id UUID REFERENCES saved_chats(id) ON DELETE CASCADE, - role TEXT NOT NULL CHECK (role IN ('user', 'assistant')), - content TEXT NOT NULL, - output JSONB, - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Source Library -CREATE TABLE sources ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, - title TEXT NOT NULL, - content TEXT NOT NULL, - type TEXT NOT NULL CHECK (type IN ('url', 'file', 'text')), - origin TEXT, - is_active BOOLEAN DEFAULT TRUE, - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Row Level Security -ALTER TABLE saved_cases ENABLE ROW LEVEL SECURITY; -ALTER TABLE saved_attacks ENABLE ROW LEVEL SECURITY; -ALTER TABLE saved_brainstorms ENABLE ROW LEVEL SECURITY; -ALTER TABLE saved_factoids ENABLE ROW LEVEL SECURITY; -ALTER TABLE saved_chats ENABLE ROW LEVEL SECURITY; -ALTER TABLE chat_messages ENABLE ROW LEVEL SECURITY; -ALTER TABLE sources ENABLE ROW LEVEL SECURITY; - --- Policies: Users can only access their own data -CREATE POLICY "Users can access own data" ON saved_cases - FOR ALL USING (auth.uid() = user_id); - -CREATE POLICY "Users can access own data" ON saved_attacks - FOR ALL USING (auth.uid() = user_id); - --- ... repeat for other tables -``` - ---- - -## Authentication - -### Supabase Auth with Google OAuth - -```typescript -// src/lib/supabase/client.ts -import { createClient } from '@supabase/supabase-js'; - -const supabaseUrl = 'https://YOUR_PROJECT.supabase.co'; -const supabaseAnonKey = 'YOUR_ANON_KEY'; - -export const supabase = createClient(supabaseUrl, supabaseAnonKey); - -// Login with Google -async function signInWithGoogle() { - const { error } = await supabase.auth.signInWithOAuth({ - provider: 'google', - options: { - redirectTo: `${window.location.origin}/` - } - }); -} - -// Logout -async function signOut() { - await supabase.auth.signOut(); -} - -// Get current user -async function getUser() { - const { data: { user } } = await supabase.auth.getUser(); - return user; -} -``` - -### Auth Provider Component - -```tsx -// src/components/AuthProvider.tsx -function AuthProvider({ children }) { - const [user, setUser] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - // Get initial session - supabase.auth.getSession().then(({ data: { session } }) => { - setUser(session?.user ?? null); - setLoading(false); - }); - - // Listen for auth changes - const { data: { subscription } } = supabase.auth.onAuthStateChange( - (_event, session) => { - setUser(session?.user ?? null); - } - ); - - return () => subscription.unsubscribe(); - }, []); - - if (loading) return ; - if (!user) return ; - - return children; -} -``` - ---- - -## AI Integration - -### Client-Side AI Calls - -```typescript -// src/lib/ai/client.ts -import { supabase } from '@/lib/supabase/client'; - -export async function callAI( - feature: 'generate' | 'attack' | 'brainstorm' | 'factoids' | 'ask', - input: any, - config: ProviderConfig, - options?: { - sources?: Source[]; - history?: Message[]; - } -): Promise { - const { data: { session } } = await supabase.auth.getSession(); - if (!session) throw new Error('Not authenticated'); - - const response = await fetch( - `${import.meta.env.VITE_SUPABASE_URL}/functions/v1/ai`, - { - method: 'POST', - headers: { - 'Authorization': `Bearer ${session.access_token}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - feature, - input, - config, - sources: options?.sources, - history: options?.history, - }), - } - ); - - if (!response.ok) { - const error = await response.json(); - throw new Error(error.message || 'AI request failed'); - } - - return response.json(); -} -``` - -### Provider Configuration - -```typescript -interface ProviderConfig { - provider: 'openai' | 'google' | 'anthropic'; - model: string; - temperature: number; -} - -// Default configurations -const DEFAULT_CONFIGS = { - generate: { - provider: 'google', - model: 'gemini-2.0-flash', - temperature: 0.7, - }, - attack: { - provider: 'google', - model: 'gemini-2.0-flash', - temperature: 0.5, - }, - // ... etc -}; -``` - ---- - -## Security - -### Security Layers - -| Layer | Implementation | Description | -|-------|----------------|-------------| -| **API Keys** | Supabase secrets | Never exposed to browser | -| **Authentication** | Supabase Auth + JWT | Required for all Edge Function calls | -| **Input Sanitization** | Regex patterns | Detects prompt injection attempts | -| **Request Limits** | Input length limits | MAX_INPUT_LENGTH = 50000 | -| **URL Validation** | Protocol + domain checks | Blocks javascript:, data: URLs | -| **File Validation** | Size + type + extension | 10MB limit, allowed extensions only | -| **CSP Headers** | Nginx configuration | Strict Content Security Policy | -| **File Blocking** | Nginx rules | Blocks .md, .env, .json access | - -### Nginx Security Headers - -```nginx -add_header Content-Security-Policy " - default-src 'self'; - script-src 'self' 'unsafe-inline'; - style-src 'self' 'unsafe-inline'; - img-src 'self' data: https:; - font-src 'self' data: https:; - connect-src 'self' - https://*.supabase.co - https://api.openai.com - https://api.anthropic.com - https://generativelanguage.googleapis.com - https://r.jina.ai - https://s.jina.ai; - frame-src 'self' https://accounts.google.com; - frame-ancestors 'none'; -" always; - -add_header X-Frame-Options "SAMEORIGIN" always; -add_header X-Content-Type-Options "nosniff" always; -add_header Referrer-Policy "strict-origin-when-cross-origin" always; -``` +The full testing matrix, staging requirements, artifact behavior, and exact browser commands are in [`docs/testing.md`](docs/testing.md). -### File Blocking - -```nginx -# Allow robots.txt -location = /robots.txt { - allow all; -} - -# Block sensitive files -location ~* \.(md|txt|env|log|yml|yaml|json|lock|git|gitignore|dockerignore)$ { - deny all; - return 404; -} - -# Block hidden files -location ~ /\. { - deny all; - return 404; -} -``` - ---- - -## Deployment +## Stack -### Docker Build +The client uses React 18, TypeScript, Vite 7, React Router 7, Zustand, TanStack Query, Tailwind CSS, Radix-based UI components, Zod, Vitest, and Playwright. -```dockerfile -# Dockerfile -# Stage 1: Build -FROM node:22-alpine AS builder -WORKDIR /app -COPY package*.json ./ -RUN npm ci -COPY . . -RUN npm run build - -# Stage 2: Production -FROM nginx:alpine -COPY --from=builder /app/dist /usr/share/nginx/html -COPY nginx.conf /etc/nginx/conf.d/default.conf -EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] -``` +The backend uses Supabase Auth, PostgreSQL, Row Level Security, migrations, pgTAP, and Deno Edge Functions. Server-side integrations include supported AI providers, research services, Stripe, and Google Docs export. Client observability uses Sentry, PostHog, Vercel Analytics, and Vercel Speed Insights. -### Production Deployment +Production assets are built with Node 22 and served by Nginx. GitHub Actions owns quality, database, browser, staging, and image-packaging workflows. -```bash -# Build Docker image -docker build -t debate-trainer . - -# Run container -docker run -d \ - -p 3001:80 \ - --name debate-app \ - --restart unless-stopped \ - debate-trainer -``` +Versions and scripts come from [`package.json`](package.json). Do not maintain a second dependency catalog in documentation. -### Raspberry Pi + Watchtower +## Architecture at a glance -```bash -# 1. Run Watchtower for auto-updates -docker run -d \ - --name watchtower \ - -v /var/run/docker.sock:/var/run/docker.sock \ - containrrr/watchtower --interval 300 - -# 2. Run the app from Docker Hub -docker run -d \ - -p 3001:80 \ - --name debate-app \ - --restart unless-stopped \ - youruser/debate-trainer:latest +```text +Browser or Capacitor shell + -> React routes and feature components + -> Zustand stores and TanStack Query + -> Supabase Auth, database, RPCs, and Edge Functions + -> AI providers, research, Stripe, and Google APIs ``` -### Cloudflare Tunnel +Important boundaries: -```bash -# Install cloudflared -brew install cloudflared +- [`src/main.tsx`](src/main.tsx) starts the client, observability, native runtime, service worker, and offline preloading. +- [`src/App.tsx`](src/App.tsx) defines public and authenticated routes. +- [`src/lib/supabase/client.ts`](src/lib/supabase/client.ts) creates the environment-specific Supabase client. +- [`src/lib/stores/`](src/lib/stores/) owns persisted client state and server hydration. +- [`src/lib/ai/client.ts`](src/lib/ai/client.ts) is the browser's AI request boundary. +- [`supabase/functions/`](supabase/functions/) contains server-side request handlers. +- [`supabase/functions/_shared/`](supabase/functions/_shared/) contains shared backend auth, validation, research, pricing, and prompt logic. +- [`supabase/migrations/`](supabase/migrations/) is the database history and schema source of truth. -# Create tunnel -cloudflared tunnel create debate-pi +Read [`docs/architecture.md`](docs/architecture.md) before changing boundaries or tracing a request across the system. -# Configure DNS -cloudflared tunnel route dns debate-pi debate.yourdomain.com +## Repository map -# Run tunnel -cloudflared tunnel run --url http://localhost:3001 debate-pi +```text +src/ React client, stores, hooks, schemas, and tests +supabase/functions/ Deno Edge Functions and shared backend modules +supabase/migrations/ Ordered PostgreSQL migrations +supabase/tests/ Database and policy tests +public/ Static pages, PWA assets, and machine-readable files +e2e/ Playwright browser and protected staging tests +ios/ Capacitor-generated Xcode project +.github/workflows/ Quality, database, browser, staging, and packaging CI +docs/ Current guides, decisions, and dated evidence +plans/ Active implementation plans and backlog ``` -### GitHub Actions CI/CD - -```yaml -# .github/workflows/docker-publish.yml -name: Docker Build and Push - -on: - push: - branches: [main] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Build and push - uses: docker/build-push-action@v5 - with: - context: . - push: true - tags: youruser/debate-trainer:latest -``` +This map is intentionally shallow. Use source search for component-level discovery instead of expanding it into an exhaustive tree. ---- +## Authentication and environments -## Development Guide +The client resolves its Supabase environment in [`src/lib/supabase/env.ts`](src/lib/supabase/env.ts). Public project URLs and anon keys are client configuration, while provider keys, service-role credentials, webhook secrets, and integrity secrets stay in server-side environments. -### Available Commands +Authentication uses Supabase sessions with PKCE and environment-specific browser storage. Protected `/app/*` routes require a session. Server-side authorization remains the source of truth for admin access, paid features, data ownership, and rate limits. -| Command | Description | -|---------|-------------| -| `npm install` | Install dependencies | -| `npm run dev` | Start dev server (port 8080) | -| `npm run build` | Production build to `dist/` | -| `npm run build:dev` | Development build with source maps | -| `npm run ios:build` | Build web assets and sync the iOS app | -| `npm run ios:open` | Open the native iOS project in Xcode | -| `npm run preview` | Preview production build | -| `npm run lint` | Run ESLint | -| `npm test` | Run tests once | -| `npm run test:watch` | Run tests in watch mode | +The checked-in [`.env.example`](.env.example) contains optional client observability settings. Never commit real credentials or copy them into documentation. -### Coding Conventions +## Data and state -**TypeScript:** -- 2-space indentation -- Named exports preferred -- Use `@/` alias for `src/` imports -- Zod for runtime validation +The session store manages generated work, chats, sources, folders, flowcharts, and form state. The settings store manages user preferences and provider selections. The sparring store keeps active round state in session storage and hydrates completed sessions from Supabase. -**React:** -- Functional components with hooks -- React Hook Form for forms -- shadcn/ui for UI components -- TailwindCSS for styling +Supabase tables, RPCs, policies, and triggers are defined by migrations. Do not use a README schema example as migration authority. -**File Naming:** -- `PascalCase.tsx` for components -- `kebab-case.ts` for utilities -- `*.test.ts` for tests +For backend ownership and local database work, read [`supabase/README.md`](supabase/README.md). -### Adding a New Feature +## AI and research requests -1. **Create Zod Schema** (`src/lib/ai/schemas/newfeature.ts`) -2. **Update Edge Function** (`supabase/functions/ai/index.ts`) -3. **Create Form Component** (`src/components/features/newfeature/NewFeatureForm.tsx`) -4. **Create Results Component** (`src/components/features/newfeature/NewFeatureResults.tsx`) -5. **Create Page** (`src/pages/NewFeaturePage.tsx`) -6. **Add Route** (`src/App.tsx`) -7. **Update Stores** (`src/lib/stores/session-store.ts`) +The browser never sends provider secrets. It sends authenticated requests to `ai` or `ai-stream` Edge Functions. Those handlers enforce supported models and controls, check access and limits, assemble source context, call providers, validate output, and account for usage. ---- +The non-streaming endpoint serves structured tools and practice modes. The streaming endpoint serves Ask through server-sent events. Import and research paths validate public URLs and apply request, content, and size limits. ## Testing -DebateLab uses Node 22, npm, Vitest, and Playwright. Install the locked dependency tree with `npm ci`. +Start with the smallest reproduction, then expand according to the blast radius. -Run the full credential-free verification ladder with one command: +The normal credential-free gate is: ```bash npm run verify ``` -It runs ESLint, TypeScript, the full selected-scope coverage suite, native Deno Edge Function tests, the production build, and desktop plus mobile Chromium against an isolated Vite server on `127.0.0.1:18080`. The browser runner never reuses an existing server. Failure traces, screenshots, videos, and the HTML report are written to ignored `test-results/` and `playwright-report/` directories. +It runs documentation checks, lint, type checking, selected-scope coverage, native Edge Function tests, a production build, and desktop plus mobile Chromium against a fresh server on `127.0.0.1:18080`. -Useful focused commands: +Database migrations and RLS policies use a separate local Supabase gate. Protected staging uses a real staging session and requires all protected values together. It must never point at production. -```bash -# Unit and Supabase function tests with enforced coverage -npm test -- --coverage - -# Native Deno Edge Function tests, including the exported handler boundary -npm run test:edge - -# Database migration replay and pgTAP policy tests, with local Supabase running -supabase db start -npm run test:db -supabase stop --no-backup +Read [`docs/testing.md`](docs/testing.md) before running database, browser, WebKit, or staging suites. -# One focused unit test -npx vitest run src/test/security/url-validation.test.ts +## iOS -# Credential-free Chromium browser suite -CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 npm run test:e2e - -# Additional Safari-engine smoke gate -npx playwright install --with-deps webkit -CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 npm run test:e2e:webkit -``` - -Protected staging verification uses a real session, staging data, and one short AI request. It requires the staging URL, matching anon key, and dedicated demo code. It rejects the production project and deletes only the uniquely named source it creates. +The iOS app is a Capacitor shell around the Vite build. ```bash -CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 \ -STAGING_SUPABASE_URL= \ -STAGING_SUPABASE_ANON_KEY= \ -STAGING_DEMO_CODE= \ -npm run test:e2e:staging -``` - -Temporary auth state lives under `test-results/.auth/`. Staging disables traces, screenshots, and video, and its workflow uploads no browser artifacts. GitHub exposes staging secrets only to the protected `staging` environment on `main`, and only after `STAGING_E2E_ENABLED=true` is set. Pull requests remain credential-free. - -A green local run does not mean the current GitHub head passed. Check the PR run itself. If GitHub Actions cannot start because of billing or spending limits, report that as an external exception. For bugs, add a failing reproduction first when the repository has a suitable test layer, and state plainly when browser or real-staging proof did not run. - ---- - -## API Reference - -### Edge Function Endpoints - -#### POST `/functions/v1/ai` - -**Headers:** -``` -Authorization: Bearer -Content-Type: application/json -``` - -**Request Body:** -```json -{ - "feature": "generate", - "input": { - "resolution": "This House would ban social media for children", - "side": "affirmative", - "numContentions": 3 - }, - "config": { - "provider": "google", - "model": "gemini-2.0-flash", - "temperature": 0.7 - }, - "sources": [] -} -``` - -**Response:** -```json -{ - "introduction": "...", - "caseStructure": { - "keyDefinitions": [...], - "contentions": [...] - }, - "conclusion": "...", - "suggestedCrossExQuestions": [...], - "potentialWeaknesses": [...] -} +npm run ios:build +npm run ios:open ``` ---- - -## Configuration +The bundle identifier is `com.adam.debatelab`, and the app callback scheme is `debatelab://`. The native build uses a relative Vite base. OAuth providers still redirect through the Supabase HTTPS callback before returning to the app. -### Environment Variables +Generated files under the Xcode project should be changed through Capacitor or Xcode workflows, not rewritten as ordinary web source. -| Variable | Description | Where | -|----------|-------------|-------| -| `VITE_SUPABASE_URL` | Supabase project URL | Frontend | -| `VITE_SUPABASE_ANON_KEY` | Supabase anonymous key | Frontend | -| `OPENAI_API_KEY` | OpenAI API key | Supabase secrets | -| `GOOGLE_API_KEY` | Google AI API key | Supabase secrets | -| `ANTHROPIC_API_KEY` | Anthropic API key | Supabase secrets | -| `JINA_API_KEY` | Jina AI API key | Supabase secrets | -| `AI_DEFAULT_PROVIDER` | Default AI provider | Supabase secrets | -| `AI_DEFAULT_MODEL` | Default AI model | Supabase secrets | +## Security boundaries -### Administrator access +- Provider and service-role secrets stay in Supabase or protected CI environments. +- Edge Functions authenticate in the handler when gateway JWT verification is disabled. +- Stripe webhooks use signature verification, not user bearer authentication. +- Database ownership relies on RLS, authenticated user IDs, and narrowly scoped server operations. +- Source imports reject private-network targets and enforce redirect, timeout, content-type, and size limits. +- Staging browser runs disable traces, screenshots, and video because those artifacts can retain authenticated data. +- Production data, secrets, migrations, and function deployments require explicit authorization and a verified target. -Administrator capabilities are authorized server-side through the authenticated -admin endpoint. Do not rely on client-side flags or shared codes for access -control. +Security details belong near the enforcing code and migrations. Report suspected vulnerabilities privately rather than opening a public issue with exploit details. ---- +## CI and deployment -## Troubleshooting +[`.github/workflows/deploy.yml`](.github/workflows/deploy.yml) runs the credential-free quality gate. Pull requests also run local database and Chromium jobs. Non-PR runs may package the built assets into a multi-architecture Docker image when registry credentials are available. -### Common Issues +[`.github/workflows/staging-e2e.yml`](.github/workflows/staging-e2e.yml) runs only for authorized main-branch staging conditions. A skipped staging job is not a staging pass. -**"Not authenticated" error:** -- Ensure you're logged in via Google OAuth -- Check that Supabase URL and anon key are correct -- Verify JWT token is being sent in headers +[`Dockerfile`](Dockerfile) builds `dist/` with Node 22 and serves it through Nginx. [`nginx.conf`](nginx.conf) owns SPA routing, cache policy, indexing rules, and response security headers. -**"AI request failed" error:** -- Check Supabase Edge Function logs -- Verify API keys are set as secrets -- Ensure Edge Function is deployed +Read [`docs/operations.md`](docs/operations.md) before changing CI, Docker, Nginx, staging, CORS, migrations, Edge Functions, or production behavior. -**Blank page in development:** -- Check browser console for errors -- Verify Supabase client is configured -- Ensure `npm run dev` is running +## Documentation -**Docker build fails:** -- Ensure Node.js 22.x is used -- Clear node_modules and package-lock.json -- Check Dockerfile syntax +[`docs/README.md`](docs/README.md) is the documentation index. The main current documents are: -### Viewing Logs - -```bash -# Supabase Edge Function logs -supabase functions logs ai +- [`docs/architecture.md`](docs/architecture.md) +- [`docs/development.md`](docs/development.md) +- [`docs/testing.md`](docs/testing.md) +- [`docs/operations.md`](docs/operations.md) +- [`supabase/README.md`](supabase/README.md) +- [`plans/README.md`](plans/README.md) -# Docker container logs -docker logs debate-app - -# Nginx access logs (inside container) -docker exec debate-app cat /var/log/nginx/access.log -``` - ---- +Dated SEO notes, pricing snapshots, security scans, plans, and generated files have different freshness rules. Do not treat them as current operational instructions. ## Contributing -1. **Fork** the repository -2. **Create** a feature branch (`git checkout -b feature/amazing-feature`) -3. **Commit** your changes (`git commit -m 'feat: Add amazing feature'`) -4. **Push** to the branch (`git push origin feature/amazing-feature`) -5. **Open** a Pull Request - -### Commit Message Format - -``` -type: subject - -body (optional) - -footer (optional) -``` - -Types: `feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore` - ---- - -## Roadmap - -See [REVIEW_AND_ROADMAP.md](./REVIEW_AND_ROADMAP.md) for detailed roadmap. - -### Upcoming Features - -- [ ] Team workspaces and sharing -- [ ] Usage analytics dashboard -- [x] Offline viewing foundation (PWA app shell, route chunk preloading, local saved work) -- [ ] AI debate partner (voice practice) -- [ ] Evidence database with semantic search -- [ ] Mobile apps (iOS/Android) - ---- - -## License - -MIT License - see LICENSE file for details. - ---- - -## Acknowledgments - -- [shadcn/ui](https://ui.shadcn.com/) for beautiful components -- [Supabase](https://supabase.com/) for backend infrastructure -- [Jina AI](https://jina.ai/) for web search and reading -- [OpenAI](https://openai.com/), [Google](https://ai.google/), [Anthropic](https://anthropic.com/) for AI models - ---- +1. Work in a feature branch or assigned worktree. +2. Preserve unrelated changes. +3. Add a failing reproduction before fixing a bug when a suitable test layer exists. +4. Run focused checks while working. +5. Run the relevant full gate before requesting review. +6. Keep documentation and source-owner links current when commands or boundaries change. +7. Do not push, deploy, migrate, or change production state without authorization. -**Built with ❀️ for competitive debaters worldwide.** +The detailed agent contract is in [`AGENTS.md`](AGENTS.md). diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..699d650e3 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,64 @@ +# DebateLab documentation + +This directory uses progressive disclosure. Start with the smallest document that answers the task instead of loading every file. + +## Freshness labels + +- **Current:** maintained operational or architectural guidance. Update it with the owning source. +- **Plan:** proposed work. Revalidate it against the current branch before execution. +- **Evidence snapshot:** captured facts from a named date or revision. Do not treat it as current state. +- **Generated:** produced by a tool or platform. Change the source or generator instead. +- **Historical:** retained context about past work. It may contain retired results or assumptions, but it must not preserve actionable retired checkout commands. + +New maintained Markdown is checked by `npm run docs:check` unless it lives under an explicit generated, evidence, security-scan, plan, or design-spec prefix. + +## Understand the system + +- [Architecture](architecture.md), current. Open before changing subsystem boundaries or tracing browser, data, AI, auth, billing, observability, PWA, or iOS flows. +- [Agent guide](../AGENTS.md), current. Always-loaded commands, safety rules, and routing. +- [Root README](../README.md), current. Product overview and human quick start. + +## Set up and develop + +- [Development guide](development.md), current. Open for installation, environment behavior, linked worktrees, frontend, backend, database, or iOS workflows. +- [Supabase backend](../supabase/README.md), current. Open before changing Edge Functions, shared backend modules, migrations, RLS, RPCs, or backend secrets. + +## Test and verify + +- [Testing guide](testing.md), current. Open before running coverage, Edge Function, database, browser, WebKit, or protected staging gates. +- [`package.json`](../package.json) owns npm script definitions. +- [`vitest.config.ts`](../vitest.config.ts) and [`playwright.config.ts`](../playwright.config.ts) own test selection and browser behavior. + +## Operate or deploy + +- [Operations guide](operations.md), current. Open before changing CI, Docker, Nginx, staging, CORS, migrations, Edge Function deployment, rollback, or production state. +- [Quality and packaging workflow](../.github/workflows/deploy.yml) and [protected staging workflow](../.github/workflows/staging-e2e.yml) are the CI sources of truth. + +## Plans + +- [Plan index](../plans/README.md), plan. It records active proposals and the backlog. +- Individual plans describe intent at a past revision. Check their branch, commit, paths, and assumptions before using them. + +## Evidence and historical records + +- [`pricing-snapshots/`](pricing-snapshots/) contains immutable provider-pricing evidence used by cost accounting. +- [SEO work log](seo-debatelab-2026-07-05.md), [SEO escalation](seo-escalation-2026-07-05.md), and [authority opportunities](seo-authority-opportunities-2026-07-05.md) are historical snapshots from 2026-07-05. +- [Security scan 06d93a47](../security/scans/2026-08-19-codex-security-06d93a47/README.md) is evidence for one old revision, not current security proof. + +## Decisions + +- [Agent-friendly documentation](decisions/001-agent-friendly-documentation.md) explains why DebateLab keeps root instructions short and routes deeper work to focused documents. + +## Maintenance rule + +Operational facts have one owner: + +- scripts and runtime versions: [`package.json`](../package.json) +- routes: [`src/App.tsx`](../src/App.tsx) +- test behavior: Vitest and Playwright config +- CI behavior: `.github/workflows/` +- database behavior: `supabase/migrations/` and database tests +- Edge Function behavior: `supabase/functions/` +- packaging and HTTP policy: [`Dockerfile`](../Dockerfile) and [`nginx.conf`](../nginx.conf) + +When an owner changes, update only the focused document that explains that fact. Prefer links over copied catalogs. diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 000000000..773be6719 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,107 @@ +# Architecture + +**Status:** Current. Verify implementation details against the linked source files. + +## System context + +DebateLab is a browser application with an optional Capacitor iOS shell. The client uses Supabase for sessions, persisted user data, database RPCs, and Edge Functions. Edge Functions keep privileged credentials server-side and integrate with AI providers, research services, Stripe, and Google APIs. + +```text +Browser or Capacitor + -> React routes, feature components, stores + -> Supabase Auth, PostgreSQL, RPCs, Edge Functions + -> AI providers, research, Stripe, Google Docs +``` + +## Client startup and routing + +[`src/main.tsx`](../src/main.tsx) mounts the application and starts native runtime handling, observability, native URL handling, service-worker registration, and offline route preloading. Vercel browser telemetry is disabled inside the native shell. + +[`src/App.tsx`](../src/App.tsx) owns routes and top-level providers. Pages are lazy loaded. Public routes cover marketing, legal pages, login, sources, and shared content. `/app/*` routes pass through `RequireAuth` and include the main tools, history, library, timer, sparring, flowchart, settings, and administration. + +`AuthProvider`, TanStack Query, theme and motion providers, toast systems, the offline indicator, and the route error boundary wrap the routed application. + +## Client state + +[`src/lib/stores/session-store.ts`](../src/lib/stores/session-store.ts) owns active AI results, conversations, saved work, sources, folders, flowcharts, autosave state, and persisted form state. It persists selected local state and hydrates authenticated rows from Supabase. + +[`src/lib/stores/settings-store.ts`](../src/lib/stores/settings-store.ts) owns language, theme, feature provider settings, prompts, and research preferences. It persists locally and synchronizes authenticated settings. + +[`src/lib/stores/sparring-store.ts`](../src/lib/stores/sparring-store.ts) owns active sparring rounds in session storage and hydrates completed sessions from Supabase. + +[`src/lib/supabase/AuthProvider.tsx`](../src/lib/supabase/AuthProvider.tsx) coordinates store hydration and reset across authentication changes. Store code is not the database schema source of truth; migrations are. + +## Supabase environments and sessions + +[`src/lib/supabase/env.ts`](../src/lib/supabase/env.ts) resolves production or staging client configuration. Public URLs and anon keys are browser configuration. Do not copy their values into docs. + +[`src/lib/supabase/client.ts`](../src/lib/supabase/client.ts) creates the client with persistent sessions, automatic refresh, PKCE, and environment-specific storage keys. + +Client-side environment selection is a developer convenience, not authorization. Edge Functions and database policies must enforce access independently. + +## AI request flow + +[`src/lib/ai/client.ts`](../src/lib/ai/client.ts) is the browser AI boundary. + +1. A feature page validates input and calls a feature wrapper. +2. The client reads the current Supabase session. +3. It invokes `ai` or `ai-stream` with selected controls and active source context. +4. The Edge Function validates authentication, input bounds, supported model and control combinations, access limits, and feature-specific rules. +5. The backend optionally gathers research, builds prompts, calls a provider, validates output, and records usage. +6. The client validates or transforms the response and updates the relevant store. + +[`supabase/functions/ai/index.ts`](../supabase/functions/ai/index.ts) serves non-streaming structured tools and practice modes. [`supabase/functions/ai-stream/index.ts`](../supabase/functions/ai-stream/index.ts) serves Ask over server-sent events. + +A configured mock provider bypasses Supabase for local and test behavior. It is not authenticated backend evidence. + +## Research and source import + +Shared research, source ranking, URL validation, and prompt construction live under [`supabase/functions/_shared/`](../supabase/functions/_shared/). + +The import endpoint validates public HTTP URLs, resolves redirect targets, blocks private networks, and enforces response timeout, content type, and size limits. Research paths bound source count and content before prompt construction. + +## Database boundary + +[`supabase/migrations/`](../supabase/migrations/) owns schema history, RLS policies, functions, triggers, subscriptions, usage accounting, sharing, sparring, announcements, and demo access. + +The browser uses user-scoped Supabase access. Privileged Edge Function operations use server credentials only after handler-level checks. Shared backend auth helpers live in [`supabase/functions/_shared/auth.ts`](../supabase/functions/_shared/auth.ts). + +Do not infer current schema from old README examples or store query names. Replay migrations and run pgTAP tests for database proof. + +## Authentication and authorization + +Supabase Auth owns user sessions. Protected routes provide a client navigation boundary, but server authorization is decisive. + +- RLS owns row visibility and mutation rights. +- Shared backend helpers validate bearer tokens and admin roles. +- Admin endpoints verify server-side roles. +- Stripe webhook requests use Stripe signatures rather than user sessions. +- Demo-code login is a pre-session flow with hashing, rate limits, bounded use, and a rule that demo access cannot grant administrator status. + +## Billing and usage + +Checkout and portal functions create Stripe sessions after authenticated ownership and redirect validation. The webhook updates subscription state and records event processing for idempotency. AI handlers record provider usage and calculated cost using shared pricing logic. + +Provider pricing evidence is retained under [`docs/pricing-snapshots/`](pricing-snapshots/). The snapshots are evidence, not runtime configuration. + +## Observability + +Client startup initializes Sentry and PostHog when configured. Product events and exceptions use [`src/lib/observability.ts`](../src/lib/observability.ts). Vercel Analytics and Speed Insights run only in the web build. + +CI and staging must not upload authenticated browser captures. Logging must not expose tokens, provider keys, user content, or protected staging values. + +## PWA and offline behavior + +The service worker and manifest provide installable web assets and an offline app shell. Client startup registers the service worker and preloads route chunks. Offline support does not make authenticated Supabase or AI operations available without a network. + +## iOS shell + +[`capacitor.config.ts`](../capacitor.config.ts) owns the native app identifier, build directory, schemes, and native plugin settings. The iOS app wraps the same Vite output using a relative asset base. + +OAuth still travels through the provider and Supabase HTTPS callback before returning to the app callback scheme. Native URL handling lives in the client startup path. + +## Deployment boundary + +[`Dockerfile`](../Dockerfile) builds `dist/` with Node 22 and serves it through Nginx. [`nginx.conf`](../nginx.conf) owns SPA fallback, cache policy, crawler restrictions, and response security headers. GitHub workflow files own quality, staging, and packaging automation. + +Read [operations.md](operations.md) before changing deployment behavior and [testing.md](testing.md) before claiming a boundary works. diff --git a/docs/decisions/001-agent-friendly-documentation.md b/docs/decisions/001-agent-friendly-documentation.md new file mode 100644 index 000000000..f24f90e4e --- /dev/null +++ b/docs/decisions/001-agent-friendly-documentation.md @@ -0,0 +1,69 @@ +# Decision 001: Agent-friendly documentation + +**Status:** Accepted on 2026-09-06 + +## Decision + +DebateLab uses progressive disclosure for project documentation. + +- `AGENTS.md` is the small, always-loaded rule and routing file. +- `README.md` is the human quick start and product overview. +- Focused documents explain architecture, development, testing, operations, and Supabase. +- Plans and dated evidence state their freshness and are not treated as current implementation truth. +- Source files remain the owners of scripts, routes, workflows, schema, backend behavior, and packaging. + +New maintained Markdown is checked by default. A dependency-free script validates the local link forms DebateLab uses and rejects actionable retired checkout paths. + +## Context + +The previous root README had grown into a historical product specification. It copied framework versions, routes, backend topology, schema examples, provider models, deployment commands, and a large source tree. Several claims were obsolete, including the local directory name. Agents loaded the file, trusted stale instructions, and spent context on details that source search could answer more accurately. + +The former agent guide had better operational rules but mixed always-needed safety constraints with long staging procedures. It also described Superset helper files that were not present in the reviewed worktree. + +## Why progressive disclosure + +Always-loaded context is finite. Most tasks need repository identity, core commands, safety boundaries, a shallow system map, and a pointer to one deeper guide. Loading database, browser, staging, deployment, and product details for every edit wastes context and increases the chance that stale prose overrides current source. + +Focused documents make ownership visible and let a reader open only the relevant path. + +## Rejected alternatives + +### Keep one comprehensive README + +Rejected because a single file mixes onboarding, reference, explanation, operations, and historical product material. It becomes expensive to load and difficult to keep current. + +### Create copies for every coding agent + +Rejected because `CLAUDE.md`, Copilot instructions, and other parallel copies would repeat commands and safety rules. They would consume more context and drift independently. `AGENTS.md` is the shared project contract unless a future tool requires a small adapter that links back without copying content. + +### Generate class-level architecture documentation + +Rejected because component and class inventories change too often. Stable system and container boundaries are maintained; lower-level structure is discovered from source when needed. + +### Install a Markdown linter + +Rejected for this scope. DebateLab needs two narrow guarantees: supported local links resolve, and retired checkout commands do not return. A Node standard-library script provides those checks without another dependency or a misleading claim of full Markdown validation. + +## Maintenance rules + +- Keep `AGENTS.md` at or below 150 lines. +- Keep one owner for every operational fact. +- Link to source instead of copying dependency, route, schema, function, or model catalogs. +- Include new Markdown in checks by default. +- Use exact exclusions for generated, immutable evidence, security-scan, and plan/spec paths. +- Update the focused document when its owning source changes. +- Label plans, evidence snapshots, generated files, and historical notes clearly. +- Revalidate plans against the current branch before execution. + +## Official guidance consulted + +- [OpenAI Codex AGENTS.md guidance](https://developers.openai.com/codex/guides/agents-md) +- [OpenAI Codex customization](https://developers.openai.com/codex/concepts/customization) +- [Anthropic Claude Code memory and project instructions](https://code.claude.com/docs/en/memory) +- [Anthropic Agent Skills quickstart](https://docs.anthropic.com/en/docs/agents-and-tools/agent-skills/quickstart) +- [GitHub Copilot repository instructions](https://docs.github.com/en/copilot/how-tos/copilot-on-github/customize-copilot/add-custom-instructions/add-repository-instructions) +- [DiΓ‘taxis documentation framework](https://diataxis.fr/) +- [C4 model diagrams](https://c4model.com/diagrams) +- [Google technical writing guidance on short sentences](https://developers.google.com/tech-writing/one/short-sentences) + +The project applies these sources as guidance, not as runtime dependencies. diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 000000000..23389f2b9 --- /dev/null +++ b/docs/development.md @@ -0,0 +1,175 @@ +# Development guide + +**Status:** Current. Commands are owned by [`package.json`](../package.json). + +## Work in the assigned checkout + +Run: + +```bash +git rev-parse --show-toplevel +git branch --show-current +git status --short --branch +``` + +The first command is the active repository root. Stay there. Superset sessions are linked worktrees and may live under `~/.superset/worktrees/`, not `/home/codex/Projects/DebateLab`. + +Never overwrite unrelated changes. Do not switch to the primary checkout because an old note names it. + +## Prerequisites + +- Node.js 22.x +- npm and `package-lock.json` +- Deno 2.x for Edge Function tests +- Docker and Supabase CLI 2.115 or newer for database work +- Playwright browser binaries for browser gates +- Xcode and CocoaPods or Swift Package Manager support for native iOS work + +Install the locked JavaScript tree: + +```bash +npm ci +``` + +## Start the client + +Outside Superset, start Vite directly: + +```bash +npm run dev +``` + +[`vite.config.ts`](../vite.config.ts) defaults to port `8080` and maps `@` to `src/`. Superset's Run action calls `./.superset/dev.sh`, which allocates an available port from `8080-8179` for the current worktree and releases it through `./.superset/teardown.sh`. Do not assume a Superset workspace uses `8080`. + +For another explicit local port: + +```bash +npm run dev -- --host 127.0.0.1 --port 8081 +``` + +Automated browser tests own `127.0.0.1:18080` by default and always start a fresh server. + +## Environment behavior + +The checked-in [`.env.example`](../.env.example) contains optional client observability variables: + +- `VITE_SENTRY_DSN` +- `VITE_POSTHOG_KEY` +- `VITE_POSTHOG_HOST` + +The frontend Supabase environment is resolved by [`src/lib/supabase/env.ts`](../src/lib/supabase/env.ts), not by those variables. Public browser configuration is distinct from server secrets. + +Never put service-role keys, AI provider keys, Stripe secrets, webhook secrets, demo-code peppers, or integrity secrets in Vite variables. Any `VITE_*` value can reach the browser bundle. + +## Client workflow + +The main entry points are: + +- [`src/main.tsx`](../src/main.tsx) for startup and platform integration. +- [`src/App.tsx`](../src/App.tsx) for routes and top-level providers. +- `src/pages/` for route-level composition. +- `src/components/features/` for feature UI. +- `src/lib/stores/` for state and Supabase hydration. +- [`src/lib/ai/client.ts`](../src/lib/ai/client.ts) for AI requests. +- `src/lib/ai/schemas/` for response validation. + +Follow existing functional React, Zustand, Zod, and shadcn/Radix patterns. Reuse current helpers before adding another abstraction. + +Run the closest unit or component test while changing behavior. Run lint, type checking, and build before handing work off. + +## Edge Function workflow + +Edge Functions live under [`supabase/functions/`](../supabase/functions/). Shared server code belongs in `_shared` only when at least two functions use the same boundary or policy. + +Run native Deno tests with: + +```bash +npm run test:edge +``` + +These tests intentionally use Deno permissions declared in the npm script. Do not broaden permissions just to make a test pass. + +Handler authentication remains necessary where [`supabase/config.toml`](../supabase/config.toml) disables gateway JWT verification. See [`../supabase/README.md`](../supabase/README.md) for function groups and auth boundaries. + +## Database workflow + +Migrations live in [`supabase/migrations/`](../supabase/migrations/) and run in filename order. Database and policy tests live under [`supabase/tests/`](../supabase/tests/). + +In a Superset workspace, use the repository's locking wrapper: + +```bash +./.superset/db-gate.sh +``` + +Outside Superset, run the underlying commands directly: + +```bash +supabase db start +npm run test:db +supabase stop --no-backup +``` + +The default local stack begins at port `54321` and uses shared container names, so it is not parallel-safe across worktrees. The wrapper holds `~/.superset/debatelab-db.lock` and stops the stack on exit, including after a test failure. Outside Superset, coordinate ownership before starting it. The agent that starts the stack stops it before finishing, including after failures when safe. + +Never point local tests at staging or production. Never apply a migration to an unverified project. + +## Browser workflow + +[`playwright.config.ts`](../playwright.config.ts) starts Vite on an isolated port and refuses to reuse an existing server. + +```bash +CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 npm run test:e2e +``` + +Failure artifacts go to ignored `test-results/` and `playwright-report/`. Protected staging disables traces, screenshots, and video. + +Read [testing.md](testing.md) before running WebKit or staging. + +## iOS workflow + +```bash +npm run ios:build +npm run ios:open +``` + +The Capacitor build uses a relative Vite base and syncs `dist/` into the native project. [`capacitor.config.ts`](../capacitor.config.ts) owns the app ID, app name, schemes, and native settings. + +Do not hand-edit generated package documentation. Make native configuration changes through Capacitor or Xcode as appropriate. + +## Documentation workflow + +Run: + +```bash +npm run docs:check +``` + +The checker includes new Markdown by default. It validates supported local link forms and rejects actionable retired checkout paths. It does not validate remote URLs, Markdown syntax, or heading fragments. + +When a command or boundary changes, update the focused document that owns it. Avoid duplicating source trees, schema listings, dependency catalogs, or model lists. + +## Troubleshooting + +### The wrong repository path appears + +Run `git rev-parse --show-toplevel`. Use that result. Do not trust a copied `cd` command over the current worktree. + +### Port 8080 is busy + +Choose a free explicit development port. Do not point browser automation at an unrelated server. + +### Browser tests attach to old state + +Use the required `CI=1`, `PORT`, and `PLAYWRIGHT_BASE_URL` values. Playwright sets `reuseExistingServer: false`. + +### Local Supabase will not start + +Check whether another worktree owns the default containers or ports. Coordinate and stop the existing local stack rather than deleting containers blindly. + +### Authentication works in one environment only + +Check the active client environment and environment-specific browser storage. Do not copy production sessions into staging or alter server authorization to hide a client configuration error. + +### A staging test is missing + +All three staging values must be present together. Partial configuration is rejected. A disabled or skipped staging workflow is not a pass. diff --git a/docs/operations.md b/docs/operations.md new file mode 100644 index 000000000..d64d51bfe --- /dev/null +++ b/docs/operations.md @@ -0,0 +1,146 @@ +# Operations and deployment + +**Status:** Current. Workflow, packaging, and runtime configuration files remain authoritative. + +## Authorization boundary + +Reading state and running local checks are safe. Production deployment, production data mutation, migrations, Edge Function deployment, secret changes, billing changes, and destructive operations require explicit authorization for the concrete target and operation. + +Never infer authorization from access. Never use production to replace missing local or staging setup. + +## CI workflows + +[`.github/workflows/deploy.yml`](../.github/workflows/deploy.yml) owns credential-free quality, pull-request database and browser tests, asset upload, and conditional Docker packaging. + +The quality job uses Node 22, `npm ci`, and Deno 2.9.6, then runs: + +- `npm run docs:check` +- `npm run lint` +- `npm run typecheck` +- `npm test -- --coverage` +- `npm run test:edge` +- `npm run build` + +Pull requests also start isolated local Supabase for pgTAP and run desktop plus mobile Chromium on port `18080`. + +Non-PR runs upload `dist/` briefly and may build a `linux/amd64` and `linux/arm64` Docker image when registry credentials are present. A successful quality run does not by itself prove that an image was pushed. + +[`.github/workflows/staging-e2e.yml`](../.github/workflows/staging-e2e.yml) owns protected staging. It accepts eligible successful main-branch quality runs or a manual dispatch from `main`. It runs only when `STAGING_E2E_ENABLED=true` and all protected values exist. + +## Production image + +[`Dockerfile`](../Dockerfile) has two stages: + +1. Node 22 Alpine installs the locked dependency tree and runs the production build. +2. Nginx Alpine serves the resulting `dist/` directory on port 80. + +[`nginx.conf`](../nginx.conf) owns: + +- SPA fallback routing; +- cache policy for HTML, runtime configuration, hashed assets, and media; +- crawler restrictions for authenticated routes; +- sensitive-file and hidden-path blocking; +- Content Security Policy and response security headers. + +Update documentation when these files change. Do not paste their full configuration into another guide. + +## Staging contract + +Protected staging requires: + +- a project that is not production; +- a matching anon key; +- current migrations and required Edge Functions; +- required provider secrets; +- a dedicated bounded demo code; +- a protected GitHub environment with a main-only deployment rule; +- deployed CORS support for `http://127.0.0.1:18080` on the tested functions. + +The staging browser suite disables trace, screenshot, and video capture. It creates uniquely named data and deletes only what it created. + +Do not enable the staging repository variable before every prerequisite is present. A skipped staging job is not a pass. + +## Supabase changes + +Before applying a migration or deploying a function: + +1. resolve the linked project and confirm whether it is local, staging, or production; +2. inspect the exact migration or function diff; +3. capture the current remote baseline needed for rollback; +4. run the relevant local tests; +5. confirm CORS and authentication behavior; +6. apply one scoped operation; +7. verify the live result before continuing. + +Stop when the project target is ambiguous, rollback is unproven, or the operation exceeds authorization. + +Destructive statements such as `DROP` or `TRUNCATE`, secret rotation, project deletion, and broad unrelated function deployment always need separate explicit approval. + +## Edge Function deployment boundaries + +[`supabase/config.toml`](../supabase/config.toml) disables gateway JWT verification for functions that authenticate or validate requests in their own handlers. Deployment must preserve those handler checks. + +Auth patterns include: + +- bearer validation through shared helpers for user and admin APIs; +- Stripe signature validation for webhooks; +- bounded pre-session validation for demo-code login; +- short-lived Google token validation for Docs export. + +A successful deploy command proves upload only. Verify the behavior through the real endpoint or protected staging flow. + +## CORS + +Current functions mostly use local explicit origin allowlists. The exact list can differ by endpoint. Treat handler code as the source of truth. + +Before enabling protected browser staging, verify `http://127.0.0.1:18080` for `demo-code-login`, `ai`, `ai-stream`, `admin`, and `announcements`. Do not broaden origins to `*` merely to make a test pass. + +Native requests may use `capacitor://localhost`. Local development origins must remain bounded to intended ports and hosts. + +## Secrets + +Secret names may be documented; values may not. Server-side categories include: + +- Supabase service credentials; +- AI provider credentials; +- research provider credentials; +- Stripe API and webhook credentials; +- demo-code hashing material; +- sparring integrity material; +- Google export configuration. + +Never place server secrets in `VITE_*`, screenshots, traces, logs, comments, commits, or documentation. + +## Rollback + +Choose rollback before changing production: + +- Frontend image: retain the prior known-good image digest or deployment reference. +- Edge Function: retain the deployed source or artifact baseline and its environment assumptions. +- Migration: prefer forward corrective migrations. Do not assume an irreversible schema or data change can be rolled back. +- Configuration: record the prior value without exposing secrets. + +After rollback, verify the user-visible flow and relevant backend state. A process restart alone is not proof. + +## Live verification + +Match verification to the change: + +- frontend: load the deployed route and complete the affected browser flow; +- auth: use the intended environment and prove session refresh or callback behavior; +- database: query only the expected rows and prove RLS with user-scoped access; +- Edge Function: call the deployed endpoint with the intended auth class; +- billing: use the authorized test or staging mode and inspect webhook state; +- CORS: issue the browser-origin request that previously failed. + +Do not expose protected data in artifacts. Record the commit, environment, operation, and result. + +## Incident clues + +- Green quality plus missing image: Docker credentials may have been absent, so packaging was skipped. +- Green local E2E plus staging failure: schema, functions, provider access, demo code, or CORS may differ. +- Function returns unauthorized after deploy: check handler auth and environment propagation before changing gateway settings. +- Browser works on one route only: check Nginx SPA fallback and cache behavior. +- Native callback fails while web works: check Capacitor scheme and Supabase redirect allowlist. + +Read [testing.md](testing.md) for evidence rules and [`../supabase/README.md`](../supabase/README.md) for backend ownership. diff --git a/docs/seo-authority-opportunities-2026-07-05.md b/docs/seo-authority-opportunities-2026-07-05.md index 479abf65b..1252277a9 100644 --- a/docs/seo-authority-opportunities-2026-07-05.md +++ b/docs/seo-authority-opportunities-2026-07-05.md @@ -1,5 +1,7 @@ # DebateLab authority and backlink opportunities - 2026-07-05 +> **Historical snapshot:** This file records SEO observations and opportunities from 2026-07-05. Rankings, indexing state, URLs, and recommendations may have changed. Recheck live sources before acting. + ## Positioning to use consistently Because `DebateLab` collides with KIT DebateLab and `debatelab.ai`, use a disambiguated entity label whenever possible: diff --git a/docs/seo-debatelab-2026-07-05.md b/docs/seo-debatelab-2026-07-05.md index a45592d35..b4e818b4d 100644 --- a/docs/seo-debatelab-2026-07-05.md +++ b/docs/seo-debatelab-2026-07-05.md @@ -1,5 +1,7 @@ # DebateLab SEO work log - 2026-07-05 +> **Historical snapshot:** This file records SEO work and search evidence from 2026-07-05. It is not current ranking, indexing, or deployment proof. + ## Objective Make `https://debate.pi-stack.org` the strongest relevant result for the Google query `debatelab` without indexing private app surfaces or changing the authenticated app experience. diff --git a/docs/seo-escalation-2026-07-05.md b/docs/seo-escalation-2026-07-05.md index 9b756d348..161f3b70e 100644 --- a/docs/seo-escalation-2026-07-05.md +++ b/docs/seo-escalation-2026-07-05.md @@ -1,5 +1,7 @@ # DebateLab SEO escalation - 2026-07-05 +> **Historical snapshot:** This file records an escalation and search evidence from 2026-07-05. Recheck current Search Console, indexing, and ranking state before using it. + ## Why this escalation happened Adam correctly pointed out that the first SEO pass did **not** achieve the stated ranking objective: for the broad query `debatelab`, `https://debate.pi-stack.org` still was not appearing in the sampled top results. diff --git a/docs/superpowers/plans/2026-09-06-documentation-system.md b/docs/superpowers/plans/2026-09-06-documentation-system.md new file mode 100644 index 000000000..5a7cf65b6 --- /dev/null +++ b/docs/superpowers/plans/2026-09-06-documentation-system.md @@ -0,0 +1,209 @@ +# DebateLab documentation system implementation plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace stale DebateLab documentation with a concise, progressive-disclosure system and prevent the retired checkout path or broken local links from returning. + +**Architecture:** Keep `AGENTS.md` and `README.md` short, route specialized work to focused documents, and make checked-in source files the owners of operational facts. Add one dependency-free Node checker that includes Markdown by default, uses explicit exclusions, validates the repository's supported local-link forms, and runs locally and in PR CI. + +**Tech stack:** Markdown, Node.js 22 standard library, TypeScript 5.8, Vitest 4, npm, GitHub Actions. + +**Spec:** `docs/superpowers/specs/2026-09-06-documentation-system-design.md` + +## Global constraints + +- The current result of `git rev-parse --show-toplevel` and the assigned worktree are authoritative. +- Do not add dependencies. +- Keep `AGENTS.md` at or below 150 lines. +- Include new Markdown in checks by default; exclusions must be exact path prefixes. +- Do not alter product behavior, production infrastructure, secrets, migrations, pricing evidence, generated iOS docs, or public machine-readable assets. +- Do not claim remote URLs, Markdown syntax, or heading fragments are validated. + +--- + +### Task 1: Documentation drift gate + +**Files:** +- Create: `src/test/docs/check-docs.test.ts` +- Create: `scripts/check-docs.mjs` +- Modify: `package.json` +- Modify: `.github/workflows/deploy.yml` +- Modify: `.gitignore` + +**Interfaces:** +- Produces: `checkDocs(rootDir: string): Promise` exported from `scripts/check-docs.mjs`. +- Produces: `DocIssue` runtime objects with `file`, `line`, and `message` string properties. +- Produces: `npm run docs:check`, with exit code 0 when no issues exist and 1 when issues exist. + +- [ ] **Step 1: Write the failing tests** + +Create `src/test/docs/check-docs.test.ts`. Import `checkDocs` from `../../../scripts/check-docs.mjs`. Use `mkdtemp`, `mkdir`, `writeFile`, and `rm` from Node's standard library. Cover: + +1. A root `README.md` with an existing inline local link returns no issues. +2. A new Markdown file in an arbitrary directory is included by default. +3. `plans/README.md` is checked while another file under `plans/` is excluded. +4. Each exact excluded prefix is ignored. +5. `cd debate-navigator`, `/tmp/debate-navigator/file`, and `/home/codex/Projects/debate-navigator` produce stale-path issues. +6. Historical prose containing bare `debate-navigator` does not fail. +7. Missing inline and reference-definition targets fail. +8. Existing percent-encoded targets and targets with query or fragment suffixes pass. +9. HTTP(S), `mailto:`, `tel:`, `data:`, and fragment-only targets pass. + +- [ ] **Step 2: Run the focused test and confirm RED** + +Run `npx vitest run src/test/docs/check-docs.test.ts`. + +Expected result: failure because `scripts/check-docs.mjs` does not exist. + +- [ ] **Step 3: Implement the minimum checker** + +Create `scripts/check-docs.mjs` with: + +- exact excluded prefixes `.git/`, `node_modules/`, `dist/`, `coverage/`, `test-results/`, `playwright-report/`, `ios/App/CapApp-SPM/`, `docs/pricing-snapshots/`, `security/scans/`, `docs/superpowers/`, and `plans/`; +- an explicit `plans/README.md` inclusion before prefix exclusion; +- recursive `readdir` with directory entries; +- stale command/path regexes limited to actionable forms; +- inline-link and reference-definition extraction; +- local-target normalization that strips query/fragment, decodes percent encoding, and resolves from the document directory; +- executable CLI behavior guarded by comparison with `pathToFileURL(process.argv[1]).href`. + +Print each issue as `: ` and set `process.exitCode = 1` when issues exist. + +- [ ] **Step 4: Run the focused test and confirm GREEN** + +Run `npx vitest run src/test/docs/check-docs.test.ts`. + +Expected result: all documentation-check tests pass. + +- [ ] **Step 5: Wire local and CI gates** + +Add `"docs:check": "node scripts/check-docs.mjs"` to `package.json`. Insert `npm run docs:check` at the start of `verify`. Add a `Documentation` step after install and before lint in `.github/workflows/deploy.yml`. + +Update `.gitignore` to track `docs/**/*.md` while retaining the existing local-note ignores. Do not unignore generated or dependency output. + +- [ ] **Step 6: Verify the gate** + +Run `npm run docs:check`, `npm run lint`, and `npm run typecheck`. + +Expected result: all three exit 0 after documentation rewrites in Tasks 2 and 3. Before those rewrites, record stale-document findings and continue directly to Task 2. + +### Task 2: Current orientation and focused docs + +**Files:** +- Modify: `AGENTS.md` +- Rewrite: `README.md` +- Create: `docs/README.md` +- Create: `docs/architecture.md` +- Create: `docs/development.md` +- Create: `docs/testing.md` +- Create: `docs/operations.md` +- Create: `docs/decisions/001-agent-friendly-documentation.md` + +**Interfaces:** +- `AGENTS.md` is the always-loaded rule and routing file. +- `README.md` is the human quick start. +- `docs/README.md` is the on-demand documentation router. +- Operational facts link to their owning source files. + +- [ ] **Step 1: Map current source owners** + +Read only the entry points needed for factual claims: `src/App.tsx`, `src/main.tsx`, `src/lib/supabase/env.ts`, `src/lib/ai/client.ts`, `src/lib/stores/`, `supabase/config.toml`, `supabase/functions/`, `.github/workflows/`, `playwright.config.ts`, `vite.config.ts`, `Dockerfile`, `nginx.conf`, `.superset/`, and `capacitor.config.ts`. + +- [ ] **Step 2: Rewrite `AGENTS.md`** + +Keep it at or below 150 lines. Include: + +- current-worktree authority using `git rev-parse --show-toplevel`; +- Node 22, npm, and `npm ci`; +- ports 8080, isolated browser 18080, and local Supabase 54321; +- a five-line architecture map; +- focused commands and `npm run verify`; +- Superset worktree and database-lock safety; +- staging and secret prohibitions that must be known before opening deeper docs; +- routing links to architecture, development, testing, operations, Supabase, and plans. + +- [ ] **Step 3: Replace `README.md`** + +Keep it between 200 and 300 lines. Cover product purpose, current feature groups, quick start, current stack, high-level architecture, repository map, environment behavior, verification, security boundaries, deployment summary, iOS shell, documentation links, and contribution expectations. Remove obsolete DDL, provider-model lists, full source trees, Watchtower instructions, old workflow examples, and the missing roadmap link. + +- [ ] **Step 4: Write focused documentation** + +Create: + +- `docs/README.md`: current/plan/evidence/generated/historical labels and task-based routing. +- `docs/architecture.md`: browser, Supabase, AI, data, auth, billing, observability, PWA, and iOS boundaries with source entry points. +- `docs/development.md`: setup, environment resolution, Superset ports, frontend/backend/database/iOS workflows, and current troubleshooting. +- `docs/testing.md`: focused checks, full gate, DB gate, Chromium, WebKit, staging, artifacts, cleanup, and evidence limits. +- `docs/operations.md`: CI, Docker/Nginx packaging, staging contract, production authorization, CORS, migrations/functions, rollback, and live verification. +- `docs/decisions/001-agent-friendly-documentation.md`: progressive-disclosure decision, rejected duplicated-agent files and giant README approach, maintenance triggers, and official research links. + +- [ ] **Step 5: Check size and links** + +Run `npm run docs:check`. Confirm `AGENTS.md` is no more than 150 lines and `README.md` is between 200 and 300 lines. + +### Task 3: Supabase, plans, and historical status + +**Files:** +- Rewrite: `supabase/README.md` +- Modify: `plans/README.md` +- Move: `plans/done/bugs.txt` to `plans/bugs.md` +- Modify: `docs/seo-authority-opportunities-2026-07-05.md` +- Modify: `docs/seo-debatelab-2026-07-05.md` +- Modify: `docs/seo-escalation-2026-07-05.md` +- Modify: `security/scans/2026-08-19-codex-security-06d93a47/README.md` + +**Interfaces:** +- `supabase/README.md` owns backend orientation. +- `plans/README.md` owns plan status, not implementation truth. +- Historical files state their date and evidentiary limits at the top. + +- [ ] **Step 1: Rewrite Supabase orientation** + +Document function groups, shared modules, auth boundaries, migrations, pgTAP, local DB locking, environment/secret names without values, and local/staging/production rules. Link to testing and operations docs. + +- [ ] **Step 2: Reconcile plan status** + +Update `plans/README.md` with the current review date and the rule that every plan must be revalidated against current source and branch state. Move `plans/done/bugs.txt` to `plans/bugs.md` after checking its four entries still describe current code; remove resolved entries instead of preserving stale backlog. + +- [ ] **Step 3: Label historical evidence** + +Add a short historical-snapshot notice to each dated SEO note. Update the security-scan README to state that it covers revision `7305a87`, is not current proof, and that only `findings.json` is retained. Remove the broken `report.md` pointer. Search for inbound links to the moved backlog and removed report pointer and update any maintained link in the same change. + +- [ ] **Step 4: Run documentation validation** + +Run `npm run docs:check` and the focused Vitest test. + +Expected result: both exit 0. + +### Task 4: Repository verification and review + +**Files:** +- Review all files changed by Tasks 1 through 3. + +- [ ] **Step 1: Run focused checks** + +Run: + +```bash +npx vitest run src/test/docs/check-docs.test.ts +npm run docs:check +npm run lint +npm run typecheck +npm run build +``` + +Expected result: every command exits 0. + +- [ ] **Step 2: Run the credential-free full gate** + +Run `npm run verify`. + +Expected result: lint, typecheck, coverage, Edge Function tests, documentation checks, build, and desktop/mobile Chromium all pass. If browser dependencies or an external service block the run, record the exact failure without substituting mocks. + +- [ ] **Step 3: Inspect the final diff** + +Confirm no secrets, production mutation, generated files, immutable pricing evidence, or unrelated product code changed. Confirm `AGENTS.md` and `README.md` stay within their size limits and every changed factual claim names or links to its source owner. + +- [ ] **Step 4: Request independent review** + +Ask CodeRabbit or the configured reviewer to review correctness, stale duplication, link-check behavior, CI wiring, and context-window impact. Resolve every valid Critical or Warning finding and rerun affected checks. diff --git a/docs/superpowers/specs/2026-09-06-documentation-system-design.md b/docs/superpowers/specs/2026-09-06-documentation-system-design.md new file mode 100644 index 000000000..2c9b5c959 --- /dev/null +++ b/docs/superpowers/specs/2026-09-06-documentation-system-design.md @@ -0,0 +1,202 @@ +# DebateLab documentation system design + +**Status:** Approved in chat on 2026-09-06 + +## Goal + +Replace DebateLab's stale, duplicated documentation with a concise system that gives people and coding agents accurate orientation without loading the whole repository into context. + +The repository is DebateLab. Agents must derive the active checkout with `git rev-parse --show-toplevel` and operate in that current worktree. `/home/codex/Projects/DebateLab` is only the conventional primary checkout on Adam's VPS, not an instruction to leave an assigned worktree. + +## Problem + +The root `README.md` is a long historical product specification. It still names `debate-navigator`, documents obsolete setup commands, older framework versions, an incomplete route map, one Supabase function, an old database model, and retired CI and deployment examples. Agents read these claims as current instructions, choose the wrong directory, and spend context on information they could discover from source. + +`AGENTS.md` has the best current operational guidance, but it carries detailed staging procedures that most tasks do not need and does not route readers to focused documents. `supabase/README.md`, the plan index, dated SEO notes, and a historical security scan also need clearer status or current ownership. + +## Design principles + +1. Keep always-loaded instructions short. `AGENTS.md` contains durable rules, canonical commands, safety constraints, a shallow architecture map, and links that say when to read deeper documents. +2. Use progressive disclosure. Detailed setup, testing, operations, and backend procedures live in focused files and load only when the task needs them. +3. Keep one source for each fact. Commands come from `package.json`; CI behavior comes from workflow files; schema history comes from migrations; runtime behavior comes from source. Documentation links to those sources instead of copying large inventories. +4. Document non-obvious boundaries. Ports, staging isolation, authentication boundaries, deployment safety, shared state, and generated files belong in documentation because agents cannot safely infer them from one file. +5. Prefer stable maps. Architecture documentation covers system and container-level flows plus entry points. It does not maintain class-level diagrams or exhaustive file trees. +6. Treat drift as a defect. A dependency-free documentation check rejects the retired local directory name and broken local Markdown links. + +These principles follow current first-party guidance from OpenAI Codex, Anthropic Claude Code, GitHub Copilot, DiΓ‘taxis, C4, and Google's technical-writing course. The decision record will preserve the source links and project-specific reasoning. + +## Information architecture + +### Always loaded + +#### `AGENTS.md` + +Target: no more than 150 lines. The current 70-line file is the baseline, so the rewrite must earn every added line and should remain shorter where possible. + +Contents: + +- Repository identity plus the rule that `git rev-parse --show-toplevel` and the current assigned worktree are authoritative. The VPS primary-checkout path may appear only as labeled host context. +- Node, npm, and clean-install requirements. +- Normal development, browser, and local Supabase ports. +- Short system map and implementation entry points. +- Canonical focused checks and `npm run verify`. +- Hard rules for secrets, staging, production, browser evidence, and local Supabase concurrency. +- A document router that tells agents which focused file to read for development, testing, operations, backend, architecture, or plans. + +Detailed staging setup, full command explanations, and long procedures move out of this file. + +### Human entry point + +#### `README.md` + +Target: roughly 200 to 300 lines. + +Contents: + +- Product purpose and live application link. +- Current feature summary. +- Supported stack at a useful level, without a dependency inventory. +- `npm ci`, `npm run dev`, and the shortest useful verification command. +- Current high-level frontend, backend, database, and deployment map. +- Links to focused documentation. +- Security and contribution expectations at summary level. + +It must not include copied database DDL, provider model catalogs, a complete source tree, obsolete deployment recipes, or roadmap claims without an owner. + +### Documentation index + +#### `docs/README.md` + +A compact router grouped by job: + +- Understand the system. +- Set up and develop. +- Test and verify. +- Operate or deploy. +- Work on Supabase. +- Review active plans or historical evidence. + +Each link says when to open it. The index defines documentation freshness labels for readers: current, plan, evidence snapshot, generated, or historical. + +The checker uses one code-owned classification rule instead of duplicating a manifest. Every Markdown file is checked by default. It excludes only generated output, vendor documentation, immutable pricing evidence, historical security scans, and plan/spec artifacts that intentionally quote old state. `plans/README.md` remains included even though other plan files are excluded. Any new Markdown file outside an explicit excluded prefix is checked automatically. + +### Focused current documentation + +#### `docs/architecture.md` + +- System context and major containers. +- Browser-to-Supabase and AI request flows. +- State ownership and persistence boundaries. +- Authentication and authorization boundaries. +- Billing, observability, PWA, and native-shell boundaries. +- Stable entry-point paths and source-of-truth links. + +#### `docs/development.md` + +- Prerequisites and clean setup. +- Environment resolution without secret values. +- Superset worktree behavior and port allocation. +- Common frontend, Edge Function, database, and iOS workflows. +- Coding conventions that are not already enforced by tooling. +- Troubleshooting tied to current commands. + +#### `docs/testing.md` + +- Verification ladder and what each command proves. +- Focused unit, Edge Function, database, Chromium, WebKit, and staging gates. +- Local Supabase locking and cleanup. +- Credential handling and the limits of stub sessions. +- Difference between local evidence and current-head CI evidence. + +#### `docs/operations.md` + +- Current GitHub Actions workflow and Docker packaging. +- Staging environment contract. +- Production deployment boundaries and required authorization. +- CORS, migrations, Edge Functions, rollback evidence, and live verification. +- Links to workflow, Docker, Nginx, and Supabase configuration sources. + +#### `supabase/README.md` + +- Current function directory map grouped by responsibility. +- Shared modules and authentication model. +- Migration and pgTAP workflow. +- Required secret names without values. +- Local, staging, and production boundaries. +- Links to deeper testing and operations docs rather than copied procedures. + +### Decision record + +#### `docs/decisions/001-agent-friendly-documentation.md` + +A concise record of the progressive-disclosure decision, rejected alternatives, maintenance rules, and official source links. It is explanatory evidence, not an always-loaded instruction file. + +### Plans and historical material + +- `plans/README.md` remains the active plan index. It records the last reconciliation point and warns that every plan must be checked against current source before execution. +- `plans/done/bugs.txt` moves to `plans/bugs.md` only after each item is checked against current source. The misleading `done` location is removed. +- Pricing snapshots remain immutable evidence. +- Dated SEO notes receive a clear historical-snapshot banner and stay out of current guidance. +- The historical security scan either gets an accurate artifact index or loses its broken `report.md` pointer. It must not imply that an old revision proves current security. +- Generated iOS documentation and public machine-readable assets remain untouched unless a verified contradiction is found. + +## Documentation check + +Add `scripts/check-docs.mjs` using Node's standard library and expose it as `npm run docs:check`. + +The checker exports its core function for tests and uses a fail-closed file policy: + +1. Walk repository Markdown files and include new files by default. +2. Exclude only these exact repository-relative prefixes: `.git/`, `node_modules/`, `dist/`, `coverage/`, `test-results/`, `playwright-report/`, `ios/App/CapApp-SPM/`, `docs/pricing-snapshots/`, `security/scans/`, `docs/superpowers/`, and `plans/`. `plans/README.md` is an explicit included exception. These exclusions live in one constant in the checker, with comments naming the owner and reason. +3. Reject actionable retired checkout forms: `cd debate-navigator`, a path segment named `debate-navigator`, and `/home/codex/Projects/debate-navigator`. Historical prose may name the former repository only when it does not form a command or path. +4. Validate inline Markdown links and reference definitions. For local targets, strip query and fragment suffixes, decode percent-encoded paths, resolve relative to the document, and require the target file or directory to exist. Ignore HTTP(S), `mailto:`, `tel:`, `data:`, and fragment-only targets. Fragment-anchor validity is explicitly out of scope. +5. Print the document, line, and failing value. + +Put tests at `src/test/docs/check-docs.test.ts`, which the current Vitest include already executes. Tests use temporary directories and cover default inclusion, each excluded class, the plan-index exception, retired command/path forms, valid and missing inline links, reference definitions, percent-encoded paths, query/fragment suffixes, external schemes, and fragment-only links. + +Add `npm run docs:check` to `npm run verify` and as its own step in `.github/workflows/deploy.yml`. The explicit CI step is required because that workflow runs quality commands individually rather than invoking `npm run verify`. + +The check does not claim to validate Markdown syntax, remote URLs, heading fragments, or factual prose. Documentation names a finite set of copied operational facts and their owners: scripts and runtime versions come from `package.json`; test selection from Vitest and Playwright config; CI behavior from `.github/workflows/`; ports from Vite, Playwright, Supabase, and `.superset/` config; CORS from Edge Function shared code; packaging from `Dockerfile` and `nginx.conf`. Changes to an owning source require reviewing the linked document in the same change. + +## Scope and non-goals + +In scope: + +- Update `.gitignore` to track maintained `docs/**/*.md`; generated, evidence, and historical exclusions remain explicit rather than relying on the blanket `*.md` rule. +- Rewrite maintained orientation, development, testing, operations, and backend documentation. +- Correct stale repository identity and command claims. +- Add the focused documentation check and tests. +- Label or index existing plans and historical evidence accurately. + +Out of scope: + +- Product feature changes. +- Database migrations, Edge Function deployment, production changes, or secret changes. +- Rewriting immutable pricing evidence or generated/vendor files. +- Creating per-agent copies such as `CLAUDE.md` or Copilot instructions that duplicate `AGENTS.md`. +- Exhaustive API, component, route, dependency, or schema catalogs that source code already provides. + +## Verification + +Focused checks: + +- Run the documentation-check unit test. +- Run `npm run docs:check`. +- Search maintained guidance for stale repository names and obsolete setup commands. +- Review every local Markdown link reported by the checker. + +Repository checks: + +- Run `npm run lint` and `npm run typecheck` because the new checker and tests are JavaScript and package-script changes. +- Run the relevant Vitest suite. +- Run `npm run build` to confirm documentation and package-script edits do not disturb packaging. +- Run the full `npm run verify` gate if time and installed browser dependencies permit. Report any browser or credential-bound gate that did not run. + +## Success criteria + +- A fresh agent can identify DebateLab and remain inside its assigned Git worktree without guessing or jumping to the primary checkout. +- Root instructions fit in a small context budget and route specialized tasks to focused documents. +- Current setup, architecture, testing, Supabase, CI, staging, and deployment descriptions agree with checked-in source. +- Maintained documentation contains no broken local links or retired checkout instructions. +- Historical evidence is clearly labeled and cannot be mistaken for current operational guidance. +- The standard verification gate prevents the original stale-path failure from returning. diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 000000000..efcb32162 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,198 @@ +# Testing and verification + +**Status:** Current. Test selection is owned by [`vitest.config.ts`](../vitest.config.ts), [`playwright.config.ts`](../playwright.config.ts), and the npm scripts in [`package.json`](../package.json). + +## Verification rule + +Run the smallest reproduction first. Expand only when the changed behavior or a failure justifies it. A command passing proves the current local commit only. + +Do not describe a stub session as authenticated backend proof. Do not describe a skipped staging job as passing. + +## Documentation + +```bash +npm run docs:check +``` + +This checks maintained Markdown for supported local-link failures and actionable retired checkout paths. It does not validate remote URLs, Markdown syntax, factual freshness, or heading anchors. + +Focused checker test: + +```bash +npx vitest run src/test/docs/check-docs.test.ts +``` + +## Lint and types + +```bash +npm run lint +npm run typecheck +``` + +Lint checks repository ESLint rules. Type checking uses `tsconfig.app.json` and emits no files. Neither command replaces runtime tests. + +## Unit and component tests + +```bash +npm test +npm test -- --coverage +``` + +Vitest runs tests selected by [`vitest.config.ts`](../vitest.config.ts). The coverage gate is deliberately selected-scope coverage for audited runtime modules, not a claim that every maintained file is measured. + +Run one test file while iterating: + +```bash +npx vitest run src/test/security/url-validation.test.ts +``` + +For a bug, watch the reproduction fail for the expected reason before implementing the fix. + +## Edge Function tests + +```bash +npm run test:edge +``` + +This uses Deno directly for `supabase/functions/`, including tests that import deployed handler boundaries and remote Deno modules. The npm script declares the allowed filesystem, environment, and loopback-network permissions. + +A browser-oriented Vitest pass does not replace native Deno tests. + +## Production build + +```bash +npm run build +``` + +This proves the Vite production bundle compiles. It does not prove authentication, database behavior, provider access, or production deployment. + +## Credential-free browser gate + +```bash +CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 npm run test:e2e +``` + +Playwright starts a new Vite server on `127.0.0.1:18080` and runs: + +- desktop Chromium at 1280 by 800 +- mobile Chromium using the Pixel 5 device profile + +The server is never reused. CI uses retries and two workers. On failure, traces, screenshots, videos, and the HTML report stay in ignored `test-results/` and `playwright-report/` directories. CI may upload those credential-free artifacts for diagnosis. + +## Full credential-free gate + +```bash +npm run verify +``` + +The gate runs, in order: + +1. documentation checks +2. lint +3. type checking +4. selected-scope coverage +5. native Edge Function tests +6. production build +7. desktop and mobile Chromium + +It does not start local Supabase, run pgTAP, run WebKit, or use protected staging. + +## Database and RLS gate + +Requirements: + +- Docker +- Supabase CLI 2.115 or newer +- exclusive ownership of the local Supabase ports and containers + +In Superset, run: + +```bash +./.superset/db-gate.sh +``` + +The wrapper acquires `~/.superset/debatelab-db.lock`, starts the stack, runs `npm run test:db`, and stops the stack on exit, including after a test failure. Outside Superset, run: + +```bash +supabase db start +npm run test:db +supabase stop --no-backup +``` + +The stack replays migrations and runs pgTAP tests. `CREATE INDEX CONCURRENTLY` requires the stated CLI floor so it runs outside the migration batch. + +The local stack begins at port `54321` and is not parallel-safe across worktrees. The task that starts it must stop it before finishing or abandoning the run. Use `supabase stop --no-backup` unless another active task explicitly owns the same stack. + +Never substitute a staging or production project for this gate. + +## WebKit release smoke + +Install the browser when needed: + +```bash +npx playwright install --with-deps webkit +``` + +Run: + +```bash +CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 npm run test:e2e:webkit +``` + +This is the Safari-engine smoke gate. State explicitly when it did not run. + +## Protected staging + +Staging requires all three protected values: + +- `STAGING_SUPABASE_URL` +- `STAGING_SUPABASE_ANON_KEY` +- `STAGING_DEMO_CODE` + +Partial configuration fails during Playwright config loading. + +```bash +CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 \ +STAGING_SUPABASE_URL= \ +STAGING_SUPABASE_ANON_KEY= \ +STAGING_DEMO_CODE= \ +npm run test:e2e:staging +``` + +The suite: + +- rejects the production project; +- verifies that the anon key belongs to the supplied staging URL; +- authenticates through `demo-code-login`; +- proves session reload; +- writes and reads one uniquely named source; +- makes one bounded AI request; +- deletes only the source it created. + +Temporary auth state lives under `test-results/.auth/`. The staging projects disable traces, screenshots, and video because browser captures can retain authenticated data. + +The GitHub staging workflow runs only after an eligible main-branch quality result or a main-branch manual dispatch, and only when `STAGING_E2E_ENABLED=true`. The protected environment must already contain current secrets, functions, schema, provider access, a dedicated test code, and a main-only deployment rule. + +A skipped staging workflow is not a pass. Missing credentials or provider access is a blocker, not permission to use production or mocks. + +## CI evidence + +[`.github/workflows/deploy.yml`](../.github/workflows/deploy.yml) runs documentation, lint, type, coverage, Edge Function, and build checks. Pull requests also run database and Chromium jobs. + +[`.github/workflows/staging-e2e.yml`](../.github/workflows/staging-e2e.yml) owns protected staging conditions. + +Before calling CI green: + +1. identify the current head commit; +2. inspect the workflow run for that commit; +3. wait for every required check to reach a terminal state; +4. report billing, minutes, provider, or infrastructure failures as external exceptions rather than passes. + +## What each result does not prove + +- Render test: no authenticated backend proof. +- Unit test: no browser integration proof. +- Build: no runtime or provider proof. +- Local browser pass: no current-head CI proof. +- Credential-free E2E: no staging synchronization proof. +- Staging pass: no authorization to deploy or mutate production. diff --git a/package.json b/package.json index b8ea255f3..462c16db0 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ }, "scripts": { "dev": "vite", + "docs:check": "node scripts/check-docs.mjs", "build": "vite build", "build:dev": "vite build --mode development", "ios:build": "vite build --mode capacitor && cap sync ios", @@ -24,7 +25,7 @@ "test:e2e:webkit": "playwright test --project=webkit", "test:e2e:staging": "playwright test --project=staging", "test:e2e:ui": "playwright test --ui", - "verify": "npm run lint && npm run typecheck && npm test -- --coverage && npm run test:edge && npm run build && CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 npm run test:e2e" + "verify": "npm run docs:check && npm run lint && npm run typecheck && npm test -- --coverage && npm run test:edge && npm run build && CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 npm run test:e2e" }, "dependencies": { "@capacitor/app": "^8.1.0", diff --git a/plans/README.md b/plans/README.md index 621681b73..bf4d54b51 100644 --- a/plans/README.md +++ b/plans/README.md @@ -1,29 +1,33 @@ # Plans -This folder holds implementation plans for remaining DebateLab product work. Keep plans actionable: scope, files, data model, rollout steps, tests, and deployment notes. +**Status:** Plan index. Plans describe proposed work, not current implementation truth. -Last reconciled against `origin/main` on 2026-09-01 at `79b65ca`. +Last reconciled against `origin/main` at `c7d3437` on 2026-09-06. -## Active Plans +Before executing any plan, recheck its branch, commit, paths, assumptions, product decisions, and verification commands against current source. Do not treat an old line number or completed review as fresh evidence. -- [Email and demo-code sign-in](./auth-email-and-demo-code-sign-in.plan) -- [Control honesty and deterministic toggles](./control-honesty.md) -- [Model cost accounting](./model-cost-accounting.md) -- [PR #11 salvage work](./pr11-salvage.md) -- [Rate-limit UI cleanup](./rate-limit-ui-cleanup.md) -- [Supabase staging](./supabase-staging.md) -- [System prompt improvement](./system-prompt-improvement.md) +## Active plans + +- [Email and demo-code sign-in](auth-email-and-demo-code-sign-in.plan) +- [Control honesty and deterministic toggles](control-honesty.md) +- [Model cost accounting](model-cost-accounting.md) +- [PR 11 salvage work](pr11-salvage.md) +- [Rate-limit UI cleanup](rate-limit-ui-cleanup.md) +- [Supabase staging](supabase-staging.md) +- [System prompt improvement](system-prompt-improvement.md) ## Backlog -- [Bug and polish cleanup](./done/bugs.txt) +- [Bug and polish cleanup](bugs.md), four items reverified on 2026-09-06. + +## Shipped reference -## Shipped +The following work was removed from active plans after merged implementation was verified on `origin/main` at the time recorded: -Removed after verifying the merged implementation on `origin/main`: +- Admin announcements, PR 19, with deactivation and dismissal history in PR 26. +- Agent verification loop, PR 21, with CI gating in PRs 26 and 27. +- Analytics and error tracking, PRs 16, 18, 22, 23, 24, 25, and 28. +- Animation polish, PR 20. +- Ask and sitewide search quality, PR 17. -- Admin announcements β€” PR #19, with deactivation and dismissal history in #26 -- Agent verification loop β€” PR #21, with CI gating in #26 and #27 -- Analytics and error tracking β€” PRs #16, #18, #22, #23, #24, #25, #28 -- Animation polish β€” PR #20 -- Ask and sitewide search quality β€” PR #17 +These entries are historical pointers. Inspect current source and GitHub before relying on their behavior or status. diff --git a/plans/bugs.md b/plans/bugs.md new file mode 100644 index 000000000..17a8e3b0a --- /dev/null +++ b/plans/bugs.md @@ -0,0 +1,86 @@ +# Bugs and polish backlog + +**Status:** Active backlog. Reverified against `super-cauliflower` at `cc39416` on 2026-09-06. + +All four items below still reproduce in current source. Recheck them again before implementation because line numbers and product decisions can change. + +## Preconditions + +- Use the current assigned worktree. Confirm it with `git rev-parse --show-toplevel`, then inspect the branch and working tree. +- Use Node 22, npm, and the committed lockfile. Run `npm ci` before verification. +- Keep changes scoped to one backlog item at a time and add its failing proof first. +- Do not deploy, publish, alter Supabase state, or change secrets during this frontend and localization work. + +## Retained items + +### 1. Static consultation date + +`src/pages/SourcesPage.tsx` still renders `[Consulta: 8 de marzo de 2026]` for every source. + +Remove the stale fixed date or derive it from an intentional source/research timestamp. Add a focused render test and browser assertion that the stale hard-coded date is absent. + +Likely files: + +- `src/pages/SourcesPage.tsx` +- `src/test/pages/SourcesPage.test.tsx` +- `e2e/quality-backlog.spec.ts` + +### 2. Factoids save button lacks an accessible name + +`src/components/features/factoids/FactoidsResults.tsx` still renders an icon-only save button with a translated `title` but no `aria-label` or visible text. + +Add a localized accessible name tied to the factoid claim and test it with a role-and-name query. Recheck the other icon-only controls in that file while staying within this item. + +Likely files: + +- `src/components/features/factoids/FactoidsResults.tsx` +- `src/test/components/FactoidsResults.test.tsx` +- `e2e/quality-backlog.spec.ts` + +### 3. Navigation and feature headings use different vocabulary + +The English `nav.*` keys still use short labels such as `Generate`, `Attack`, `Brainstorm`, and `Ask`, while feature headings use longer names. + +Choose one product vocabulary, reuse the matching keys for navigation and H1 text, and update English, Spanish, and Hungarian together. Add authenticated browser assertions for nav-to-heading agreement in each locale. + +Likely files: + +- `src/components/layout/AppLayout.tsx` +- `src/pages/GeneratePage.tsx` +- `src/pages/AttackPage.tsx` +- `src/pages/BrainstormPage.tsx` +- `src/pages/FactoidsPage.tsx` +- `src/pages/AskPage.tsx` +- `src/locales/en.json` +- `src/locales/es.json` +- `src/locales/hu.json` +- `e2e/quality-backlog.spec.ts` + +### 4. Selected Flowchart node deletion has no feedback + +`deleteSelectedNode` in `src/pages/FlowchartPage.tsx` still removes a node and connected edges immediately, clears selection, and provides no confirmation, undo path, success message, or error path. Saved-flowchart deletion has separate toast handling and remains out of scope. + +Add a confirmation or undo path appropriate for the editor, then localized feedback. Cover both accepted and cancelled or recovered behavior with a focused page test. + +Likely files: + +- `src/pages/FlowchartPage.tsx` +- `src/locales/en.json` +- `src/locales/es.json` +- `src/locales/hu.json` +- `src/test/pages/FlowchartPage.test.tsx` +- `e2e/quality-backlog.spec.ts` + +## Verification + +Run the focused test for each item, then: + +```bash +npm run lint +npm run typecheck +npm test -- --coverage +npm run build +CI=1 PORT=18080 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18080 npm run test:e2e -- e2e/quality-backlog.spec.ts +``` + +Do not reuse an existing Vite server. State which desktop, mobile, and locale flows ran. Static search alone is not browser proof. diff --git a/plans/control-honesty.md b/plans/control-honesty.md index 58f3a71c5..a1f9e180a 100644 --- a/plans/control-honesty.md +++ b/plans/control-honesty.md @@ -55,7 +55,7 @@ Both streaming gates currently reject only `clarify`. The client helper and `ai- Extend the existing shared control module with explicit allowlists for the controls already present in the feature input schemas. Validate Generate side/format/role/length/audience/tone, Attack aggressiveness/judge paradigm/toggles, Brainstorm side/output mode, Factoids direction/source type/timeframe/source count where typed, and Ask mode/toggles. Preserve current defaults for omitted optional fields. Return a safe client error for malformed present values. -Call the validator on the raw control fields before any `String(...)`/`Boolean(...)` coercion, and before quota reservation or any Exa/provider work. Bounded text sanitization (prompt-injection stripping, length truncation) may run first only because it preserves the original type of non-string fields; it must not normalize or coerce control values ahead of the validator. The streaming route must use the same Ask mode and boolean rules before it checks advanced-mode eligibility. Keep provider/model allowlisting unchanged. +Call the validator on the raw control fields before any `String(...)` or `Boolean(...)` coercion and before quota reservation or any Exa/provider work. Bounded text sanitization may run first only when it preserves the original type of non-string fields; it must not normalize or coerce control values before validation. The streaming route must use the same Ask mode and boolean rules before it checks advanced-mode eligibility. Keep provider/model allowlisting unchanged. Change `isAskStreamingModeAllowed` and `isAskStreamingSupported` to allow only `general`; advanced toggles remain non-streaming. Use an error message that names the unsupported mode without implying that structured output was produced. diff --git a/plans/done/bugs.txt b/plans/done/bugs.txt deleted file mode 100644 index a24eb0bfa..000000000 --- a/plans/done/bugs.txt +++ /dev/null @@ -1,57 +0,0 @@ -# Bugs and polish backlog - -Audit date: 2026-09-01 on `main` (`79b65ca`). This file lists only reproducible items from the earlier backlog. All other entries were removed after checking current source and history. All four items below were re-verified against `79b65ca`, including their line references. - -## Preconditions - -- Create and use your own isolated T3 Code worktree and feature branch for this backlog; do not edit `main` or reuse another plan's worktree. Before edits, verify `pwd`, `git branch --show-current`, `git status --short --branch`, and `git worktree list --porcelain`, and keep this branch/worktree independent through its PR lifecycle. -- Use Node `22.x` from `package.json`, npm, and the committed `package-lock.json`; verify `node --version`, `npm --version`, and `test -f package-lock.json`, then run `npm ci` before the gates. - -## Retained items - -1. **Static consultation date in `src/pages/SourcesPage.tsx:133-155`** - - The bibliography still renders `[Consulta: 8 de marzo de 2026]` for every source. It is stale as of this audit. Remove the consultation date or derive it from an intentional build/research timestamp. Add a focused render test and an E2E assertion that no stale hard-coded date remains. - - Likely files: `src/pages/SourcesPage.tsx`, new `src/test/pages/SourcesPage.test.tsx`, and new `e2e/quality-backlog.spec.ts`. - -2. **Unlabeled icon-only Factoids save button in `src/components/features/factoids/FactoidsResults.tsx:75-84`** - - The button has a translated `title` but no accessible name. Add a localized `aria-label` tied to the factoid claim and test it with `getByRole('button', { name: ... })`. Recheck all remaining `size="icon"` controls while changing this file; do not remove existing visible labels or screen-reader text. - - Likely files: `src/components/features/factoids/FactoidsResults.tsx`, `src/test/components/FactoidsResults.test.tsx`, and `e2e/quality-backlog.spec.ts`. - -3. **Navigation labels do not match feature page titles** - - `src/components/layout/AppLayout.tsx:28-78` uses short labels such as `Generate`, `Attack`, `Brainstorm`, and `Ask`, while the page headings in `src/locales/en.json:75-118` read `Generate Case`, `Attack My Case`, `Brainstorm Ideas`, and `Ask Your Coach`. Choose one product vocabulary, put it in the shared `nav.*` keys, reuse it for the matching page H1s in `src/pages/{Generate,Attack,Brainstorm,Factoids,Ask}Page.tsx`, and update `en/es/hu` together. Add an authenticated E2E assertion for nav-to-heading agreement in each locale. - - Likely files: `src/components/layout/AppLayout.tsx`, the five feature pages, `src/locales/{en,es,hu}.json`, and `e2e/quality-backlog.spec.ts`. - -4. **Selected Flowchart node deletion has no success feedback in `src/pages/FlowchartPage.tsx:337-341`** - - `deleteSelectedNode` removes the node and connected edges immediately, but it neither confirms the action nor reports success. Add a focused confirmation or undo path appropriate for the editor, then show a localized success/error toast and cover both paths in a new `src/test/pages/FlowchartPage.test.tsx`. Keep saved-flowchart deletion's existing feedback intact. - - Likely files: `src/pages/FlowchartPage.tsx`, `src/locales/{en,es,hu}.json`, new `src/test/pages/FlowchartPage.test.tsx`, and `e2e/quality-backlog.spec.ts`. - -## Ordered implementation and gates - -1. Fix the stale Sources page date and add its render/browser proof. -2. Add the Factoids accessible name and focused test. -3. Align navigation and page headings in all three locales, then run authenticated browser checks. -4. Add Flowchart node-delete confirmation/feedback and its focused page test. - -Run focused tests after each item, then: - -```bash -npm run lint -npm run typecheck -npm test -- --coverage -npm run build -CI=1 PORT=18084 PLAYWRIGHT_BASE_URL=http://127.0.0.1:18084 npm run test:e2e -- e2e/quality-backlog.spec.ts -``` - -Confirm port `18084` is free before the browser run; the explicit `PORT` and `PLAYWRIGHT_BASE_URL` prevent reuse of the dev server's default `8080`. The new focused browser spec must cover the affected desktop/mobile flows; do not treat a static string search as sufficient proof for browser behavior. No Deno gate applies to this frontend/localization-only backlog. - -## Rollout and authorization boundary - -These are frontend/localization changes. Do not deploy, publish, alter Supabase state, or change secrets during implementation. Roll back the previous frontend build if a destructive action loses data, a locale falls back unexpectedly, or navigation no longer reaches the intended heading. diff --git a/plans/supabase-staging.md b/plans/supabase-staging.md index 3eb680747..5834ce902 100644 --- a/plans/supabase-staging.md +++ b/plans/supabase-staging.md @@ -104,15 +104,15 @@ rollback script. Do not edit migration history to hide a failed apply. For function regressions, capture the current version/source-hash baseline and redeploy the previously verified commit's exact function names to staging. The -CLI has no native revision rollback, so the working tree must actually be at -that commit before deploying β€” deploying from an unrelated checkout redeploys -whatever source happens to be on disk, not the verified revision: +CLI has no native revision rollback, so the working tree must contain that +revision's complete function tree before deployment. Deploying from unrelated +on-disk source can mix revisions through shared imports: ```bash -git status --short --branch # must be clean before switching -git checkout -- supabase/functions/ +git status --short --branch # must be clean before switching +git restore --source= --worktree -- supabase/functions npx supabase functions deploy --project-ref "$STAGING_REF" --use-api -git checkout - -- supabase/functions/ # restore the working tree +git restore --source=HEAD --worktree -- supabase/functions ``` Do not use `--prune`, delete the project, rotate secrets, or deploy the rollback diff --git a/scripts/check-docs.mjs b/scripts/check-docs.mjs new file mode 100644 index 000000000..c768f1056 --- /dev/null +++ b/scripts/check-docs.mjs @@ -0,0 +1,167 @@ +import { readdir, readFile, stat } from "node:fs/promises"; +import { dirname, relative, resolve, sep } from "node:path"; +import { pathToFileURL } from "node:url"; + +// These paths are generated, vendored, immutable evidence, or plan history. +// Everything else is checked by default so new maintained docs cannot opt out silently. +const EXCLUDED_PREFIXES = [ + ".git/", + "node_modules/", + "dist/", + "coverage/", + "test-results/", + "playwright-report/", + "ios/App/CapApp-SPM/", + "docs/pricing-snapshots/", + "security/scans/", + "docs/superpowers/", + "plans/", +]; + +const PLAN_INDEX = "plans/README.md"; +const EXTERNAL_TARGET = /^(?:https?:|mailto:|tel:|data:|\/\/)/i; +const RETIRED_CHECKOUT = [ + /\b(?:cd|pushd)\s+(?:["'`])?debate-navigator(?=[/\\\s"'`]|$)/i, + /\bgit\s+-C\s+(?:["'`])?debate-navigator(?=[/\\\s"'`]|$)/i, + /\bdebate-navigator(?=[/\\])/i, + /(?:^|[/\\])debate-navigator(?=[/\\\s"'`.,;:)]|$)/i, +]; +const INLINE_LINK = /!?\[[^\]]*\]\(\s*(<[^>]+>|[^\s)]+)(?:\s+["'][^"']*["'])?\s*\)/g; +const REFERENCE_LINK = /^\s*\[[^\]]+\]:\s*(?:<([^>]+)>|(\S+))/; + +function toRepositoryPath(path) { + return path.split(sep).join("/"); +} + +function isExcluded(relativePath) { + if (relativePath === "plans/" || relativePath === PLAN_INDEX) return false; + return EXCLUDED_PREFIXES.some((prefix) => relativePath.startsWith(prefix)); +} + +async function markdownFiles(rootDir) { + const files = []; + + async function walk(directory) { + for (const entry of await readdir(directory, { withFileTypes: true })) { + const absolutePath = resolve(directory, entry.name); + const relativePath = toRepositoryPath(relative(rootDir, absolutePath)); + const prefixPath = entry.isDirectory() ? `${relativePath}/` : relativePath; + + if (isExcluded(prefixPath)) continue; + if (entry.isDirectory()) { + await walk(absolutePath); + } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) { + files.push({ absolutePath, relativePath }); + } + } + } + + await walk(rootDir); + return files.sort((left, right) => left.relativePath.localeCompare(right.relativePath)); +} + +function normalizeTarget(target) { + return target.startsWith("<") && target.endsWith(">") ? target.slice(1, -1) : target; +} + +function inlineTargets(content) { + const targets = []; + let line = 1; + let cursor = 0; + + for (const match of content.matchAll(INLINE_LINK)) { + const index = match.index ?? 0; + line += content.slice(cursor, index).split("\n").length - 1; + targets.push({ target: normalizeTarget(match[1]), line }); + cursor = index; + } + + return targets; +} + +function referenceTarget(line) { + const match = line.match(REFERENCE_LINK); + return match ? normalizeTarget(match[1] ?? match[2]) : null; +} + +function localTarget(rootDir, documentPath, rawTarget) { + if (!rawTarget || rawTarget.startsWith("#") || EXTERNAL_TARGET.test(rawTarget)) return null; + + const withoutSuffix = rawTarget.replace(/[?#].*$/, ""); + if (!withoutSuffix) return null; + + let decoded; + try { + decoded = decodeURIComponent(withoutSuffix); + } catch { + return { error: `invalid encoded local link target: ${rawTarget}` }; + } + + const absolutePath = decoded.startsWith("/") + ? resolve(rootDir, decoded.slice(1)) + : resolve(dirname(documentPath), decoded); + const relativePath = relative(rootDir, absolutePath); + + if (relativePath === ".." || relativePath.startsWith(`..${sep}`)) { + return { error: `local link target leaves repository: ${rawTarget}` }; + } + + return { absolutePath }; +} + +export async function checkDocs(rootDir) { + const root = resolve(rootDir); + const issues = []; + + for (const document of await markdownFiles(root)) { + const content = await readFile(document.absolutePath, "utf8"); + const lines = content.split(/\r?\n/); + + async function checkTarget(rawTarget, line) { + const target = localTarget(root, document.absolutePath, rawTarget); + if (!target) return; + if (target.error) { + issues.push({ file: document.relativePath, line, message: target.error }); + return; + } + + try { + await stat(target.absolutePath); + } catch { + issues.push({ + file: document.relativePath, + line, + message: `missing local link target: ${rawTarget}`, + }); + } + } + + for (const [index, line] of lines.entries()) { + if (RETIRED_CHECKOUT.some((pattern) => pattern.test(line))) { + issues.push({ + file: document.relativePath, + line: index + 1, + message: "retired checkout path or command", + }); + } + + const reference = referenceTarget(line); + if (reference) await checkTarget(reference, index + 1); + } + + for (const inline of inlineTargets(content)) { + await checkTarget(inline.target, inline.line); + } + } + + return issues; +} + +const invokedPath = process.argv[1] ? pathToFileURL(resolve(process.argv[1])).href : ""; +if (import.meta.url === invokedPath) { + const issues = await checkDocs(process.cwd()); + for (const issue of issues) { + console.error(`${issue.file}:${issue.line} ${issue.message}`); + } + if (issues.length > 0) process.exitCode = 1; +} diff --git a/security/scans/2026-08-19-codex-security-06d93a47/README.md b/security/scans/2026-08-19-codex-security-06d93a47/README.md index 15312516c..9e21e501d 100644 --- a/security/scans/2026-08-19-codex-security-06d93a47/README.md +++ b/security/scans/2026-08-19-codex-security-06d93a47/README.md @@ -1,10 +1,13 @@ -# Codex Security scan 06d93a47 +# Codex security scan 06d93a47 + +**Status:** Historical evidence for one revision. This is not current security proof. - Date: 2026-08-19 - Target revision: `7305a87634021fdb80d3702c56f3e171d6c8abb0` -- Scope: whole repository -- Findings: 11 (2 high, 8 medium, 1 low) +- Scope: whole repository at that revision +- Findings: 11, comprising 2 high, 8 medium, and 1 low - Coverage: partial -Use `report.md` for the human-readable audit and `findings.json` for model-assisted remediation. +The retained machine-readable artifact is [`findings.json`](findings.json). The human-readable `report.md` referenced by an earlier index is not present in this repository. +Reproduce findings against current source and run current security checks before treating any item as open, fixed, or accepted. diff --git a/src/test/docs/check-docs.test.ts b/src/test/docs/check-docs.test.ts new file mode 100644 index 000000000..b00c7dae5 --- /dev/null +++ b/src/test/docs/check-docs.test.ts @@ -0,0 +1,161 @@ +import { afterEach, describe, expect, it } from "vitest"; +import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { pathToFileURL } from "node:url"; + +interface DocIssue { + file: string; + line: number; + message: string; +} + +type CheckDocs = (rootDir: string) => Promise; + +const temporaryRoots: string[] = []; + +async function createRoot(): Promise { + const root = await mkdtemp(join(tmpdir(), "debatelab-docs-")); + temporaryRoots.push(root); + return root; +} + +async function write(root: string, relativePath: string, content: string): Promise { + const target = join(root, relativePath); + await mkdir(dirname(target), { recursive: true }); + await writeFile(target, content); +} + +async function loadCheckDocs(): Promise { + const checkerUrl = pathToFileURL(resolve("scripts/check-docs.mjs")).href; + const checkerModule = await import(checkerUrl); + return checkerModule.checkDocs; +} + +afterEach(async () => { + await Promise.all(temporaryRoots.splice(0).map((root) => rm(root, { recursive: true, force: true }))); +}); + +describe("checkDocs", () => { + it("accepts an existing local inline link", async () => { + const root = await createRoot(); + await write(root, "README.md", "Read the [guide](docs/guide.md).\n"); + await write(root, "docs/guide.md", "# Guide\n"); + + await expect((await loadCheckDocs())(root)).resolves.toEqual([]); + }); + + it("checks new Markdown files by default", async () => { + const root = await createRoot(); + await write(root, "notes/new.md", "Run `cd debate-navigator`.\n"); + + const issues = await (await loadCheckDocs())(root); + + expect(issues).toHaveLength(1); + expect(issues[0]).toMatchObject({ file: "notes/new.md", line: 1 }); + }); + + it("checks the plan index but excludes other plan artifacts", async () => { + const root = await createRoot(); + await write(root, "plans/README.md", "Run `cd debate-navigator`.\n"); + await write(root, "plans/old.md", "Run `cd debate-navigator`.\n"); + + const issues = await (await loadCheckDocs())(root); + + expect(issues).toHaveLength(1); + expect(issues[0].file).toBe("plans/README.md"); + }); + + it.each([ + ".git/notes.md", + "node_modules/package/README.md", + "dist/report.md", + "coverage/report.md", + "test-results/report.md", + "playwright-report/report.md", + "ios/App/CapApp-SPM/README.md", + "docs/pricing-snapshots/openai.md", + "security/scans/old/README.md", + "docs/superpowers/specs/old.md", + ])("excludes %s", async (relativePath) => { + const root = await createRoot(); + await write(root, relativePath, "Run `cd debate-navigator`.\n"); + + await expect((await loadCheckDocs())(root)).resolves.toEqual([]); + }); + + it.each([ + "Run `cd debate-navigator`.", + "Run `pushd debate-navigator`.", + "Run `git -C debate-navigator status`.", + "Open `debate-navigator/docs/setup.md`.", + "Open `/tmp/debate-navigator/file`.", + "Open `/home/codex/Projects/debate-navigator`.", + ])("rejects an actionable retired checkout form: %s", async (content) => { + const root = await createRoot(); + await write(root, "README.md", `${content}\n`); + + const issues = await (await loadCheckDocs())(root); + + expect(issues).toHaveLength(1); + expect(issues[0].message).toContain("retired checkout"); + }); + + it("allows the former repository name in historical prose", async () => { + const root = await createRoot(); + await write(root, "README.md", "The project was formerly named debate-navigator.\n"); + + await expect((await loadCheckDocs())(root)).resolves.toEqual([]); + }); + + it.each([ + "Read the [missing guide](docs/missing.md).", + "[guide]: docs/missing.md", + ])("rejects a missing local target: %s", async (content) => { + const root = await createRoot(); + await write(root, "README.md", `${content}\n`); + + const issues = await (await loadCheckDocs())(root); + + expect(issues).toHaveLength(1); + expect(issues[0].message).toContain("missing local link target"); + }); + + it("rejects a missing multiline local target", async () => { + const root = await createRoot(); + await write(root, "README.md", "Read the [missing guide](\n docs/missing.md\n).\n"); + + const issues = await (await loadCheckDocs())(root); + + expect(issues).toHaveLength(1); + expect(issues[0]).toMatchObject({ file: "README.md", line: 1 }); + expect(issues[0].message).toContain("missing local link target"); + }); + + it("accepts encoded paths and query or fragment suffixes", async () => { + const root = await createRoot(); + await write( + root, + "README.md", + "[encoded](docs/my%20guide.md) [query](docs/guide.md?raw=1) [fragment](docs/guide.md#setup)\n", + ); + await write(root, "docs/my guide.md", "# Encoded\n"); + await write(root, "docs/guide.md", "# Setup\n"); + + await expect((await loadCheckDocs())(root)).resolves.toEqual([]); + }); + + it.each([ + "[web](https://example.com)", + "[protocol-relative](//cdn.example.com/guide.md)", + "[mail](mailto:test@example.com)", + "[phone](tel:+123456789)", + "[data](data:text/plain,hello)", + "[section](#section)", + ])("ignores non-file target %s", async (link) => { + const root = await createRoot(); + await write(root, "README.md", `${link}\n`); + + await expect((await loadCheckDocs())(root)).resolves.toEqual([]); + }); +}); diff --git a/src/test/scripts/db-gate.test.ts b/src/test/scripts/db-gate.test.ts new file mode 100644 index 000000000..2bffa3f50 --- /dev/null +++ b/src/test/scripts/db-gate.test.ts @@ -0,0 +1,47 @@ +import { spawnSync } from "node:child_process"; +import { chmod, mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; +import { afterEach, expect, it } from "vitest"; + +const temporaryRoots: string[] = []; + +async function executable(path: string, content: string): Promise { + await writeFile(path, content); + await chmod(path, 0o755); +} + +afterEach(async () => { + await Promise.all(temporaryRoots.splice(0).map((root) => rm(root, { recursive: true, force: true }))); +}); + +it("stops local Supabase when database tests fail", async () => { + const root = await mkdtemp(join(tmpdir(), "debatelab-db-gate-")); + temporaryRoots.push(root); + const bin = join(root, "bin"); + const log = join(root, "calls.log"); + await mkdir(bin); + await executable( + join(bin, "supabase"), + '#!/usr/bin/env bash\nprintf "supabase %s\\n" "$*" >> "$CALL_LOG"\n', + ); + await executable( + join(bin, "npm"), + '#!/usr/bin/env bash\nprintf "npm %s\\n" "$*" >> "$CALL_LOG"\nexit 17\n', + ); + + const result = spawnSync("bash", [resolve(".superset/db-gate.sh")], { + encoding: "utf8", + env: { + ...process.env, + CALL_LOG: log, + HOME: root, + PATH: `${bin}:${process.env.PATH ?? ""}`, + }, + }); + + expect(result.status).toBe(17); + await expect(readFile(log, "utf8")).resolves.toBe( + "supabase db start\nnpm run test:db\nsupabase stop --no-backup\n", + ); +}); diff --git a/supabase/README.md b/supabase/README.md index 27008f7aa..3c4221f75 100644 --- a/supabase/README.md +++ b/supabase/README.md @@ -1,39 +1,176 @@ -# Supabase Backend (Production-Ready Wiring) - -This backend is fully wired to the app (auth + data + AI proxy). No placeholders. - -## Project -- URL: https://uziwdtkzofptihahfooo.supabase.co -- Publishable key: sb_publishable_FCOjs726U466nD8jmv-HZw_DUYqSz-6 - -## What this provides -- Auth via Google OAuth (required to use the app) -- Postgres storage for cases, chats, sources, settings -- Edge Function AI proxy (no API keys in browser) - -## Setup (Supabase dashboard) - -1) Run the migration SQL -- Open SQL editor -- Paste `supabase/migrations/20260129_init.sql` - -2) Create Edge Function `ai` -- In Supabase Dashboard, create a function named `ai` -- Paste `supabase/functions/ai/index.ts` -- Add secrets: - - OPENAI_API_KEY - - ANTHROPIC_API_KEY (optional) - - GOOGLE_API_KEY (optional) - - JINA_API_KEY (optional) - - AI_DEFAULT_PROVIDER (optional, default: openai) - - AI_DEFAULT_MODEL (optional, default: gpt-4o-mini) - -3) Enable Google OAuth -- Auth -> Providers -> Google -- Set redirect URL to: https://uziwdtkzofptihahfooo.supabase.co/auth/v1/callback -- Add `http://localhost:8080` as a redirect URL for local dev - -## Local dev -- Start app with `npm run dev` -- Sign in with Google -- All saved data syncs to Supabase +# Supabase backend + +**Status:** Current orientation. Function code, migrations, tests, and `supabase/config.toml` remain authoritative. + +## Responsibilities + +Supabase provides: + +- user sessions and OAuth; +- PostgreSQL persistence and Row Level Security; +- RPCs, triggers, quotas, subscriptions, sharing, sparring, announcements, and usage accounting; +- Edge Functions that protect privileged credentials and integrate with external services; +- local migration replay and pgTAP policy tests. + +Do not configure this backend by copying SQL or keys from documentation. Use checked-in migrations, CLI commands, protected environments, and explicit deployment authorization. + +## Layout + +```text +supabase/config.toml local and function gateway configuration +supabase/functions/ Deno Edge Functions +supabase/functions/_shared shared auth, validation, prompts, research, and cost logic +supabase/migrations/ ordered database history +supabase/tests/ pgTAP database and policy tests +``` + +## Function groups + +### AI and practice + +- `ai`: authenticated non-streaming endpoint for generate, attack, brainstorm, factoids, Ask, Crossfire, and sparring. It enforces supported providers and controls, access limits, research bounds, output validation, usage accounting, and sparring integrity. +- `ai-stream`: authenticated server-sent events endpoint for Ask. It emits deltas, a final event, or an error and records usage. + +The browser boundary is [`../src/lib/ai/client.ts`](../src/lib/ai/client.ts). Shared prompt, research, source-quality, model-pricing, cost, and control logic lives in `_shared`. + +### Content and sharing + +- `import-source`: authenticated public-URL import with SSRF protection on redirects, timeout, content-type and size limits, readable-text extraction, quota checks, and logging. +- `share`: authenticated creation of expiring share codes for supported saved content. +- `export-gdoc`: authenticated Google Docs export using a short-lived Google access token. It rejects refresh tokens. + +### Authentication and administration + +- `demo-code-login`: bounded pre-session access-code flow with normalized hashing, attempt limits, atomic use consumption, demo-user provisioning, and a prohibition on granting administrator status. +- `admin`: authenticated administrator operations with server-side role checks. +- `announcements`: authenticated announcement listing and dismissal. +- `redeem-promo`: authenticated atomic promo redemption. + +### Billing + +- `create-checkout`: authenticated Stripe Checkout creation. Price selection is server-controlled and redirects are validated. +- `create-portal`: authenticated Stripe Billing Portal creation with customer ownership checks. +- `stripe-webhook`: Stripe-signature-validated subscription and invoice event handling with idempotency state. + +## Shared backend modules + +[`functions/_shared/auth.ts`](functions/_shared/auth.ts) validates bearer tokens, creates user-scoped and service clients, enforces administrator roles, and formats JSON errors. + +Other shared modules own: + +- prompt construction and source bounds; +- research orchestration and source ranking; +- public URL validation; +- model pricing and normalized cost calculations; +- control enforcement; +- crossfire response validation; +- announcement input validation. + +Move code into `_shared` only when multiple functions use the same boundary or policy. Feature-specific behavior stays with its handler. + +## Authentication boundary + +Several entries in [`config.toml`](config.toml) set `verify_jwt = false`. This does not make those functions public. Their handlers perform their own request validation because they need one of these patterns: + +- user bearer validation through shared auth helpers; +- administrator role validation; +- Stripe webhook signature verification; +- pre-session demo-code validation; +- short-lived Google token handling. + +Do not remove handler authentication because the gateway setting looks permissive. Do not enable gateway verification without checking custom request flows and tests. + +The effective gateway behavior for a function without an explicit config entry must be verified before documenting or changing it. + +## CORS + +Current handlers mostly define explicit origin allowlists. The exact list differs by endpoint and handler code is authoritative. + +Relevant origins include the production site, bounded local development hosts, automated staging browser origin, and `capacitor://localhost` where native access is supported. + +Do not use wildcard origins to hide a failed browser test. Before protected staging, verify `http://127.0.0.1:18080` for `demo-code-login`, `ai`, `ai-stream`, `admin`, and `announcements`. + +## Migrations and database tests + +Migration filenames define execution order. Never edit an applied migration to change production history; add a new forward migration. + +Local gate: + +```bash +supabase db start +npm run test:db +supabase stop --no-backup +``` + +Requirements: + +- Docker +- Supabase CLI 2.115 or newer +- exclusive ownership of the default local ports and containers + +The stack begins at port `54321` and is not parallel-safe across worktrees. Coordinate before starting it. Stop the stack before finishing, including after failures when safe. + +The database gate proves migration replay and pgTAP assertions locally. It does not prove staging or production state. + +## Edge Function tests + +Run: + +```bash +npm run test:edge +``` + +The Deno suite covers shared modules, handler boundaries, control enforcement, research behavior, source validation, cost normalization, and persistence rules. The npm script grants only the permissions required by the test suite. + +Run focused tests while changing one function, then run the complete Edge Function gate. + +## Environment and secrets + +Document names, never values. Depending on deployed features, server-side configuration includes categories such as: + +- Supabase URL, anon key, and service-role key; +- supported AI provider keys; +- research provider keys; +- Stripe secret, webhook secret, and price configuration; +- demo-code hashing material; +- sparring integrity material; +- Google export configuration; +- allowed origins and environment-specific application URLs. + +Use `supabase secrets` or protected deployment configuration for server values. Never place server secrets in `VITE_*`, source files, docs, screenshots, traces, logs, or comments. + +Public browser configuration in [`../src/lib/supabase/env.ts`](../src/lib/supabase/env.ts) is not a replacement for server authorization. + +## Local, staging, and production + +### Local + +Use local Supabase for migration replay and pgTAP. Do not point local tests at a remote project. + +### Staging + +Protected staging must use a non-production project with matching anon key, current schema and functions, required provider access, a dedicated bounded demo code, correct CORS, and a protected main-only GitHub environment. + +Read [`../docs/testing.md`](../docs/testing.md) before running staging. + +### Production + +Migrations, function deployments, secret changes, and data mutation require explicit authorization. Confirm the linked project, exact diff, current baseline, rollback path, and local evidence before any operation. + +Read [`../docs/operations.md`](../docs/operations.md) before changing remote state. + +## Deployment checklist + +For an authorized backend change: + +1. Confirm the current worktree and commit. +2. Resolve the linked Supabase project and environment. +3. Inspect the exact migration or function diff. +4. Capture the remote baseline needed for rollback. +5. Run focused tests, `npm run test:edge`, and database tests when schema or RLS changes. +6. Verify CORS and handler authentication. +7. Apply one scoped operation. +8. Exercise the live endpoint or user-scoped database path. +9. Record the target, operation, result, and any exception without exposing secrets. + +Stop when the target is ambiguous, rollback is unproven, or the operation exceeds authorization.