diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..f7dcc3e --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +OPENROUTER_API_KEY= +OPENROUTER_MODEL=google/gemini-3-flash-preview +OPENROUTER_SITE_URL=http://localhost:3000 diff --git a/.gitignore b/.gitignore index cb8e0a2..e0ff70e 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,15 @@ id_ed25519 # Local automation caches /.cache/ /.cargo-audit/ + +# Oddly Exact web application +/node_modules/ +/.next/ +/.open-next/ +/.wrangler/ +/public/wasm/ +/next-env.d.ts +/worker-configuration.d.ts +/tsconfig.tsbuildinfo +/test-results/ +/playwright-report/ diff --git a/.impeccable.md b/.impeccable.md new file mode 100644 index 0000000..0c52478 --- /dev/null +++ b/.impeccable.md @@ -0,0 +1,79 @@ +# Oddly Exact web experience — design context + +## Audience + +The first audience is curious developers, technical professionals, and advanced +students who want to ask a mathematical question in natural language while +retaining an inspectable record of what was interpreted, computed, and +explained. + +## Jobs to be done + +- Ask a mathematical question without first translating it into calculator + syntax. +- See the interpretation, assumptions, selected operation, tool request, + deterministic result, and explanation as distinct stages. +- Understand why a mathematical procedure was chosen. +- Inspect or copy the exact calculator evidence rather than trusting prose. +- Receive a typed refusal when the request is ambiguous, unsupported, unsafe, + or exceeds declared resource limits. + +## Personality + +Precise, lucid, and quietly theatrical. + +The experience should feel like a master teacher unfolding a proof in a +beautiful research notebook—not a chatbot, generic AI dashboard, or +science-fiction command center. + +## Visual direction + +- Light theme derived from psychologically tuned Radix Colors. +- Material-adjacent hierarchy and interaction clarity without imitating Google. +- Radix primitives and Radix Icons for accessible controls and consistent + symbolic language. +- Warm neutral paper-like foundation, mineral blue for interpretation, + botanical green for verified calculator evidence, amber for assumptions, and + restrained vermilion for challenge or refusal states. +- Mathematical typography and kinetic disclosure provide the visual identity. +- Avoid cyan-on-black AI styling, purple gradients, glassmorphism, chat bubbles, + mascot imagery, and generic card grids. + +## Signature interaction + +The submitted question unfolds into a vertical proof theater: + +1. interpretation +2. assumptions +3. selected Oddly Exact operation +4. tool request +5. deterministic evidence +6. explanation + +Each stage emerges from the previous one with spatial continuity. The result is +not streamed as undifferentiated prose. The interface reveals a typed event +sequence and keeps the calculator response inspectable. + +## AI and trust boundary + +- OpenRouter supplies the language model. +- The OpenRouter API key remains server-side. +- The model may interpret, select an allowlisted calculator operation, request + a tool call, and explain returned evidence. +- The model must not supply final arithmetic from its own reasoning. +- Every numerical claim in the final answer must be grounded in a returned + Oddly Exact result. +- Tool calls are schema validated, size limited, time bounded, turn bounded, + and executed without shell interpolation. +- User text is untrusted data and cannot alter system policy, tool schemas, or + runtime limits. +- Unsupported or ambiguous requests fail closed with a useful explanation. + +## Accessibility and performance + +- Responsive from mobile through wide desktop. +- Keyboard complete, visible focus, accessible names, and WCAG AA contrast. +- Respect `prefers-reduced-motion` with a composed static alternative. +- The core experience must remain functional without advanced motion APIs. +- Target smooth interaction on a mid-range phone; use transform and opacity for + motion and avoid permanent animation loops. diff --git a/.openai/hosting.json b/.openai/hosting.json new file mode 100644 index 0000000..7d51d2a --- /dev/null +++ b/.openai/hosting.json @@ -0,0 +1,3 @@ +{ + "project_id": "appgprj_6a645d90e0988191a89e9ea18e98f715" +} diff --git a/Cargo.lock b/Cargo.lock index 10d938d..d396422 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,10 +2,23 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "accurate" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeeaf4e8673753ed50c991c2055d2ec90edfbdc590d8d31453b61f107715e0b9" +dependencies = [ + "cfg-if", + "embed-doc-image", + "ieee754", + "num-traits", +] + [[package]] name = "agent-calc" version = "0.1.0" dependencies = [ + "accurate", "argmin", "good_lp", "mutants", @@ -15,10 +28,12 @@ dependencies = [ "num-rational", "num-traits", "proptest", + "rug", "serde", "serde_json", "statrs", "uom", + "wasm-bindgen", ] [[package]] @@ -71,6 +86,18 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "az" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "bit-set" version = "0.8.0" @@ -110,6 +137,18 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "embed-doc-image" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af36f591236d9d822425cb6896595658fa558fcebf5ee8accac1d4b92c47166e" +dependencies = [ + "base64", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -301,6 +340,16 @@ version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f70749695b063ecbf6b62949ccccde2e733ec3ecbbd71d467dca4e5c6c97cca0" +[[package]] +name = "gmp-mpfr-sys" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7db155b537cb791b133341f99f68371d86ee7fa4c79aacfbc376d72d23c70531" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "good_lp" version = "1.15.1" @@ -338,6 +387,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +[[package]] +name = "ieee754" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9007da9cacbd3e6343da136e98b0d2df013f553d35bdec8b518f07bea768e19c" + [[package]] name = "indexmap" version = "2.14.0" @@ -380,6 +435,12 @@ version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -468,7 +529,7 @@ checksum = "973e7178a678cfd059ccec50887658d482ce16b0aa9da3888ddeab5cd5eb4889" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -586,7 +647,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", ] [[package]] @@ -703,6 +764,18 @@ version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +[[package]] +name = "rug" +version = "1.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07a8857882aec59d27254b02481c709327c13de6fad1da60bfc4f9783eaaa61e" +dependencies = [ + "az", + "gmp-mpfr-sys", + "libc", + "libm", +] + [[package]] name = "rustix" version = "1.1.4" @@ -776,7 +849,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -839,6 +912,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.117" @@ -880,7 +964,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -976,7 +1060,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -1094,7 +1178,7 @@ dependencies = [ "heck", "indexmap", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata", "wit-bindgen-core", "wit-component", @@ -1110,7 +1194,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core", "wit-bindgen-rust", ] @@ -1169,7 +1253,7 @@ checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5d6c4cd..1955f75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,11 @@ exclude = [ "target/**", ] +[lib] +crate-type = ["cdylib", "rlib"] + [dependencies] +accurate = { version = "0.4", default-features = false } argmin = { version = "0.11", default-features = false } mutants = "0.0.4" good_lp = { version = "1.15", default-features = false, features = ["microlp"] } @@ -33,5 +37,12 @@ serde_json = "1" statrs = { version = "0.18", default-features = false } uom = { version = "0.38", default-features = false, features = ["f64", "si", "std"] } +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" + +[package.metadata.wasm-pack.profile.release] +wasm-opt = false + [dev-dependencies] proptest = "1" +rug = { version = "1.30", default-features = false, features = ["float", "std"] } diff --git a/README.md b/README.md index 1fe70de..35a5cb4 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Tip my tokens](https://tokentip.to/badge/copyleftdev.svg?logo=1)](https://tokentip.to/@copyleftdev) -![The Standard Deviants](assets/article/agent-calc-standard-deviants-population-1000x420-v1.png) +![The Reference Beam: an independent reference exposing a mathematical fault](assets/article/oddly-exact-dev-cover-reference-beam-1000x420-v2.png) **Exact computation for AI agents.** @@ -17,7 +17,7 @@ traceable proof steps, assumptions, affine solving, calculus, inequalities, polynomials, intervals, finance, units, matrices, statistics, optimization, linear programming, and complex numbers. -**Proof over vibes. Meet the Standard Deviants.** +**Proof over vibes. Trust must survive challenge.** > **Compatibility:** Oddly Exact is the product name. The crate, executable, > schemas, and machine-readable contract remain `agent-calc`, so existing @@ -97,7 +97,10 @@ range analysis over closed rational domains, including exact critical points found through the polynomial derivative. The `finance` slice supports exact rational future value, present value, -discount factors, and net present value over typed expression cash flows. +discount factors, net present value, and discounted-cash-flow price models. +DCF inputs may be fixed-point decimal strings, which are parsed directly into +arbitrary-size rationals without passing through binary floating point. Price +output declares its rounding mode. Unit conversion and dimension-checked quantity arithmetic are exposed through `agent-calc units` and are backed by `uom`. @@ -288,6 +291,17 @@ printf '%s\n' '{ }' | cargo run -- finance ``` +```bash +printf '%s\n' '{ + "intent": "discounted_cash_flow", + "cash_flows": ["100.00", "110.00", "121.00"], + "discount_rate": "0.10", + "terminal_growth_rate": "0.03", + "decimal_places": 2, + "rounding_mode": "half_even" +}' | cargo run -- finance +``` + ```bash printf '%s\n' '{ "intent": "solve", diff --git a/app/api/solve/route.ts b/app/api/solve/route.ts new file mode 100644 index 0000000..3b9c678 --- /dev/null +++ b/app/api/solve/route.ts @@ -0,0 +1,265 @@ +import { + calculatorSchema, + executeCalculator, +} from "@/lib/oddly-exact/calculator"; +import { canonicalizeToolRequest } from "@/lib/oddly-exact/canonicalize"; +import { + classifyQuestion, + compileToolRequest, + explainEvidence, + openRouterErrorStatus, + selectToolSchema, +} from "@/lib/oddly-exact/openrouter"; +import { + assertSameOrigin, + enforceLocalRateLimit, + GuardrailError, + narrationIsGrounded, + normalizeQuestion, + safeFallbackNarration, + ungroundedNarrationNumbers, +} from "@/lib/oddly-exact/security"; +import { + QuestionSchema, + type CalculatorCommand, + type SolveEvent, +} from "@/lib/oddly-exact/types"; + +export const dynamic = "force-dynamic"; +export const maxDuration = 45; + +function clientKey(request: Request): string { + return ( + request.headers.get("cf-connecting-ip") || + request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || + "local" + ); +} + +function errorEvent(error: unknown): SolveEvent { + if (error instanceof GuardrailError) { + return { + type: "error", + at: Date.now(), + data: { code: error.code, reason: error.message, retryable: false }, + }; + } + + const status = openRouterErrorStatus(error); + const reason = + status === 401 || status === 403 + ? "The interpretation service is not authorized." + : status === 402 + ? "The interpretation service has insufficient credits." + : status === 408 || status === 429 || status >= 500 + ? "The interpretation service is temporarily unavailable." + : "The proof could not be assembled from the calculator evidence."; + + return { + type: "error", + at: Date.now(), + data: { + code: status === 429 ? "rate_limited" : "interpretation_failed", + reason, + retryable: status === 408 || status === 429 || status >= 500, + }, + }; +} + +export async function POST(request: Request): Promise { + if (!request.headers.get("content-type")?.includes("application/json")) { + return Response.json( + { error: { code: "invalid_content_type", reason: "Expected JSON." } }, + { status: 415 }, + ); + } + + const encoder = new TextEncoder(); + + return new Response( + new ReadableStream({ + async start(controller) { + const send = (event: SolveEvent) => { + controller.enqueue(encoder.encode(`${JSON.stringify(event)}\n`)); + }; + + try { + assertSameOrigin(request); + enforceLocalRateLimit(clientKey(request)); + + const body = QuestionSchema.parse(await request.json()); + const question = normalizeQuestion(body.question); + send({ + type: "accepted", + at: Date.now(), + data: { question }, + }); + + const classification = await classifyQuestion(question); + send({ + type: "interpreted", + at: Date.now(), + data: classification, + }); + + if (classification.command === "unsupported") { + send({ + type: "refused", + at: Date.now(), + data: { + code: "unsupported_question", + reason: + "This question could not be represented by one supported Oddly Exact operation.", + }, + }); + return; + } + + const command = classification.command as CalculatorCommand; + const fullSchema = await calculatorSchema(command, request.url); + const schema = await selectToolSchema( + question, + classification, + fullSchema, + ); + let compiled = await compileToolRequest( + question, + classification, + schema, + ); + compiled.request = canonicalizeToolRequest(compiled.request); + if (process.env.ODDLY_EXACT_DEBUG === "1") { + console.error( + "[oddly-exact] canonical tool request", + JSON.stringify(compiled.request), + ); + } + let calculatorEvidence = await executeCalculator( + command, + compiled.request, + request.url, + ); + let validationRetries = 0; + + if ( + calculatorEvidence.status === "rejected" && + calculatorEvidence.error?.code === "invalid_request" + ) { + compiled = await compileToolRequest( + question, + classification, + schema, + { + assistantMessage: compiled.assistantMessage, + toolCallId: compiled.toolCallId, + calculatorRejection: calculatorEvidence, + }, + ); + compiled.request = canonicalizeToolRequest(compiled.request); + if (process.env.ODDLY_EXACT_DEBUG === "1") { + console.error( + "[oddly-exact] corrected canonical tool request", + JSON.stringify(compiled.request), + ); + } + calculatorEvidence = await executeCalculator( + command, + compiled.request, + request.url, + ); + validationRetries = 1; + } + + send({ + type: "tool_call", + at: Date.now(), + data: { + command, + request: compiled.request, + validation_retries: validationRetries, + }, + }); + + send({ + type: "tool_result", + at: Date.now(), + data: calculatorEvidence, + }); + + const evidenceBytes = JSON.stringify(calculatorEvidence).length; + if (evidenceBytes > 64 * 1024) { + throw new GuardrailError( + "evidence_too_large", + "The calculator result is too large to narrate safely. Narrow the request.", + ); + } + + let proposedNarration = await explainEvidence( + question, + classification, + command, + compiled.request, + calculatorEvidence, + ); + const firstPassUngrounded = ungroundedNarrationNumbers( + proposedNarration, + question, + calculatorEvidence, + ); + if (firstPassUngrounded.length > 0) { + proposedNarration = await explainEvidence( + question, + classification, + command, + compiled.request, + calculatorEvidence, + { forbiddenNumbers: [...new Set(firstPassUngrounded)] }, + ); + } + const narration = narrationIsGrounded( + proposedNarration, + question, + calculatorEvidence, + ) + ? proposedNarration + : safeFallbackNarration(calculatorEvidence); + if ( + process.env.ODDLY_EXACT_DEBUG === "1" && + narration !== proposedNarration + ) { + console.error( + "[oddly-exact] withheld narration numbers", + ungroundedNarrationNumbers( + proposedNarration, + question, + calculatorEvidence, + ), + proposedNarration, + ); + } + + send({ type: "explained", at: Date.now(), data: narration }); + send({ + type: "complete", + at: Date.now(), + data: { grounded: true }, + }); + } catch (error) { + if (process.env.ODDLY_EXACT_DEBUG === "1") { + console.error("[oddly-exact] solve pipeline failure", error); + } + send(errorEvent(error)); + } finally { + controller.close(); + } + }, + }), + { + headers: { + "Content-Type": "application/x-ndjson; charset=utf-8", + "Cache-Control": "no-store", + "X-Content-Type-Options": "nosniff", + }, + }, + ); +} diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..dd701a5 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,1152 @@ +@import "@radix-ui/colors/sand.css"; +@import "@radix-ui/colors/indigo.css"; +@import "@radix-ui/colors/grass.css"; +@import "@radix-ui/colors/amber.css"; +@import "@radix-ui/colors/tomato.css"; + +:root { + --paper: var(--sand-1); + --paper-raised: var(--sand-2); + --ink: var(--sand-12); + --ink-soft: var(--sand-11); + --rule: var(--sand-6); + --blue: var(--indigo-10); + --blue-soft: var(--indigo-3); + --blue-rule: var(--indigo-6); + --green: var(--grass-10); + --green-soft: var(--grass-3); + --green-rule: var(--grass-6); + --amber: var(--amber-10); + --amber-soft: var(--amber-3); + --amber-rule: var(--amber-6); + --red: var(--tomato-10); + --red-soft: var(--tomato-3); + --red-rule: var(--tomato-6); + --space-xs: 0.25rem; + --space-sm: 0.5rem; + --space-md: 0.75rem; + --space-lg: 1rem; + --space-xl: 1.5rem; + --space-2xl: 2rem; + --space-3xl: 3rem; + --space-4xl: 4rem; + --space-5xl: 6rem; + --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); +} + +* { + box-sizing: border-box; +} + +html { + background: var(--paper); + color: var(--ink); + scroll-behavior: smooth; +} + +body { + min-height: 100vh; + margin: 0; + font-family: var(--font-body), sans-serif; + background: + radial-gradient(circle at 78% 5%, color-mix(in oklch, var(--indigo-4) 38%, transparent) 0, transparent 26rem), + linear-gradient(90deg, transparent 0 7.9%, color-mix(in oklch, var(--sand-6) 34%, transparent) 8%, transparent 8.1%), + var(--paper); + color: var(--ink); + font-size: 1rem; + line-height: 1.55; +} + +button, +textarea { + font: inherit; +} + +button, +a { + -webkit-tap-highlight-color: transparent; +} + +button:focus-visible, +a:focus-visible, +textarea:focus-visible { + outline: 3px solid color-mix(in oklch, var(--indigo-8) 62%, transparent); + outline-offset: 3px; +} + +::selection { + background: var(--indigo-5); + color: var(--indigo-12); +} + +.site { + width: min(100% - 2rem, 86rem); + margin: 0 auto; +} + +.siteHeader { + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + min-height: 5.25rem; + border-bottom: 1px solid var(--rule); +} + +.brand { + display: inline-flex; + width: fit-content; + align-items: center; + gap: var(--space-md); + color: var(--ink); + font-family: var(--font-display), sans-serif; + font-size: 1.05rem; + font-weight: 680; + letter-spacing: -0.025em; + text-decoration: none; +} + +.brandMark { + position: relative; + display: grid; + width: 1.65rem; + height: 1.65rem; + place-items: center; + border: 1px solid var(--sand-9); + border-radius: 50%; +} + +.brandMark::before, +.brandMark::after, +.brandMark span { + position: absolute; + display: block; + content: ""; + background: var(--ink); +} + +.brandMark::before { + width: 0.68rem; + height: 1px; +} + +.brandMark::after { + width: 1px; + height: 0.68rem; +} + +.brandMark span { + width: 0.26rem; + height: 0.26rem; + border-radius: 50%; + background: var(--red); +} + +.headerBoundary { + display: flex; + align-items: center; + gap: var(--space-sm); + color: var(--ink-soft); + font-size: 0.8rem; + letter-spacing: 0.025em; +} + +.headerBoundary svg { + color: var(--green); +} + +.githubLink { + display: inline-flex; + justify-self: end; + align-items: center; + gap: var(--space-sm); + color: var(--ink-soft); + font-size: 0.88rem; + text-decoration: none; + transition: color 180ms var(--ease-out-expo); +} + +.githubLink:hover { + color: var(--ink); +} + +.hero { + display: grid; + grid-template-columns: minmax(0, 1fr) 13rem; + gap: clamp(2rem, 8vw, 8rem); + align-items: end; + padding: clamp(4.5rem, 11vw, 9rem) 0 clamp(2.5rem, 6vw, 5rem); +} + +.heroCopy { + max-width: 62rem; +} + +.kicker { + display: inline-flex; + align-items: center; + gap: var(--space-sm); + margin-bottom: var(--space-xl); + color: var(--blue); + font-size: 0.78rem; + font-weight: 720; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.kicker::before { + width: 1.5rem; + height: 1px; + content: ""; + background: currentColor; +} + +h1, +h2, +h3, +p { + margin: 0; +} + +.hero h1 { + max-width: 14ch; + font-family: var(--font-display), sans-serif; + font-size: clamp(3.35rem, 7.5vw, 7.5rem); + font-variation-settings: "wdth" 88; + font-weight: 570; + letter-spacing: -0.066em; + line-height: 0.89; +} + +.hero h1 em { + color: var(--blue); + font-style: normal; + font-weight: 430; +} + +.heroCopy > p { + max-width: 46rem; + margin-top: var(--space-2xl); + color: var(--ink-soft); + font-size: clamp(1.05rem, 1.7vw, 1.3rem); + line-height: 1.55; +} + +.boundaryMap { + display: grid; + gap: var(--space-md); + padding-bottom: var(--space-sm); +} + +.boundaryMap > div { + display: grid; + grid-template-columns: 1.8rem 1fr; + gap: var(--space-md); + align-items: center; +} + +.boundaryMap > div > svg { + width: 1.3rem; + height: 1.3rem; + color: var(--blue); +} + +.boundaryMap > div:nth-of-type(2) > svg { + color: var(--green); +} + +.boundaryMap > div:nth-of-type(3) > svg { + color: var(--ink); +} + +.boundaryMap > svg { + margin-left: 0.47rem; + color: var(--sand-8); +} + +.boundaryMap span { + color: var(--ink); + font-size: 0.9rem; + font-weight: 650; +} + +.boundaryMap small { + display: block; + color: var(--ink-soft); + font-size: 0.68rem; + font-weight: 500; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.composerShell { + position: relative; + border-top: 1px solid var(--sand-7); + border-bottom: 1px solid var(--sand-7); + background: + linear-gradient(135deg, color-mix(in oklch, var(--indigo-2) 45%, transparent), transparent 55%), + var(--paper-raised); +} + +.composerShell::before, +.composerShell::after { + position: absolute; + top: -1px; + bottom: -1px; + width: calc((100vw - min(100vw - 2rem, 86rem)) / 2); + content: ""; + border-top: 1px solid var(--sand-7); + border-bottom: 1px solid var(--sand-7); + background: inherit; +} + +.composerShell::before { + right: 100%; +} + +.composerShell::after { + left: 100%; +} + +.composer { + display: grid; + gap: var(--space-md); + padding: clamp(1.5rem, 4vw, 2.75rem) 0; +} + +.composer label { + color: var(--ink-soft); + font-size: 0.82rem; + font-weight: 680; + letter-spacing: 0.05em; +} + +.composerInput { + display: grid; + grid-template-columns: 1fr auto; + gap: var(--space-lg); + align-items: end; +} + +.composer textarea { + width: 100%; + min-height: 4.5rem; + resize: vertical; + border: 0; + border-radius: 0; + outline: 0; + background: transparent; + color: var(--ink); + font-family: var(--font-display), sans-serif; + font-size: clamp(1.55rem, 3vw, 2.6rem); + font-variation-settings: "wdth" 94; + font-weight: 470; + letter-spacing: -0.035em; + line-height: 1.16; +} + +.composer textarea::placeholder { + color: var(--sand-9); +} + +.composer textarea:disabled { + opacity: 0.72; +} + +.submitButton { + display: inline-flex; + min-height: 3.25rem; + align-items: center; + justify-content: center; + gap: var(--space-sm); + padding: 0 var(--space-xl); + border: 1px solid var(--ink); + border-radius: 999px; + background: var(--ink); + color: var(--paper); + cursor: pointer; + font-weight: 690; + transition: + transform 160ms var(--ease-out-expo), + background 180ms var(--ease-out-expo); +} + +.submitButton:hover:not(:disabled) { + transform: translateY(-2px); + background: var(--blue); +} + +.submitButton:active:not(:disabled) { + transform: translateY(0) scale(0.98); +} + +.submitButton:disabled { + cursor: not-allowed; + opacity: 0.35; +} + +.submitButton--stop { + border-color: var(--red); + background: var(--red); +} + +.composerMeta { + display: flex; + justify-content: space-between; + gap: var(--space-lg); + color: var(--sand-10); + font-size: 0.72rem; +} + +.composerMeta span { + display: inline-flex; + align-items: center; + gap: var(--space-sm); +} + +.composerMeta svg { + color: var(--green); +} + +.examples { + display: grid; + grid-template-columns: repeat(3, 1fr); + border-top: 1px solid var(--sand-6); +} + +.examples button { + display: flex; + min-height: 4.5rem; + align-items: center; + justify-content: space-between; + gap: var(--space-lg); + padding: var(--space-lg) var(--space-xl); + border: 0; + border-right: 1px solid var(--sand-6); + background: transparent; + color: var(--ink-soft); + cursor: pointer; + text-align: left; + transition: + color 180ms var(--ease-out-expo), + background 180ms var(--ease-out-expo); +} + +.examples button:last-child { + border-right: 0; +} + +.examples button:hover { + background: var(--indigo-3); + color: var(--indigo-12); +} + +.examples svg { + flex: 0 0 auto; +} + +.proofWorkspace { + padding: clamp(3rem, 7vw, 6rem) 0; +} + +.proofHeading { + display: grid; + grid-template-columns: auto 1fr auto; + gap: var(--space-lg); + align-items: center; + margin-bottom: var(--space-3xl); + color: var(--sand-10); + font-size: 0.72rem; + font-weight: 650; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.proofRule { + height: 1px; + background: var(--rule); +} + +.proofRail { + position: relative; + display: grid; + max-width: 70rem; + gap: var(--space-2xl); + margin: 0 auto; +} + +.proofRail::before { + position: absolute; + top: 1.5rem; + bottom: 1.5rem; + left: 1.5rem; + width: 1px; + content: ""; + background: linear-gradient(var(--blue), var(--amber), var(--green), var(--sand-8)); + opacity: 0.36; +} + +.proofStage { + position: relative; + display: grid; + grid-template-columns: 3rem minmax(0, 1fr); + gap: var(--space-xl); +} + +.stageMarker { + z-index: 1; + display: grid; + width: 3rem; + height: 3rem; + place-items: center; + border: 1px solid currentColor; + border-radius: 50%; + background: var(--paper); +} + +.proofStage--blue .stageMarker, +.proofStage--blue .stageEyebrow { + color: var(--blue); +} + +.proofStage--amber .stageMarker, +.proofStage--amber .stageEyebrow { + color: var(--amber); +} + +.proofStage--green .stageMarker, +.proofStage--green .stageEyebrow { + color: var(--green); +} + +.proofStage--red .stageMarker, +.proofStage--red .stageEyebrow { + color: var(--red); +} + +.proofStage--sand .stageMarker, +.proofStage--sand .stageEyebrow { + color: var(--sand-11); +} + +.stageBody { + min-width: 0; + padding: var(--space-sm) 0 var(--space-3xl); + border-bottom: 1px solid var(--rule); +} + +.stageHeader { + display: flex; + justify-content: space-between; + gap: var(--space-xl); +} + +.stageEyebrow { + display: block; + margin-bottom: var(--space-sm); + font-size: 0.7rem; + font-weight: 740; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.stageHeader h2, +.terminalMessage h2 { + max-width: 26ch; + font-family: var(--font-display), sans-serif; + font-size: clamp(1.65rem, 3.5vw, 3rem); + font-variation-settings: "wdth" 92; + font-weight: 520; + letter-spacing: -0.045em; + line-height: 1.06; +} + +.stageLead { + max-width: 66ch; + margin-top: var(--space-xl); + color: var(--ink-soft); + font-size: 1.02rem; +} + +.decisionRow { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--space-md) var(--space-xl); + margin-top: var(--space-xl); +} + +.domainBadge, +.statusStamp { + display: inline-flex; + align-items: center; + gap: var(--space-sm); + padding: 0.4rem 0.7rem; + border: 1px solid var(--blue-rule); + border-radius: 999px; + background: var(--blue-soft); + color: var(--indigo-11); + font-size: 0.72rem; + font-weight: 720; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +.boundaryNote { + color: var(--sand-10); + font-size: 0.78rem; +} + +.assumptionField { + display: grid; + grid-template-columns: 12rem 1fr; + gap: var(--space-xl); + margin-top: var(--space-2xl); + padding: var(--space-xl); + border: 1px solid var(--blue-rule); + background: var(--blue-soft); +} + +.subheading { + display: flex; + gap: var(--space-sm); + align-items: center; + color: var(--indigo-11); + font-size: 0.78rem; + font-weight: 720; +} + +.assumptionField ul, +.caveatGroup ul { + display: grid; + gap: var(--space-sm); + margin: 0; + padding-left: 1.1rem; +} + +.assumptionField li { + color: var(--indigo-12); +} + +.toolSignature { + display: flex; + width: fit-content; + align-items: center; + gap: var(--space-md); + margin-top: var(--space-2xl); + padding: var(--space-md) var(--space-lg); + border: 1px solid var(--amber-rule); + background: var(--amber-soft); + color: var(--amber-12); + font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; + font-size: 0.85rem; +} + +.jsonDisclosure { + margin-top: var(--space-xl); + border-top: 1px solid var(--rule); +} + +.jsonTrigger { + display: flex; + width: 100%; + align-items: center; + justify-content: space-between; + gap: var(--space-lg); + padding: var(--space-lg) 0; + border: 0; + background: transparent; + color: var(--ink-soft); + cursor: pointer; + font-size: 0.82rem; + font-weight: 680; +} + +.jsonTrigger > span { + display: inline-flex; + align-items: center; + gap: var(--space-sm); +} + +.jsonTrigger .chevron { + transition: transform 240ms var(--ease-out-expo); +} + +.jsonTrigger[data-state="open"] .chevron { + transform: rotate(180deg); +} + +.jsonContent { + overflow: hidden; +} + +.jsonContent[data-state="open"] { + animation: disclosure-in 280ms var(--ease-out-expo); +} + +.jsonContent[data-state="closed"] { + animation: disclosure-out 180ms var(--ease-out-expo); +} + +@keyframes disclosure-in { + from { + opacity: 0; + transform: translateY(-0.5rem); + } +} + +@keyframes disclosure-out { + to { + opacity: 0; + transform: translateY(-0.35rem); + } +} + +.codeToolbar { + display: flex; + min-height: 2.5rem; + align-items: center; + justify-content: space-between; + padding: 0 var(--space-md); + border: 1px solid var(--sand-7); + border-bottom: 0; + background: var(--sand-3); + color: var(--sand-10); + font-size: 0.7rem; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.iconButton { + display: grid; + width: 2rem; + height: 2rem; + place-items: center; + border: 0; + border-radius: 50%; + background: transparent; + color: var(--ink-soft); + cursor: pointer; +} + +.iconButton:hover { + background: var(--sand-5); + color: var(--ink); +} + +.jsonContent pre { + max-height: 24rem; + overflow: auto; + margin: 0; + padding: var(--space-xl); + border: 1px solid var(--sand-7); + background: var(--sand-2); + color: var(--sand-12); + font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; + font-size: 0.75rem; + line-height: 1.65; + tab-size: 2; +} + +.verificationBand { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-xl); + margin-top: var(--space-xl); + padding: var(--space-lg); + border: 1px solid var(--green-rule); + background: var(--green-soft); +} + +.verificationSeal { + display: flex; + align-items: center; + gap: var(--space-md); + color: var(--grass-12); +} + +.verificationSeal > svg { + width: 1.3rem; + height: 1.3rem; + color: var(--green); +} + +.verificationSeal span { + display: grid; + font-size: 0.76rem; +} + +.verificationSeal strong { + font-family: var(--font-display), sans-serif; + font-size: 0.95rem; +} + +.statusStamp { + border-color: var(--green-rule); + background: var(--grass-2); + color: var(--grass-11); +} + +.statusStamp--rejected, +.statusStamp--failed { + border-color: var(--red-rule); + background: var(--red-soft); + color: var(--tomato-11); +} + +.evidenceGrid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin: var(--space-xl) 0 0; + border-top: 1px solid var(--green-rule); + border-inline-start: 1px solid var(--green-rule); +} + +.evidenceGrid > div { + min-width: 0; + padding: var(--space-lg); + border-right: 1px solid var(--green-rule); + border-bottom: 1px solid var(--green-rule); +} + +.evidenceGrid dt { + overflow: hidden; + color: var(--grass-11); + font-size: 0.68rem; + text-overflow: ellipsis; + white-space: nowrap; +} + +.evidenceGrid dd { + overflow-wrap: anywhere; + margin: var(--space-sm) 0 0; + color: var(--grass-12); + font-family: var(--font-display), sans-serif; + font-size: clamp(1rem, 2vw, 1.35rem); + font-weight: 560; +} + +.refusalCopy { + margin-top: var(--space-xl); + padding: var(--space-xl); + border: 1px solid var(--red-rule); + background: var(--red-soft); + color: var(--tomato-12); +} + +.refusalCopy strong { + text-transform: capitalize; +} + +.refusalCopy p { + margin-top: var(--space-sm); +} + +.formula { + overflow-x: auto; + margin-top: var(--space-2xl); + padding: clamp(1.25rem, 4vw, 2.5rem); + border-block: 1px solid var(--sand-7); + color: var(--ink); + font-size: clamp(1rem, 2.4vw, 1.55rem); + text-align: center; +} + +.method { + max-width: 65ch; + margin-top: var(--space-xl); + color: var(--ink-soft); + font-size: 1.05rem; +} + +.explanationSteps { + display: grid; + gap: 0; + margin: var(--space-2xl) 0 0; + padding: 0; + list-style: none; +} + +.explanationSteps li { + display: grid; + grid-template-columns: 3rem 1fr; + gap: var(--space-lg); + padding: var(--space-xl) 0; + border-top: 1px solid var(--rule); +} + +.explanationSteps > li > span { + color: var(--sand-9); + font-family: var(--font-display), sans-serif; + font-size: 0.8rem; + font-weight: 650; +} + +.explanationSteps h3 { + font-family: var(--font-display), sans-serif; + font-size: 1.08rem; + font-weight: 660; + letter-spacing: -0.015em; +} + +.explanationSteps p { + max-width: 66ch; + margin-top: var(--space-sm); + color: var(--ink-soft); +} + +.explanationSteps small { + display: block; + margin-top: var(--space-md); + color: var(--sand-9); + font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; + font-size: 0.68rem; +} + +.caveatGroup { + display: grid; + grid-template-columns: 6rem 1fr; + gap: var(--space-lg); + margin-top: var(--space-xl); + padding: var(--space-xl); + border: 1px solid var(--amber-rule); + background: var(--amber-soft); + color: var(--amber-12); +} + +.caveatGroup > span { + font-size: 0.72rem; + font-weight: 740; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.waitingStage { + display: flex; + min-height: 4rem; + align-items: center; + gap: var(--space-md); + margin-left: 4.5rem; + color: var(--sand-10); + font-size: 0.85rem; +} + +.spinner { + animation: spin 1.2s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.terminalMessage { + display: grid; + grid-template-columns: 3rem 1fr; + gap: var(--space-xl); + max-width: 70rem; + margin: var(--space-2xl) auto 0; + padding: var(--space-2xl); + border: 1px solid var(--red-rule); + background: var(--red-soft); + color: var(--tomato-12); +} + +.terminalMessage > svg { + width: 1.5rem; + height: 1.5rem; +} + +.terminalMessage span { + font-size: 0.7rem; + font-weight: 740; + letter-spacing: 0.1em; + text-transform: uppercase; +} + +.terminalMessage h2 { + margin-top: var(--space-sm); +} + +.terminalMessage button { + display: inline-flex; + align-items: center; + gap: var(--space-sm); + margin-top: var(--space-lg); + padding: 0; + border: 0; + background: transparent; + color: currentColor; + cursor: pointer; + font-weight: 700; +} + +.tooltip { + z-index: 20; + padding: 0.4rem 0.65rem; + border-radius: 0.35rem; + background: var(--ink); + color: var(--paper); + font-size: 0.72rem; +} + +.tooltipArrow { + fill: var(--ink); +} + +footer { + display: flex; + min-height: 6rem; + align-items: center; + justify-content: space-between; + gap: var(--space-xl); + border-top: 1px solid var(--rule); + color: var(--sand-10); + font-size: 0.76rem; +} + +.srOnly { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + clip-path: inset(50%); +} + +@media (max-width: 760px) { + body { + background: var(--paper); + } + + .site { + width: min(100% - 1.25rem, 86rem); + } + + .siteHeader { + grid-template-columns: 1fr auto; + min-height: 4.5rem; + } + + .headerBoundary { + display: none; + } + + .githubLink span { + display: none; + } + + .hero { + grid-template-columns: 1fr; + gap: var(--space-3xl); + padding-top: 4rem; + } + + .hero h1 { + font-size: clamp(3rem, 15vw, 5rem); + } + + .boundaryMap { + grid-template-columns: 1fr auto 1fr auto 1fr; + gap: var(--space-sm); + align-items: center; + } + + .boundaryMap > svg { + margin: 0; + transform: rotate(-90deg); + } + + .boundaryMap > div { + grid-template-columns: 1fr; + gap: var(--space-xs); + } + + .composerInput { + grid-template-columns: 1fr; + } + + .composer textarea { + min-height: 6rem; + } + + .submitButton { + width: 100%; + } + + .composerMeta span:first-child { + max-width: 18rem; + } + + .examples { + grid-template-columns: 1fr; + } + + .examples button { + border-right: 0; + border-bottom: 1px solid var(--sand-6); + } + + .examples button:last-child { + border-bottom: 0; + } + + .proofStage { + grid-template-columns: 2.5rem minmax(0, 1fr); + gap: var(--space-md); + } + + .proofRail::before { + left: 1.25rem; + } + + .stageMarker { + width: 2.5rem; + height: 2.5rem; + } + + .stageBody { + padding-top: 0; + } + + .assumptionField, + .caveatGroup { + grid-template-columns: 1fr; + } + + .verificationBand { + align-items: flex-start; + flex-direction: column; + } + + .evidenceGrid { + grid-template-columns: 1fr; + } + + .waitingStage { + margin-left: 3.5rem; + } + + footer { + align-items: flex-start; + flex-direction: column; + justify-content: center; + } +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } + + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; + } +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..2ac9a7a --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,38 @@ +import type { Metadata } from "next"; +import { + Atkinson_Hyperlegible_Next, + Bricolage_Grotesque, +} from "next/font/google"; +import "katex/dist/katex.min.css"; +import "./globals.css"; + +const display = Bricolage_Grotesque({ + subsets: ["latin"], + weight: "variable", + axes: ["opsz", "wdth"], + variable: "--font-display", +}); + +const body = Atkinson_Hyperlegible_Next({ + subsets: ["latin"], + weight: "variable", + style: ["normal", "italic"], + variable: "--font-body", +}); + +export const metadata: Metadata = { + title: "Oddly Exact — Proof over vibes", + description: + "Ask a mathematical question. Watch interpretation become deterministic, inspectable evidence.", + metadataBase: new URL("https://oddly-exact-math.chatgpt.team"), +}; + +export default function RootLayout({ + children, +}: Readonly<{ children: React.ReactNode }>) { + return ( + + {children} + + ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..58cd261 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,5 @@ +import { ProofTheater } from "@/components/proof-theater"; + +export default function Home() { + return ; +} diff --git a/assets/article/agent-calc-standard-deviants-population-1000x420-v1.png b/assets/article/agent-calc-standard-deviants-population-1000x420-v1.png deleted file mode 100644 index 74d8b45..0000000 Binary files a/assets/article/agent-calc-standard-deviants-population-1000x420-v1.png and /dev/null differ diff --git a/assets/article/oddly-exact-dev-cover-reference-beam-1000x420-v2.png b/assets/article/oddly-exact-dev-cover-reference-beam-1000x420-v2.png new file mode 100644 index 0000000..aad4f3a Binary files /dev/null and b/assets/article/oddly-exact-dev-cover-reference-beam-1000x420-v2.png differ diff --git a/assets/article/oddly-exact-dev-cover-reference-beam-master-v2.png b/assets/article/oddly-exact-dev-cover-reference-beam-master-v2.png new file mode 100644 index 0000000..f73ed7d Binary files /dev/null and b/assets/article/oddly-exact-dev-cover-reference-beam-master-v2.png differ diff --git a/components/formula.tsx b/components/formula.tsx new file mode 100644 index 0000000..a6d4b3c --- /dev/null +++ b/components/formula.tsx @@ -0,0 +1,28 @@ +"use client"; + +import katex from "katex"; +import { useMemo } from "react"; + +export function Formula({ expression }: { expression: string }) { + const markup = useMemo(() => { + if (!expression.trim()) return ""; + + return katex.renderToString(expression, { + displayMode: true, + throwOnError: false, + strict: "warn", + trust: false, + output: "htmlAndMathml", + }); + }, [expression]); + + if (!markup) return null; + + return ( +
+ ); +} diff --git a/components/proof-theater.tsx b/components/proof-theater.tsx new file mode 100644 index 0000000..7efb3ef --- /dev/null +++ b/components/proof-theater.tsx @@ -0,0 +1,708 @@ +"use client"; + +import * as Accordion from "@radix-ui/react-accordion"; +import { + ArrowDownIcon, + ArrowRightIcon, + CheckCircledIcon, + ChevronDownIcon, + CodeIcon, + CopyIcon, + CrossCircledIcon, + CubeIcon, + ExclamationTriangleIcon, + GitHubLogoIcon, + LightningBoltIcon, + LockClosedIcon, + MagicWandIcon, + ReaderIcon, + ReloadIcon, + SewingPinFilledIcon, + StopIcon, +} from "@radix-ui/react-icons"; +import * as Tooltip from "@radix-ui/react-tooltip"; +import { + AnimatePresence, + LayoutGroup, + motion, + useReducedMotion, +} from "motion/react"; +import Link from "next/link"; +import { + FormEvent, + useCallback, + useMemo, + useRef, + useState, +} from "react"; + +import type { + CalculatorEnvelope, + Classification, + Narration, + SolveEvent, +} from "@/lib/oddly-exact/types"; +import { Formula } from "./formula"; + +const EXAMPLES = [ + "Solve 3x + 7 = 28", + "What are the mean and sample variance of 12, 15, 18, 21, 24?", + "Differentiate 3x² + 2x − 5", +]; + +type ScalarLeaf = { + path: string; + value: string; +}; + +function flattenScalars( + value: unknown, + path = "$", + leaves: ScalarLeaf[] = [], +): ScalarLeaf[] { + if (leaves.length >= 10) return leaves; + + if ( + value === null || + typeof value === "string" || + typeof value === "number" || + typeof value === "boolean" + ) { + leaves.push({ path, value: String(value) }); + return leaves; + } + + if (Array.isArray(value)) { + value.slice(0, 5).forEach((item, index) => { + flattenScalars(item, `${path}[${index}]`, leaves); + }); + return leaves; + } + + if (typeof value === "object") { + Object.entries(value as Record).forEach(([key, item]) => { + flattenScalars(item, `${path}.${key}`, leaves); + }); + } + + return leaves; +} + +function stageMotion(reducedMotion: boolean | null) { + return { + initial: reducedMotion ? false : { opacity: 0, y: 22, scale: 0.99 }, + animate: { opacity: 1, y: 0, scale: 1 }, + transition: { + duration: reducedMotion ? 0 : 0.52, + ease: [0.16, 1, 0.3, 1] as [number, number, number, number], + }, + }; +} + +function CopyButton({ value, label }: { value: string; label: string }) { + const [copied, setCopied] = useState(false); + + const copy = async () => { + await navigator.clipboard.writeText(value); + setCopied(true); + window.setTimeout(() => setCopied(false), 1600); + }; + + return ( + + + + + + + {copied ? "Copied" : label} + + + + + ); +} + +function JsonDisclosure({ + label, + value, +}: { + label: string; + value: unknown; +}) { + const serialized = JSON.stringify(value, null, 2); + + return ( + + + + + + + {label} + + + + + +
+ JSON · exact contract + +
+
+            {serialized}
+          
+
+
+
+ ); +} + +function Stage({ + number, + tone, + icon, + eyebrow, + title, + children, +}: { + number: string; + tone: "blue" | "amber" | "green" | "sand" | "red"; + icon: React.ReactNode; + eyebrow: string; + title: string; + children: React.ReactNode; +}) { + const reducedMotion = useReducedMotion(); + + return ( + +
+ {icon} +
+
+
+
+ + {number} / {eyebrow} + +

{title}

+
+
+ {children} +
+
+ ); +} + +function WaitingStage({ label }: { label: string }) { + return ( + + + {label} + + ); +} + +function InterpretationStage({ + data, +}: { + data: Classification; +}) { + return ( + } + eyebrow="Interpretation" + title={data.interpretation} + > +

{data.rationale}

+
+ + + {data.command.replaceAll("_", " ")} + + + Procedure selected by model · no calculation performed + +
+ {data.assumptions.length > 0 && ( +
+
+ + Explicit assumptions +
+
    + {data.assumptions.map((assumption) => ( +
  • {assumption}
  • + ))} +
+
+ )} +
+ ); +} + +function ToolCallStage({ + command, + request, + validationRetries = 0, +}: { + command: string; + request: Record; + validationRetries?: number; +}) { + return ( + } + eyebrow="Formalization" + title="Natural language became a typed request." + > +

+ The selected domain supplied its own executable JSON Schema. The model + could populate that contract, but it could not change it. +

+
+ oddly_exact + + {command} +
+ {validationRetries > 0 && ( +

+ The first argument shape failed deterministic validation. The model + received only that typed rejection and corrected the call. +

+ )} + +
+ ); +} + +function EvidenceStage({ data }: { data: CalculatorEnvelope }) { + const leaves = useMemo(() => flattenScalars(data.output), [data.output]); + const executed = data.status === "executed"; + + return ( + : } + eyebrow="Deterministic evidence" + title={ + executed + ? "The calculator answered under contract." + : "The calculator refused the request." + } + > +
+
+ + + {data.contract_version ?? "calc1"} + Rust · WebAssembly · model-independent + +
+ + {data.status} + +
+ + {executed && leaves.length > 0 && ( +
+ {leaves.map((leaf) => ( +
+
{leaf.path.replace("$.output.", "").replace("$.", "")}
+
{leaf.value}
+
+ ))} +
+ )} + + {!executed && data.error && ( +
+ {data.error.code.replaceAll("_", " ")} +

{data.error.reason}

+
+ )} + + +
+ ); +} + +function NarrationStage({ data }: { data: Narration }) { + return ( + } + eyebrow="Explanation" + title={data.answer} + > + +

{data.method}

+ +
    + {data.steps.map((step, index) => ( +
  1. + {String(index + 1).padStart(2, "0")} +
    +

    {step.title}

    +

    {step.explanation}

    + {step.evidence_paths.length > 0 && ( + {step.evidence_paths.join(" · ")} + )} +
    +
  2. + ))} +
+ + {data.caveats.length > 0 && ( +
+ Caveats +
    + {data.caveats.map((caveat) => ( +
  • {caveat}
  • + ))} +
+
+ )} +
+ ); +} + +export function ProofTheater() { + const [question, setQuestion] = useState(""); + const [events, setEvents] = useState([]); + const [busy, setBusy] = useState(false); + const abortRef = useRef(null); + const reducedMotion = useReducedMotion(); + + const interpretation = events.find( + (event) => event.type === "interpreted", + )?.data; + const toolCall = events.find((event) => event.type === "tool_call")?.data; + const toolResult = events.find( + (event) => event.type === "tool_result", + )?.data; + const narration = events.find((event) => event.type === "explained")?.data; + const refusal = events.find((event) => event.type === "refused")?.data; + const error = events.find((event) => event.type === "error")?.data; + const hasRun = events.length > 0; + + const appendEvent = useCallback((event: SolveEvent) => { + setEvents((current) => [...current, event]); + }, []); + + const solve = async (event?: FormEvent) => { + event?.preventDefault(); + const trimmed = question.trim(); + if (trimmed.length < 3 || busy) return; + + abortRef.current?.abort(); + const abort = new AbortController(); + abortRef.current = abort; + setEvents([]); + setBusy(true); + + try { + const response = await fetch("/api/solve", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ question: trimmed }), + signal: abort.signal, + }); + + if (!response.ok || !response.body) { + throw new Error("The proof stream could not be opened."); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ""; + + while (true) { + const { done, value } = await reader.read(); + buffer += decoder.decode(value ?? new Uint8Array(), { stream: !done }); + const lines = buffer.split("\n"); + buffer = lines.pop() ?? ""; + + for (const line of lines) { + if (!line.trim()) continue; + appendEvent(JSON.parse(line) as SolveEvent); + } + + if (done) { + if (buffer.trim()) appendEvent(JSON.parse(buffer) as SolveEvent); + break; + } + } + } catch (requestError) { + if ((requestError as Error).name !== "AbortError") { + appendEvent({ + type: "error", + at: Date.now(), + data: { + code: "connection_failed", + reason: + "The proof stream was interrupted before the evidence arrived.", + retryable: true, + }, + }); + } + } finally { + setBusy(false); + abortRef.current = null; + } + }; + + const stop = () => { + abortRef.current?.abort(); + setBusy(false); + }; + + const tryExample = (example: string) => { + setQuestion(example); + setEvents([]); + }; + + return ( + + +
+
+ + + + + Oddly Exact + +
+ + The model never does the math +
+ + + Source + +
+ +
+ + An instrument for mathematical truth +

+ Ask the question. +
+ Watch the proof unfold. +

+

+ AI interprets your intent. Oddly Exact performs the computation. + Every assumption, tool call, and result remains visible. +

+
+ + +
+ + + Model + interprets + +
+ +
+ + + Calculator + computes + +
+ +
+ + + Evidence + explains + +
+
+
+ + +
+ +
+