diff --git a/README.md b/README.md index 88f863a..8e7d9ba 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Interactive Proof is an educational reading companion for mathematical papers and their Lean formalizations. Select a sentence, equation, or Lean declaration and request a focused explanation without leaving the proof. The interface keeps four evidence types distinct: what the paper states, what Lean verifies, prerequisite background, and AI-generated explanation. -This repository contains a working local MVP. Public deployment and hackathon submission evidence are still pending. +This repository contains a tested release candidate. Public deployment, one reviewed live model run, and final hackathon submission evidence are still pending. ## Included proof packages @@ -32,6 +32,8 @@ npm run dev Open [http://localhost:3000](http://localhost:3000) and choose either proof. +The in-app setup and reader guide is available at [http://localhost:3000/docs](http://localhost:3000/docs). Repository setup and deployment details are also collected in [docs/SETUP.md](docs/SETUP.md). + To work with your own material, open [http://localhost:3000/upload](http://localhost:3000/upload). A PDF is required and Lean source is optional. Files are parsed in the browser and remain temporary; only a bounded selection and nearby text are sent when you explicitly request an AI explanation. Uploaded Lean is always labeled unverified. ### Environment @@ -207,6 +209,7 @@ After deployment, test both packages in a private browser session, one paper sel ## Project documents +- [Setup and operator guide](docs/SETUP.md) - [Product requirements](docs/PRD.md) - [Technical specification](docs/SPEC.md) - [Implementation plan](PLAN.md) diff --git a/app/docs/page.module.css b/app/docs/page.module.css new file mode 100644 index 0000000..566c985 --- /dev/null +++ b/app/docs/page.module.css @@ -0,0 +1,220 @@ +.page { + padding-block: clamp(3rem, 8vw, 6rem); +} + +.shell { + max-width: 72rem; +} + +.hero { + max-width: 52rem; +} + +.hero h1 { + max-width: 48rem; + font-size: clamp(2.75rem, 7vw, 5.25rem); +} + +.lede { + max-width: 46rem; + margin: 1.5rem 0 0; + color: var(--muted-foreground); + font-size: clamp(1.0625rem, 2vw, 1.25rem); + line-height: 1.7; +} + +.actions { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin-top: 2rem; +} + +.layout { + display: grid; + gap: 3rem; + margin-top: clamp(4rem, 9vw, 7rem); + align-items: start; +} + +.contents { + padding-block: 0.5rem; +} + +.contentsTitle { + margin: 0; + color: var(--muted-foreground); + font-size: 0.8125rem; + font-weight: 750; + letter-spacing: 0.075em; + text-transform: uppercase; +} + +.contents ol { + display: grid; + gap: 0.25rem; + margin: 1rem 0 0; + padding: 0; + list-style: none; +} + +.contents a { + display: grid; + min-height: 2.75rem; + grid-template-columns: 2rem 1fr; + gap: 0.5rem; + align-items: center; + color: var(--muted-foreground); + font-size: 0.9375rem; + font-weight: 650; +} + +.contents a:hover { + color: var(--foreground); +} + +.contents a span, +.sectionNumber { + color: var(--primary); + font-variant-numeric: tabular-nums; +} + +.article { + min-width: 0; +} + +.article section { + scroll-margin-top: 6rem; + padding-bottom: clamp(3rem, 8vw, 5rem); +} + +.article section + section { + padding-top: clamp(3rem, 8vw, 5rem); + border-top: 0.0625rem solid var(--border); +} + +.sectionNumber { + margin: 0 0 0.75rem; + font-size: 0.8125rem; + font-weight: 750; + letter-spacing: 0.075em; +} + +.article h2 { + max-width: 42rem; + font-size: clamp(2rem, 5vw, 3.25rem); +} + +.article h3 { + margin: 2rem 0 0.75rem; + font-family: var(--font-sans); + font-size: 1.125rem; + line-height: 1.35; +} + +.article > section > p:not(.sectionNumber), +.article li, +.article dd, +.note p { + max-width: 44rem; + color: var(--muted-foreground); + line-height: 1.75; +} + +.article > section > p:not(.sectionNumber) { + margin: 1.25rem 0 0; +} + +.article code { + font-size: 0.9em; +} + +.steps { + display: grid; + gap: 0.75rem; + margin: 1.5rem 0 0; + padding-left: 1.5rem; +} + +.note { + max-width: 44rem; + margin-top: 1.5rem; + padding: 1.25rem; + border-left: 0.25rem solid var(--primary); + background: var(--surface-subtle); +} + +.note p { + margin: 0.375rem 0 0; +} + +.codeBlock { + max-width: 44rem; + overflow-x: auto; + margin: 1rem 0 0; + padding: 1.25rem; + border: 0.0625rem solid var(--border); + border-radius: calc(var(--radius) - 0.25rem); + background: var(--surface); + color: var(--foreground); + font-size: 0.875rem; + line-height: 1.7; + tab-size: 2; +} + +.evidenceList { + display: grid; + gap: 0; + max-width: 44rem; + margin: 1.75rem 0 0; +} + +.evidenceList div { + display: grid; + gap: 0.375rem; + padding-block: 1rem; + border-top: 0.0625rem solid var(--border); +} + +.evidenceList div:last-child { + border-bottom: 0.0625rem solid var(--border); +} + +.evidenceList dt { + font-weight: 750; +} + +.evidenceList dd { + margin: 0; +} + +.textLink { + display: inline-flex; + min-height: 2.75rem; + align-items: center; + gap: 0.5rem; + margin-top: 1rem; + color: var(--primary); + font-weight: 750; +} + +.textLink:hover { + color: var(--primary-strong); +} + +@media (min-width: 52rem) { + .layout { + grid-template-columns: minmax(11rem, 14rem) minmax(0, 1fr); + gap: clamp(3rem, 7vw, 6rem); + } + + .contents { + position: sticky; + top: 6rem; + } + + .evidenceList div { + grid-template-columns: minmax(10rem, 0.7fr) minmax(0, 1.3fr); + gap: 1.5rem; + } +} diff --git a/app/docs/page.tsx b/app/docs/page.tsx new file mode 100644 index 0000000..27efe19 --- /dev/null +++ b/app/docs/page.tsx @@ -0,0 +1,186 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import styles from "./page.module.css"; + +export const metadata: Metadata = { + title: "Documentation", + description: + "Set up Interactive Proof, understand its evidence model, and bring your own paper and optional Lean source.", +}; + +const CONTENTS = [ + { href: "#first-read", label: "Start reading" }, + { href: "#your-material", label: "Use your material" }, + { href: "#api-key", label: "Enable AI explanations" }, + { href: "#evidence", label: "Read the evidence" }, + { href: "#local-development", label: "Run locally" }, +] as const; + +export default function DocumentationPage() { + return ( +
+
+
+

