Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Browser or PWA
-> OpenRouter models, research providers, Stripe, Google APIs
```

- The frontend is hosted on **Vercel**. [`vercel.json`](vercel.json) owns the build, SPA fallback, cache policy, and security headers. Merging to `main` deploys it to production.
- The frontend is hosted on **Vercel**. [`vercel.json`](vercel.json) owns the build, app-route rewrites, cache policy, and security headers. Merging to `main` deploys it to production.
- **Supabase** holds auth, data, and Edge Functions. Migrations and function deploys are separate operations that need explicit authorization.
- The browser never holds provider secrets. AI requests go to the `ai` and `ai-stream` Edge Functions, which check auth, quotas, and controls, load debate sources by ID, call the model through OpenRouter, validate the output, and record usage.

Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,6 @@ The service worker and manifest provide installable web assets and an offline ap

## Deployment boundary

Vercel builds `dist/` with Node 22 and serves it. [`vercel.json`](../vercel.json) owns SPA fallback, cache policy, crawler restrictions, and response security headers. Merging to `main` deploys the frontend to production; Supabase migrations and Edge Functions deploy separately. GitHub workflow files own quality, live-provider, staging, and canary automation.
Vercel builds `dist/` with Node 22 and serves it. [`vercel.json`](../vercel.json) owns the app-route rewrites, the 404 for everything else, cache policy, crawler restrictions, and response security headers. Merging to `main` deploys the frontend to production; Supabase migrations and Edge Functions deploy separately. GitHub workflow files own quality, live-provider, staging, and canary automation.

Read [operations.md](operations.md) before changing deployment behavior and [testing.md](testing.md) before claiming a boundary works.
12 changes: 8 additions & 4 deletions docs/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,16 @@ Vercel builds and serves the frontend from the Git repository. Merging to `main`
[`vercel.json`](../vercel.json) owns:

- the Vite build command and `dist` output;
- SPA fallback to `index.html`;
- no-cache headers for `/`, `index.html`, `theme-init.js`, and `sw.js`, and immutable caching for hashed `/assets/`;
- rewrites of the app routes (`/app`, `/login`, `/share`, `/expocit/sources`) to `app-shell.html`; any other path that is not a file gets `404.html` with a real 404 status;
- `trailingSlash: false`, which 308-redirects `/app/settings/` to `/app/settings` before routing, since rewrite and redirect sources match exactly;
- 301 redirects from the retired brand pages (`/debatelab`, `/debatelab-by-programcode`, and the rest) to `/`;
- no-cache headers for `/`, `index.html`, `app-shell.html`, `theme-init.js`, and `sw.js`, and immutable caching for hashed `/assets/`;
- `noindex` headers for `/app`, `/login`, and `/share` routes;
- Content Security Policy and the other response security headers, including the `Permissions-Policy` that allows the microphone for the app's own origin only.

The build also writes `dist/terms/index.html` and `dist/privacy/index.html`: copies of the app shell whose canonical URL names their own route instead of the landing page (`selfCanonicalShells` in [`vite.config.ts`](../vite.config.ts)). Vercel serves them before the SPA fallback. Add a route there when a new SPA page joins `public/sitemap.xml`.
After `vite build`, [`scripts/prerender.mjs`](../scripts/prerender.mjs) renders the landing page, `/terms`, `/privacy`, and the 404 page in Node through [`src/prerender/entry.tsx`](../src/prerender/entry.tsx). It writes them to `dist/index.html`, `dist/terms/index.html`, `dist/privacy/index.html`, and `dist/404.html`, each with its own title, description, and canonical URL from [`src/lib/seo/pages.ts`](../src/lib/seo/pages.ts); the landing page also carries the Organization and SoftwareApplication JSON-LD. The built shell is kept as `dist/app-shell.html`. On load, the client replaces the prerendered HTML with the same page. A new public SPA page needs an entry in `PAGE_HEADS`, in the prerender entry, and a route kept out of the 404.

[`scripts/sitemap.mjs`](../scripts/sitemap.mjs) then writes `dist/sitemap.xml` from every page in `dist/` that names itself as canonical and is not `noindex`. Each `<lastmod>` is the `dateModified` the page declares in its JSON-LD: the guide pages' `CONTENT_DATE` in [`scripts/generate_seo_content.py`](../scripts/generate_seo_content.py), the legal pages' "last updated" dates, and the landing page's last source commit. A page without a date is listed without `<lastmod>`.

[`src/test/vercel-deployment-config.test.ts`](../src/test/vercel-deployment-config.test.ts) guards these rules. Update it together with `vercel.json`, and do not paste the full configuration into another guide.

Expand Down Expand Up @@ -205,6 +209,6 @@ Do not expose protected data in artifacts. Record the commit, environment, opera
- Green local E2E plus staging failure: schema, functions, provider access, demo code, or CORS may differ. Run the staging suite locally first; if it fails there too on a UI step, the spec is stale, not staging.
- New frontend errors against Supabase right after a merge: the matching migration or function may not be applied to production yet. Run `npm run backend:status` from the linked checkout.
- Function returns unauthorized after deploy: check handler auth and environment propagation before changing gateway settings.
- Browser works on one route only: check the `vercel.json` SPA rewrite and cache headers.
- Browser works on one route only, or an app route shows the 404 page: check the `vercel.json` app-route rewrites and cache headers.

Read [testing.md](testing.md) for evidence rules and [`../supabase/README.md`](../supabase/README.md) for backend ownership.
78 changes: 8 additions & 70 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,68 +6,27 @@
<meta name="jam:team" content="b79917fc-80c6-4de1-9047-c98fa5db0aff" />
<link rel="stylesheet" href="/src/index.css" />
<script src="/src/bootstrap.ts" type="module"></script>
<title>DebateLab by Pi-Stack | AI Debate Prep, Cases & Rebuttals</title>
<meta name="title" content="DebateLab by Pi-Stack | AI Debate Prep, Cases & Rebuttals" />
<!-- The shell for app routes. scripts/prerender.mjs writes the public pages
from it with their own title, description, canonical URL and JSON-LD. -->
<title>DebateLab</title>
<meta name="description" content="DebateLab by Pi-Stack helps students and competitive debaters practice with AI opponents, case generation, rebuttal drills, evidence leads, coaching, timers, and argument flowcharts." />
<meta name="keywords" content="DebateLab, debate lab, AI debate practice, debate practice platform, debate drills, rebuttal practice, argumentation training, debate coach, debate preparation" />
<meta name="author" content="DebateLab" />
<meta name="author" content="DebateLab by Pi-Stack" />
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1" />
<meta name="language" content="English" />
<meta name="theme-color" content="#f1f5f9" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#0f172a" media="(prefers-color-scheme: dark)" />
<link rel="icon" type="image/png" href="/favicon.png" />
<link rel="canonical" href="https://debate.pi-stack.org/" />
<link rel="manifest" href="/manifest.webmanifest" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://debate.pi-stack.org/" />
<meta property="og:site_name" content="DebateLab" />
<meta property="og:site_name" content="DebateLab by Pi-Stack" />
<meta property="og:title" content="DebateLab by Pi-Stack | AI Debate Prep, Cases & Rebuttals" />
<meta property="og:description" content="Practice debate with AI opponents, case generation, rebuttal drills, evidence leads, coaching, timers, and argument flowcharts built for competitive debaters." />
<meta property="og:image" content="https://debate.pi-stack.org/hero-visual.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="DebateLab by Pi-Stack | AI Debate Prep" />
<meta name="twitter:description" content="Practice debate with AI opponents, case generation, rebuttal drills, evidence leads, coaching, timers, and argument flowcharts." />
<meta name="twitter:image" content="https://debate.pi-stack.org/hero-visual.png" />
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://debate.pi-stack.org/#organization",
"name": "DebateLab",
"alternateName": ["DebateLab AI", "Debate Lab"],
"url": "https://debate.pi-stack.org/",
"sameAs": ["https://github.com/Programcode", "https://github.com/Programcode/debatelab-ai-profile"],
"description": "DebateLab builds AI-powered debate practice and preparation tools for students and competitive debaters."
},
{
"@type": "WebSite",
"@id": "https://debate.pi-stack.org/#website",
"url": "https://debate.pi-stack.org/",
"name": "DebateLab",
"alternateName": "DebateLab AI",
"publisher": { "@id": "https://debate.pi-stack.org/#organization" },
"potentialAction": {
"@type": "SearchAction",
"target": "https://debate.pi-stack.org/ai-debate-practice?q={search_term_string}",
"query-input": "required name=search_term_string"
}
},
{
"@type": "SoftwareApplication",
"@id": "https://debate.pi-stack.org/#software",
"name": "DebateLab",
"applicationCategory": "EducationalApplication",
"operatingSystem": "Web, iOS",
"url": "https://debate.pi-stack.org/",
"description": "AI-powered debate practice platform with targeted drills, source-backed case prep, rebuttal training, instant feedback, and argument flowcharts.",
"offers": { "@type": "Offer", "price": "5", "priceCurrency": "USD" },
"publisher": { "@id": "https://debate.pi-stack.org/#organization" }
}
]
}
</script>
<script src="/theme-init.js"></script>
<style>
body {
Expand Down Expand Up @@ -157,30 +116,9 @@
</main>
</div>
<noscript>
<main style="max-width: 1080px; margin: 0 auto; padding: 48px 20px; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; line-height: 1.6; color: #0f172a; background: #ffffff;">
<h1 style="font-size: clamp(2.25rem, 6vw, 4.5rem); line-height: 1; letter-spacing: -0.06em; margin: 0 0 18px;">DebateLab by Pi-Stack</h1>
<p style="font-size: 1.18rem; max-width: 760px; color: #334155;">DebateLab by Pi-Stack is the official DebateLab AI debate prep platform at debate.pi-stack.org. It helps students and competitive debaters practice with AI opponents, generate debate cases, train rebuttals, find evidence leads, build argument flowcharts, and time practice speeches.</p>
<nav aria-label="DebateLab public guides" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 12px; margin-top: 28px;">
<a href="/debatelab">DebateLab by Programcode</a>
<a href="/debatelab-by-programcode">DebateLab official brand page</a>
<a href="/what-is-debatelab">What is DebateLab?</a>
<a href="/debatelab-app">DebateLab app</a>
<a href="/debatelab-ai">DebateLab AI</a>
<a href="/debate-lab">Debate Lab</a>
<a href="/ai-debate-practice">AI debate practice</a>
<a href="/debate-case-generator">Debate case generator</a>
<a href="/debate-rebuttal-practice">Debate rebuttal practice</a>
<a href="/ai-debate-coach">AI debate coach</a>
<a href="/debate-evidence-finder">Debate evidence finder</a>
<a href="/argument-flowchart">Argument flowchart</a>
<a href="/online-debate-timer">Online debate timer</a>
<a href="/how-to-write-a-debate-case">How to write a debate case</a>
<a href="/how-to-practice-rebuttals">How to practice rebuttals</a>
<a href="/debate-weighing-guide">Debate weighing guide</a>
<a href="/ai-tools-for-debate-students">AI tools for debate students</a>
<a href="/wsdc-debate-practice">WSDC debate practice</a>
<a href="/about-debatelab">About DebateLab</a>
</nav>
<main style="max-width: 40rem; margin: 0 auto; padding: 48px 20px; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; line-height: 1.6;">
<h1>DebateLab by Pi-Stack</h1>
<p>DebateLab needs JavaScript to run. Read about it on the <a href="/">home page</a> or in the <a href="/ai-debate-practice">AI debate practice guide</a>.</p>
</main>
</noscript>
</body>
Expand Down
1 change: 1 addition & 0 deletions knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"scripts/staging/seed.mjs",
"e2e/full-stack/fake-openrouter.mjs",
"e2e.config.ts",
"src/prerender/entry.tsx",
"agentic/**/*.e2e.ts",
"services/collaboration/src/index.ts",
"services/collaboration/src/**/*.test.ts",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"docs:check": "node scripts/check-docs.mjs",
"backend:status": "node scripts/backend-status.mjs",
"ci:failures": "node scripts/ci-failures.mjs",
"build": "vite build && node scripts/check-build-boundaries.mjs",
"build": "vite build && node scripts/prerender.mjs && node scripts/sitemap.mjs && node scripts/check-build-boundaries.mjs",
"build:dev": "vite build --mode development",
"lint": "eslint . && npm run lint:repo",
"lint:repo": "knip --no-config-hints && node scripts/check-repo-rules.mjs",
Expand Down
Loading
Loading