Documentation

+

From paper to grounded explanation.

+

+ Interactive Proof is useful before an API key is configured. Begin with a + recorded example, inspect the paper-to-Lean correspondence, or open a + temporary workspace with your own files. Add a server-side key only when + you are ready to request live explanations. +

+
+ + Read a sample proof + + + Open an upload workspace + +
+
+ +
+ + +
+
+

01

+

Start with a proof already in the reading room.

+

+ Choose a sample proof, switch between the paper and Lean views, and + inspect its proof map and verification evidence. Recorded examples show + the complete explanation interface without making a model request. +

+
    +
  1. Select a sentence, equation, or Lean declaration.
  2. +
  3. Choose an action such as More details or Connect to Lean.
  4. +
  5. Follow source chips back to the exact paper or Lean location.
  6. +
  7. Check the verification disclosure before treating a formal claim as checked.
  8. +
+
+ +
+

02

+

Use a paper of your own.

+

+ The upload workspace requires a PDF and accepts optional individual + .lean files or a ZIP of Lean sources. Files are parsed in + your browser and are not added to the public proof library. +

+
+ Temporary by design. +

+ Only the selected passage and bounded nearby context are sent when you + explicitly request an explanation. Uploaded Lean is labeled unverified. +

+
+ + Review the upload consent and limits + +
+ +
+

03

+

Enable live AI explanations.

+

+ Keep the OpenAI key on the server. Never paste it into browser code, + commit it to Git, or give it a NEXT_PUBLIC_ prefix. +

+

Local development

+
+                {`cp .env.example .env.local
+# Edit .env.local and set:
+OPENAI_API_KEY=your_key_here
+OPENAI_MODEL=gpt-5.6
+npm run dev`}
+              
+

+ Restart the development server after changing environment variables. + Without a key, the reader remains available and explanation requests + return an explicit configuration error. +

+

Hosted deployment

+

+ Add OPENAI_API_KEY using the host's secret manager. Add + OPENAI_MODEL, EXPLAIN_RATE_LIMIT_PER_HOUR, and + an explicit PUBLIC_PROOF_IDS allowlist as environment + variables, then deploy a new version. +

+
+ +
+

04

+

Know what each label promises.

+
+
+
Paper states
+
The claim appears in the supplied paper source.
+
+
+
Lean verifies
+
A recorded toolchain checked the named formal declaration.
+
+
+
Curated correspondence
+
A human-authored mapping connects paper and Lean locations.
+
+
+
AI explains
+
Generated interpretation grounded in the bounded supplied context.
+
+
+

+ A successful Lean build does not by itself prove that the formal theorem + perfectly translates the paper. Interactive Proof keeps those claims + separate so readers can inspect both. +

+ + Read the evaluation and security methodology + +
+ +
+

05

+

Run and verify the project locally.

+
+                {`npm ci
+cp .env.example .env.local
+npm run dev
+
+# Before opening a pull request
+npm run proof:validate
+npm run eval:validate
+npm test
+npm run typecheck
+npm run lint
+npm run build`}
+              
+

+ Node.js 24 is required. Chromium is needed only for browser tests, and + Lean is needed only when regenerating verification evidence for a full + local proof package. +

+
+
+
+
+
+ ); +} diff --git a/app/globals.css b/app/globals.css index ccbb960..c02783d 100644 --- a/app/globals.css +++ b/app/globals.css @@ -522,6 +522,24 @@ h2 { margin: 0; } +.site-footer__links { + display: flex; + flex-wrap: wrap; + gap: 0.25rem 1rem; +} + +.site-footer__links a { + display: inline-flex; + min-height: 2.75rem; + align-items: center; + color: var(--foreground); + font-weight: 650; +} + +.site-footer__links a:hover { + color: var(--primary); +} + .proof-page { min-height: calc(100vh - 8rem); padding-block: 2rem 4rem; diff --git a/app/layout.tsx b/app/layout.tsx index 41052ae..5456d60 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -45,6 +45,9 @@ export default function RootLayout({ Evaluation + + Docs + Upload @@ -56,8 +59,15 @@ export default function RootLayout({ {children} diff --git a/docs/SETUP.md b/docs/SETUP.md new file mode 100644 index 0000000..d262a60 --- /dev/null +++ b/docs/SETUP.md @@ -0,0 +1,92 @@ +# Setup and operator guide + +This guide separates the key-free reading experience from live OpenAI requests. You can inspect sample proofs, use recorded examples, navigate mappings, view verification evidence, and open the temporary upload workspace before configuring any secret. + +The same guidance is published in the application at `/docs`. + +## Local setup + +Requirements: + +- Node.js 24 +- npm +- Chromium only for browser tests +- Lean only when regenerating verification evidence for a full local proof package + +Install and start the application: + +```bash +npm ci +cp .env.example .env.local +npm run dev +``` + +Open . The empty `OPENAI_API_KEY` is intentional: the reading room remains usable, while a live explanation request fails with an explicit configuration error. + +## Enable live explanations + +Edit `.env.local` and set: + +```dotenv +OPENAI_API_KEY=your_key_here +OPENAI_MODEL=gpt-5.6 +EXPLAIN_RATE_LIMIT_PER_HOUR=30 +PUBLIC_PROOF_IDS=odd-sum-square +``` + +Restart the development server after changing environment variables. + +Security rules: + +- Keep `OPENAI_API_KEY` server-side. +- Never commit `.env.local` or paste the key into source code. +- Never use a `NEXT_PUBLIC_` prefix for the key. +- Use the deployment provider's secret manager for hosted environments. +- Treat `PUBLIC_PROOF_IDS` as an explicit production allowlist. The current cleared public package is `odd-sum-square`. + +## Bring your own paper and Lean + +Open `/upload` and provide: + +- one PDF paper; and +- optionally, individual `.lean` files or a ZIP containing Lean sources. + +The browser parses these files into a temporary workspace. They are not added to the repository or public proof library. Only a selected passage and bounded nearby context are sent when the reader explicitly requests an explanation. Uploaded Lean is always presented as unverified. + +Permanent, curated proof packages use the authoring workflow documented in the root README. Before redistributing outside material, record its license and attribution in `THIRD_PARTY_NOTICES.md`. + +## Deployment configuration + +Configure `OPENAI_API_KEY` as a server-only secret. Configure `OPENAI_MODEL`, `EXPLAIN_RATE_LIMIT_PER_HOUR`, and `PUBLIC_PROOF_IDS` as production environment variables, then deploy a newly saved version. + +Do not treat the current process-local request limiter as a globally shared quota across Cloudflare isolates. Use a provider-backed rate-limit binding or shared durable store before relying on it as the only production abuse control. + +## Validation gate + +Run these checks before deployment: + +```bash +npm run proof:validate +npm run eval:validate +npm test +npm run typecheck +npm run lint +npm run build +npm run build:sites +npm audit --omit=dev +``` + +For the browser suite: + +```bash +npx playwright install chromium +npm run test:e2e +``` + +The release check intentionally requires production configuration: + +```bash +npm run release:check +``` + +After deployment, verify the signed-out journey in `docs/submission/LAUNCH_CHECKLIST.md` and record the hosted URL and release commit. diff --git a/docs/submission/LAUNCH_CHECKLIST.md b/docs/submission/LAUNCH_CHECKLIST.md index 5383595..812eb87 100644 --- a/docs/submission/LAUNCH_CHECKLIST.md +++ b/docs/submission/LAUNCH_CHECKLIST.md @@ -4,11 +4,32 @@ Nothing in this checklist is complete merely because it exists in source control Live Devpost fields and dates were checked through the Devpost Hackathons connector on July 13, 2026. Submissions were open, and the configured deadline was July 22, 2026 at 00:00 UTC, equivalent to July 21 at 5:00 PM Pacific Time. Recheck immediately before submitting. +## Current status — July 16, 2026 + +**Release candidate:** `main` at `759d08a` (`Fix repository-wide autoreview findings (#19)`). + +### Completed in the repository + +- PRs #10–#19 are merged, including release gates, deep links, themes, guided learning, authoring tools, proof map, instructor guidance, recorded demonstrations, ephemeral uploads, and repository-wide review fixes. +- Both proof packages validate locally; `odd-sum-square` has recorded passing Lean evidence and `cycle-double-cover` remains explicitly `not-run`. +- The production allowlist is `PUBLIC_PROOF_IDS=odd-sum-square`, so unresolved cycle-double-cover material is excluded unless the owner deliberately changes that setting. +- Proof and evaluation validation, 31 unit-test files with 120 tests, typecheck, lint, Next.js build, Sites build, and the full 33-test applicable browser suite pass. Five browser cases are intentionally skipped by project configuration. +- `npm audit --omit=dev` reports zero production vulnerabilities after the reviewed dependency override. +- The MIT code license, third-party rights matrix, no-key demonstration path, evaluation methodology, Devpost copy, demo script, upload security boundary, and public setup documentation are checked in. + +### Still blocking public launch + +- Add `OPENAI_API_KEY` as a server-only production secret, deploy the site, and verify one real billed GPT-5.6 streamed response. +- Add provider-backed/shared production rate limiting before treating the current process-local limiter as the only abuse control. +- Record `[HOSTED_URL]`, complete signed-out deployment QA, and record the final release commit. +- Decide how judges access the currently private GitHub repository. +- Complete the demo video, Devpost owner fields, and final submission rehearsal. + ## Release blockers -- [ ] Resolve the cycle-double-cover paper and Lean excerpt redistribution status, or remove/replace that material in the public build. -- [ ] Merge the release documentation, accessibility acceptance, evaluation/security, deployment, and submission branches selected for release. -- [ ] Run the repository's complete validation gate against the release commit. +- [x] Resolve the cycle-double-cover paper and Lean excerpt redistribution status, or remove/replace that material in the public build. **Current mitigation:** excluded by `PUBLIC_PROOF_IDS=odd-sum-square`; do not add it back without clearance. +- [x] Merge the release documentation, accessibility acceptance, evaluation/security, deployment, and submission branches selected for release. **Evidence:** PRs #10–#19 are merged into `main`. +- [x] Run the repository's complete offline validation gate against the release commit. **Evidence:** proof/evaluation validation, 120 tests, typecheck, lint, both production builds, 33 applicable browser tests, and the production audit passed for `759d08a`. - [ ] Deploy with `OPENAI_MODEL=gpt-5.6` and a server-only `OPENAI_API_KEY`. - [ ] Confirm a live, billed GPT-5.6 response on the deployed application; deterministic test fixtures are not sufficient evidence. @@ -38,36 +59,36 @@ Test in a fresh private window with extensions disabled and no existing app sess | PRD requirement | Release evidence to inspect | Final check | |---|---|---| -| R1 Proof-package reader | Two package cards, generic `/proofs/[proofId]` route, designed not-found state | [ ] | -| R2 Selectable paper | PDF.js text selection records page-aware bounded context; oversize/blank selection rejected | [ ] | -| R3 Selectable Lean source | Declaration-aware selection; excerpt-only source explicitly labeled | [ ] | -| R4 Contextual action menu | Five actions, keyboard navigation, Escape, viewport positioning, mobile sheet | [ ] | +| R1 Proof-package reader | Two package cards, generic `/proofs/[proofId]` route, designed not-found state | [x] | +| R2 Selectable paper | PDF.js text selection records page-aware bounded context; oversize/blank selection rejected | [x] | +| R3 Selectable Lean source | Declaration-aware selection; excerpt-only source explicitly labeled | [x] | +| R4 Contextual action menu | Five actions, keyboard navigation, Escape, viewport positioning, mobile sheet | [x] | | R5 Grounded explanation | Server reconstructs package context; deployed GPT-5.6 response streams; source IDs constrained | [ ] | -| R6 Trust distinctions | Paper, Lean, prerequisite, and explanation labels; insufficient-evidence behavior | [ ] | -| R7 Contextual follow-up | Two follow-ups, bounded history, preserved content, retry | [ ] | -| R8 Lean verification record | Odd-sum record includes revision, toolchain, command, timestamp, result, `sorry`, axioms, digest | [ ] | +| R6 Trust distinctions | Paper, Lean, prerequisite, and explanation labels; insufficient-evidence behavior | [x] | +| R7 Contextual follow-up | Two follow-ups, bounded history, preserved content, retry | [x] | +| R8 Lean verification record | Odd-sum record includes revision, toolchain, command, timestamp, result, `sorry`, axioms, digest | [x] | | R9 Public demo and local run | Signed-out URL, server-only secrets, complete README, deterministic no-key tests | [ ] | ## PRD experience and quality gates -- [ ] The selected passage and source location precede generated text. -- [ ] Loading, streaming, completion, insufficient evidence, failure, retry, and cancellation are understandable. -- [ ] Color is not the only evidence or status cue. -- [ ] The second proof requires no proof-specific branch in shared reader components. -- [ ] Every displayed source chip resolves to registered package data. -- [ ] Every displayed verification claim resolves to a recorded build result. -- [ ] Public wording distinguishes generated explanation, curated correspondence, and machine-checked declaration. -- [ ] No success target is presented as a measured result unless a checked-in evaluation artifact supports it. +- [x] The selected passage and source location precede generated text. +- [x] Loading, streaming, completion, insufficient evidence, failure, retry, and cancellation are understandable. +- [x] Color is not the only evidence or status cue. +- [x] The second proof requires no proof-specific branch in shared reader components. +- [x] Every displayed source chip resolves to registered package data. +- [x] Every displayed verification claim resolves to a recorded build result. +- [x] Public wording distinguishes generated explanation, curated correspondence, and machine-checked declaration. +- [x] No success target is presented as a measured result unless a checked-in evaluation artifact supports it. ## Repository and rights - [ ] https://github.com/jjjhenriksen/interactive-proof is public while signed out, or the required judge accounts have explicit access. -- [ ] The release commit contains an appropriate code license. -- [ ] Third-party notices match every bundled paper, Lean source, font, and other redistributed asset. -- [ ] The README setup succeeds from a clean clone on Node 24. -- [ ] `.env.example` contains names only, never a real secret. -- [ ] `npm ci`, proof validation, unit tests, type checking, lint, production build, and Playwright release flows pass. -- [ ] The repository contains sample proof data and lets reviewers inspect the reader without spending API credits. +- [x] The release commit contains an appropriate code license. **Evidence:** MIT `LICENSE`. +- [x] Third-party notices record the status of bundled paper and Lean assets. **Evidence:** `THIRD_PARTY_NOTICES.md`; cycle-double-cover remains `review-required` and excluded from the configured public build. +- [x] The README setup succeeds from a clean dependency install on Node 24. +- [x] `.env.example` contains names only, never a real secret. +- [x] `npm ci`, proof validation, unit tests, type checking, lint, production builds, and Playwright release flows pass for `759d08a`. +- [x] The repository contains sample proof data and lets reviewers inspect the reader without spending API credits. - [ ] The release tag or commit SHA used for judging is recorded: ____________________. ## Demo video @@ -116,5 +137,8 @@ Test in a fresh private window with extensions disabled and no existing app sess 1. Hosted project URL: `[HOSTED_URL]` 2. Public YouTube demo URL: `[YOUTUBE_URL]` 3. Codex `/feedback` session ID covering the majority of core implementation: `[CODEX_FEEDBACK_SESSION_ID]` -4. Final rights decision for the cycle-double-cover PDF and Lean excerpts: permission confirmed, cleared replacement, or omitted from the public release. -5. Required Devpost identity choices: Submitter Type and eligible Country of Residence. +4. OpenAI API key added as a server-only deployment secret; never commit it or expose it with a `NEXT_PUBLIC_` prefix. +5. GitHub judge-access decision for the currently private repository. +6. Required Devpost identity choices: Submitter Type and eligible Country of Residence. + +The cycle-double-cover rights decision is no longer a blocker for the configured public build because the package is omitted from `PUBLIC_PROOF_IDS`. It becomes required again if that package is added to the deployed release. diff --git a/tests/e2e/docs.spec.ts b/tests/e2e/docs.spec.ts new file mode 100644 index 0000000..b8d60c4 --- /dev/null +++ b/tests/e2e/docs.spec.ts @@ -0,0 +1,29 @@ +import { expect, test } from "@playwright/test"; + +test("publishes setup guidance before API-key configuration", async ({ page }) => { + const runtimeErrors: string[] = []; + page.on("console", (message) => { + if (message.type() === "error") runtimeErrors.push(message.text()); + }); + page.on("pageerror", (error) => runtimeErrors.push(error.message)); + + await page.goto("/docs"); + + await expect(page.getByRole("heading", { level: 1, name: "From paper to grounded explanation." })).toBeVisible(); + await expect(page.getByRole("navigation", { name: "Documentation sections" })).toBeVisible(); + await expect(page.getByRole("link", { name: "Documentation", exact: true })).toBeVisible(); + await expect(page.getByRole("heading", { name: "Enable live AI explanations." })).toBeVisible(); + await expect(page.getByText("OPENAI_API_KEY=your_key_here", { exact: false })).toBeVisible(); + await expect(page.getByText("NEXT_PUBLIC_", { exact: false })).toBeVisible(); + await expect(page.getByRole("link", { name: /evaluation and security methodology/i })).toHaveAttribute( + "href", + "/methodology", + ); + + await page.getByRole("link", { name: "Open an upload workspace" }).click(); + await expect(page).toHaveURL(/\/upload$/); + await expect( + page.getByRole("heading", { level: 1, name: "Bring a paper. Add Lean when you have it." }), + ).toBeVisible(); + expect(runtimeErrors).toEqual([]); +});