diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..189ab88 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,30 @@ +# Repository Guidelines + +## Project Structure & Module Organization + +This is a pnpm TypeScript monorepo. `apps/web/` contains the Next.js UI and API routes; keep browser code in `src/client/`, server logic in `src/server/`, request contracts in `src/contracts/`, and React components in `src/components/`. Domain packages live under `packages/`: `report-schema`, `decision-engine`, `moss-adapter`, and `preflight-core`. Packages keep implementation in `src/` and tests in `test/`. Repository integration suites are in `tests/`; synthetic inputs are in `fixtures/`; architecture and security rules are in `docs/`. `vendor/moss/` is a pinned Git submodule—do not edit it as application code. + +## Build, Test, and Development Commands + +- `pnpm install --frozen-lockfile` installs the pinned workspace dependencies (Node 22.13–22.x and pnpm 11 required). +- `pnpm dev` starts the web app in development mode. +- `pnpm build` builds packages in dependency order, then builds Next.js. +- `pnpm test` builds and runs all Vitest tests. +- `pnpm test:e2e` runs Playwright against a production build; artifacts go to `artifacts/test-results/`. +- `pnpm check` runs the CI quality gate: formatting, linting, types, builds, tests, coverage, and smoke checks. + +## Coding Style & Naming Conventions + +Use strict TypeScript and ES modules. Biome enforces two-space indentation and lint rules; run `pnpm format:check` and `pnpm lint`. Follow existing naming: kebab-case files (`preflight-service.ts`), PascalCase React components, camelCase functions, and uppercase constants. Prefer explicit types at package and API boundaries. Keep domain logic deterministic and side-effect free where established. + +## Testing Guidelines + +Vitest files use `*.test.ts` or `*.test.tsx`; Playwright files use `*.e2e.ts`; Node entry-point checks use `*.smoke.mjs`. Place tests beside their package or app. Add regression coverage for changed behavior and use synthetic fixtures with explicit `FIXTURE` provenance. `preflight-core` enforces 100% coverage via `pnpm test:coverage`. + +## Commit & Pull Request Guidelines + +Start every change from an approved Issue. History uses concise imperative subjects such as `feat(web): orchestrate fail-closed preflight API`, `test: ...`, and milestone labels like `M3-04: ...`; include the PR number when GitHub squashes. PRs must close the Issue and document actual changes, non-goals, verification evidence, risks, and required maintainer decisions. Include screenshots for UI changes. Never push or merge `main`; only the Maintainer merges after checks and review conversations are resolved. + +## Security & Evidence Boundaries + +Never add private keys, signing, transaction submission, or unverified protocol/token/spender addresses. Do not present mocks or fixtures as real Moss or Monad evidence, weaken mandatory `STOP` conditions, or rewrite the original Capability Tree. Architecture, schema, trust-boundary, and decision-semantics changes require explicit Maintainer approval. diff --git a/apps/web/app/forensic-ledger.css b/apps/web/app/forensic-ledger.css new file mode 100644 index 0000000..aed8768 --- /dev/null +++ b/apps/web/app/forensic-ledger.css @@ -0,0 +1,2771 @@ +:root { + --ledger-dark: #090b10; + --ledger-moss: #0e1d1c; + --ledger-ink: #18231f; + --ledger-paper: #f4f2ec; + --ledger-sheet: #fffefa; + --ledger-teal: #0c7565; + --ledger-mint: #42c6a7; + --ledger-red: #c7423a; + --ledger-red-soft: #f1d8d2; + --ledger-violet: #7c20e8; + --ledger-line: rgb(24 35 31 / 25%); + --ledger-line-dark: rgb(255 254 250 / 20%); + --display-font: + "Arial Narrow", "Roboto Condensed", "Helvetica Neue Condensed", Impact, + sans-serif; + --body-font: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, + "Segoe UI", sans-serif; + --mono-font: + "IBM Plex Mono", "Cascadia Mono", "SFMono-Regular", Consolas, monospace; + --cut-lg: polygon( + 0 0, + calc(100% - 22px) 0, + 100% 22px, + 100% 100%, + 22px 100%, + 0 calc(100% - 22px) + ); + --cut-sm: polygon( + 0 0, + calc(100% - 10px) 0, + 100% 10px, + 100% 100%, + 10px 100%, + 0 calc(100% - 10px) + ); +} + +html { + scroll-behavior: smooth; + scroll-padding-top: 92px; +} + +body { + background-color: var(--ledger-paper); + background-image: url("/visuals/warm-paper-seamless.webp"); + color: var(--ledger-ink); + font-family: var(--body-font); + overflow-x: clip; +} + +::selection { + background: var(--ledger-mint); + color: var(--ledger-dark); +} + +.workbench-shell { + background: var(--ledger-paper); + min-height: 100vh; + overflow: clip; +} + +.app-bar { + align-items: center; + background: var(--ledger-moss); + border-bottom: 1px solid rgb(66 198 167 / 55%); + box-shadow: inset 0 -2px 0 var(--ledger-violet); + color: var(--ledger-sheet); + display: flex; + justify-content: space-between; + min-height: 76px; + padding: 9px clamp(18px, 3vw, 52px); + position: fixed; + top: 0; + width: 100%; + z-index: 50; +} + +.brand-lockup { + align-items: center; + color: inherit; + display: flex; + gap: 12px; + min-width: 0; + text-decoration: none; +} + +.brand-logo { + background: #fff; + border: 1px solid rgb(255 255 255 / 35%); + display: block; + height: 54px; + object-fit: cover; + width: 55px; +} + +.brand-copy { + display: grid; + gap: 2px; +} + +.brand-name { + color: var(--ledger-sheet); + font-size: 1rem; + font-weight: 800; + letter-spacing: 0; +} + +.brand-surface { + color: rgb(255 254 250 / 62%); + font-family: var(--mono-font); + font-size: 0.66rem; + letter-spacing: 0; + text-transform: uppercase; +} + +.environment-list { + flex-wrap: nowrap; + gap: 9px; + max-width: 68%; + min-width: 0; +} + +.environment-item, +.state-badge, +.provenance-badge { + border-radius: 0; + font-family: var(--mono-font); + font-size: 0.65rem; + letter-spacing: 0; + padding: 6px 8px; +} + +.environment-item { + color: rgb(255 254 250 / 72%); + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.forensic-intro { + align-items: end; + background-color: var(--ledger-dark); + background-image: url("/visuals/forensic-desk-desktop.webp"); + background-position: center; + background-repeat: no-repeat; + background-size: cover; + color: var(--ledger-sheet); + display: grid; + isolation: isolate; + min-height: 560px; + overflow: hidden; + padding: 124px clamp(28px, 5.5vw, 108px) clamp(52px, 6vw, 92px); + position: relative; +} + +.forensic-intro::before { + background: + linear-gradient(180deg, rgb(9 11 16 / 34%) 0%, transparent 28%), + linear-gradient( + 0deg, + rgb(9 11 16 / 92%) 0%, + rgb(9 11 16 / 20%) 58%, + transparent 78% + ); + content: ""; + inset: 0; + pointer-events: none; + position: absolute; + z-index: -1; +} + +.forensic-intro::after { + background-image: + radial-gradient( + circle at center, + rgb(66 198 167 / 18%) 0 1px, + transparent 1.4px + ), + linear-gradient( + 90deg, + transparent 49.8%, + rgb(66 198 167 / 18%) 50%, + transparent 50.2% + ); + background-size: + 28px 28px, + 100% 100%; + content: ""; + inset: 0; + mix-blend-mode: screen; + opacity: 0.14; + pointer-events: none; + position: absolute; + z-index: -1; +} + +.intro-technical-frame { + border: 1px solid rgb(66 198 167 / 55%); + clip-path: var(--cut-lg); + inset: 100px 30px 30px; + pointer-events: none; + position: absolute; +} + +.intro-technical-frame::before, +.intro-technical-frame::after { + background: var(--ledger-mint); + content: ""; + height: 2px; + position: absolute; + top: -1px; + width: 86px; +} + +.intro-technical-frame::before { + left: 28%; +} + +.intro-technical-frame::after { + right: 10%; +} + +.intro-provenance { + align-items: center; + color: var(--ledger-mint); + display: flex; + font-family: var(--mono-font); + font-size: 0.82rem; + gap: 10px; + left: 50%; + letter-spacing: 0; + position: absolute; + text-transform: uppercase; + top: 22%; + transform: translateX(-50%); + white-space: nowrap; +} + +.intro-provenance strong { + color: var(--ledger-sheet); + font-weight: 600; +} + +.intro-provenance-mark { + color: var(--ledger-teal); + font-weight: 900; +} + +.intro-copy { + max-width: none; + min-width: 0; + position: relative; + width: 100%; + z-index: 2; +} + +.intro-chapter, +.workbench-kicker, +.chapter-title { + color: var(--ledger-mint); + display: block; + font-family: var(--mono-font); + font-size: 0.72rem; + letter-spacing: 0; + margin-bottom: 18px; + text-transform: uppercase; +} + +.intro-copy h1 { + font-family: var(--display-font); + font-size: 7rem; + font-stretch: condensed; + font-weight: 800; + letter-spacing: 0; + line-height: 0.78; + margin: 0; + text-transform: uppercase; + white-space: nowrap; +} + +.intro-copy p { + font-family: var(--mono-font); + font-size: 1rem; + letter-spacing: 0; + line-height: 1.5; + margin: 28px 0 34px; +} + +.intro-cta { + align-items: center; + background: rgb(9 11 16 / 78%); + border: 1px solid var(--ledger-mint); + clip-path: var(--cut-sm); + color: var(--ledger-mint); + display: inline-flex; + font-family: var(--mono-font); + font-size: 0.86rem; + gap: 34px; + justify-content: space-between; + letter-spacing: 0; + min-width: 250px; + padding: 17px 20px; + text-decoration: none; + text-transform: uppercase; + transition: + background 160ms ease, + color 160ms ease; +} + +.intro-cta svg { + fill: none; + stroke: currentColor; + stroke-linecap: square; + stroke-linejoin: miter; + stroke-width: 1.5; + transition: transform 160ms ease; +} + +.intro-cta:hover { + background: var(--ledger-mint); + color: var(--ledger-dark); +} + +.intro-cta:hover svg { + transform: translateX(4px); +} + +.intro-evidence-spine { + bottom: 52px; + display: grid; + grid-template-columns: repeat(4, minmax(84px, 1fr)); + list-style: none; + margin: 0; + padding: 0; + position: absolute; + right: clamp(28px, 5vw, 96px); + width: min(48vw, 720px); + z-index: 2; +} + +.intro-evidence-spine::before { + background: var(--ledger-mint); + content: ""; + height: 1px; + left: 9%; + position: absolute; + right: 9%; + top: 12px; +} + +.intro-evidence-spine li { + align-items: center; + display: grid; + gap: 8px; + justify-items: center; + position: relative; +} + +.intro-evidence-spine li > span { + align-items: center; + background: var(--ledger-moss); + border: 1px solid var(--ledger-mint); + border-radius: 50%; + color: var(--ledger-mint); + display: flex; + font-family: var(--mono-font); + font-size: 0.62rem; + height: 25px; + justify-content: center; + width: 25px; + z-index: 1; +} + +.intro-evidence-spine strong { + color: rgb(255 254 250 / 72%); + font-family: var(--mono-font); + font-size: 0.6rem; + font-weight: 500; + letter-spacing: 0; + text-transform: uppercase; +} + +.workbench-main { + background: + linear-gradient(rgb(244 242 236 / 82%), rgb(244 242 236 / 82%)), + url("/visuals/warm-paper-seamless.webp"); + max-width: none; + padding: clamp(72px, 8vw, 124px) clamp(20px, 4vw, 72px) 120px; +} + +.workbench-heading { + align-items: end; + border-bottom: 1px solid var(--ledger-line); + margin: 0 auto 32px; + max-width: 1600px; + padding-bottom: 24px; +} + +.workbench-heading h2 { + font-family: var(--display-font); + font-size: 4.5rem; + font-stretch: condensed; + letter-spacing: 0; + line-height: 0.9; + margin: 0 0 14px; + text-transform: uppercase; +} + +.workbench-heading p { + color: rgb(24 35 31 / 68%); + font-family: var(--mono-font); + font-size: 0.75rem; + letter-spacing: 0; +} + +.workbench-kicker { + color: var(--ledger-teal); + margin-bottom: 14px; +} + +.state-badge { + background: transparent; + clip-path: var(--cut-sm); + min-width: 88px; + text-align: center; +} + +.state-badge.idle { + color: rgb(24 35 31 / 55%); +} + +.state-badge.running { + color: var(--ledger-teal); +} + +.state-badge.result { + color: #8a5a00; +} + +.state-badge.error { + color: var(--ledger-red); +} + +.workbench-grid { + align-items: start; + gap: 26px; + grid-template-columns: minmax(328px, 390px) minmax(0, 1fr); + margin: 0 auto; + max-width: 1600px; +} + +.control-pane, +.result-pane { + border: 1px solid var(--ledger-line); + border-radius: 0; +} + +.control-pane { + background: + linear-gradient(rgb(255 254 250 / 92%), rgb(255 254 250 / 92%)), + url("/visuals/warm-paper-seamless.webp"); + border-right: 1px solid var(--ledger-line); + clip-path: var(--cut-lg); + max-height: calc(100svh - 110px); + overflow: auto; + padding: 24px; + position: sticky; + scrollbar-color: var(--ledger-teal) transparent; + scrollbar-width: thin; + top: 92px; +} + +.pane-heading, +.result-header { + border-color: var(--ledger-line); +} + +.pane-heading h2, +.result-header h2 { + font-family: var(--display-font); + font-size: 1.5rem; + letter-spacing: 0; + text-transform: uppercase; +} + +.pane-heading p, +.result-header p { + font-family: var(--mono-font); + font-size: 0.66rem; +} + +.mode-control { + background: rgb(14 29 28 / 8%); + border-color: var(--ledger-line); + padding: 3px; +} + +.mode-control button { + border-radius: 0; + font-family: var(--mono-font); + font-size: 0.68rem; + letter-spacing: 0; +} + +.mode-control button.active { + background: var(--ledger-sheet); + box-shadow: 0 1px 0 rgb(24 35 31 / 18%); + color: var(--ledger-ink); +} + +.fixture-options button, +.check-control, +.field-control input { + border-color: rgb(24 35 31 / 30%); + border-radius: 0; +} + +.fixture-options button { + background: rgb(255 254 250 / 68%); + clip-path: var(--cut-sm); + padding: 12px 13px; +} + +.fixture-options button.selected { + background: rgb(66 198 167 / 15%); + border-color: var(--ledger-teal); + box-shadow: inset 3px 0 0 var(--ledger-teal); +} + +.fixture-label, +.protocol-label { + font-family: var(--mono-font); + font-size: 0.74rem; +} + +.fixture-description, +.fixture-loaded, +.fixture-required, +.field-control small, +.asset-control small, +.check-control small, +.selection-rule small { + font-family: var(--mono-font); + font-size: 0.62rem; +} + +.field-label, +.protocol-control legend, +dt { + color: rgb(24 35 31 / 58%); + font-family: var(--mono-font); + font-size: 0.62rem; + letter-spacing: 0; +} + +.field-control input { + background: rgb(255 254 250 / 78%); + font-family: var(--mono-font); + font-size: 0.74rem; + min-height: 44px; +} + +.field-control input:focus-visible { + border-color: var(--ledger-teal); + outline-color: rgb(12 117 101 / 28%); +} + +.fixed-asset { + background: rgb(12 117 101 / 8%); + border-left-color: var(--ledger-teal); +} + +.command-button { + border-radius: 0; + clip-path: var(--cut-sm); + font-family: var(--mono-font); + font-size: 0.7rem; + letter-spacing: 0; + min-height: 48px; + text-transform: uppercase; +} + +.command-button.primary { + background: var(--ledger-teal); +} + +.command-button.primary:hover:not(:disabled) { + background: var(--ledger-moss); +} + +.command-button.secondary { + background: transparent; + border-color: rgb(24 35 31 / 42%); +} + +.fixture-request-facts > div, +.request-facts > div { + border-color: var(--ledger-line); +} + +.result-pane { + background: transparent; + border: 0; + display: block; + overflow: visible; +} + +.result-header { + background: var(--ledger-moss); + border: 1px solid rgb(9 11 16 / 20%); + clip-path: var(--cut-sm); + color: var(--ledger-sheet); + margin-bottom: 16px; + min-height: 82px; + padding: 17px 20px; +} + +.result-header:focus-visible { + outline: 1px solid var(--ledger-mint); + outline-offset: 4px; +} + +.result-header p { + color: rgb(255 254 250 / 58%); +} + +.provenance-badge { + color: var(--ledger-mint); +} + +.provenance-label { + color: rgb(255 254 250 / 50%); +} + +.result-surface { + min-height: 600px; + padding: 0; +} + +.empty-state, +.loading-state, +.error-state { + align-content: center; + background: + linear-gradient(rgb(255 254 250 / 89%), rgb(255 254 250 / 89%)), + url("/visuals/warm-paper-seamless.webp"); + border: 1px solid var(--ledger-line); + clip-path: var(--cut-lg); + min-height: 620px; + overflow: hidden; + padding: clamp(32px, 6vw, 88px); + position: relative; +} + +.empty-state::after, +.loading-state::after, +.error-state::after { + background-image: radial-gradient( + circle, + rgb(12 117 101 / 18%) 0 1px, + transparent 1.3px + ); + background-size: 20px 20px; + content: ""; + inset: auto 0 0 50%; + opacity: 0.4; + pointer-events: none; + position: absolute; + top: 45%; +} + +.empty-state-index { + color: rgb(12 117 101 / 14%); + font-family: var(--display-font); + font-size: 8rem; + font-weight: 800; + line-height: 0.8; + position: absolute; + right: 4%; + top: 10%; +} + +.empty-state h3, +.loading-state h3, +.error-state h3 { + font-family: var(--display-font); + font-size: 4rem; + letter-spacing: 0; + line-height: 0.9; + text-transform: uppercase; +} + +.empty-state p, +.loading-state p, +.error-state p { + font-family: var(--mono-font); + font-size: 0.76rem; +} + +.empty-evidence-track { + display: grid; + grid-template-columns: repeat(4, 1fr); + list-style: none; + margin: 70px 0 0; + padding: 0; + width: min(680px, 100%); +} + +.empty-evidence-track li { + border-top: 1px solid var(--ledger-teal); + color: var(--ledger-teal); + font-family: var(--mono-font); + font-size: 0.62rem; + padding-top: 13px; + position: relative; + text-transform: uppercase; +} + +.empty-evidence-track li::before { + background: var(--ledger-sheet); + border: 1px solid var(--ledger-teal); + border-radius: 50%; + content: ""; + height: 12px; + left: 0; + position: absolute; + top: -7px; + width: 12px; +} + +.loading-indicator { + border-color: rgb(12 117 101 / 20%); + border-top-color: var(--ledger-teal); +} + +.result-content { + display: grid; + gap: 18px; +} + +.result-content > * { + animation: ledger-reveal 520ms both; +} + +.result-content > :nth-child(2) { + animation-delay: 70ms; +} + +.result-content > :nth-child(3) { + animation-delay: 120ms; +} + +@keyframes ledger-reveal { + from { + opacity: 0; + transform: translateY(16px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.decision-banner { + align-items: start; + background: + linear-gradient(rgb(255 254 250 / 92%), rgb(255 254 250 / 92%)), + url("/visuals/warm-paper-seamless.webp"); + border: 1px solid var(--ledger-line); + border-radius: 0; + clip-path: var(--cut-lg); + display: grid; + gap: 28px; + grid-template-columns: 58px minmax(0, 1fr) auto; + min-height: 320px; + overflow: hidden; + padding: clamp(30px, 4vw, 62px); + position: relative; +} + +.decision-banner::after { + background-image: radial-gradient( + circle, + rgb(199 66 58 / 20%) 0 1px, + transparent 1.3px + ); + background-size: 18px 18px; + bottom: 0; + content: ""; + opacity: 0.5; + position: absolute; + right: 0; + top: 0; + width: 28%; +} + +.decision-banner.stop { + border-color: rgb(199 66 58 / 55%); + box-shadow: inset 8px 0 0 var(--ledger-red); +} + +.decision-banner.manual-review { + box-shadow: inset 8px 0 0 #a97718; +} + +.decision-icon { + align-items: center; + border: 2px solid currentColor; + border-radius: 50%; + display: flex; + font-family: var(--mono-font); + font-size: 1.5rem; + height: 48px; + justify-content: center; + width: 48px; +} + +.decision-banner.stop .decision-icon, +.decision-banner.stop .decision-value, +.decision-banner.stop #decision-heading { + color: var(--ledger-red); +} + +.decision-copy { + max-width: 900px; +} + +.decision-copy > strong { + display: block; + font-family: var(--display-font); + font-size: 7rem; + letter-spacing: 0; + line-height: 0.72; + margin-bottom: 28px; + text-transform: uppercase; +} + +.decision-banner p { + color: var(--ledger-ink); + font-family: var(--mono-font); + font-size: 0.82rem; + line-height: 1.5; +} + +.action-boundary { + border: 1px solid currentColor; + color: var(--ledger-red); + display: inline-flex; + font-family: var(--mono-font); + font-size: 0.64rem; + letter-spacing: 0; + margin-top: 8px; + padding: 7px 9px; + text-transform: uppercase; +} + +.decision-limitations { + border-top: 1px solid var(--ledger-line); + display: block; + margin-top: 22px; + padding-top: 18px; +} + +.decision-limitations summary { + color: var(--ledger-red); + cursor: pointer; + font-family: var(--mono-font); + font-size: 0.64rem; + letter-spacing: 0; + list-style: none; + text-transform: uppercase; +} + +.decision-limitations summary::-webkit-details-marker { + display: none; +} + +.decision-limitations summary::after { + content: "+"; + float: right; +} + +.decision-limitations[open] summary::after { + content: "−"; +} + +.decision-limitations-list { + display: grid; + gap: 8px; + list-style: none; + margin: 16px 0 0; + padding: 0; +} + +.decision-limitations li { + gap: 7px; +} + +.decision-limitations strong, +.decision-limitations span { + font-family: var(--mono-font); + font-size: 0.61rem; + line-height: 1.45; +} + +.decision-value { + font-family: var(--mono-font); + font-size: 0.7rem; + letter-spacing: 0; + position: relative; + text-transform: uppercase; + z-index: 1; +} + +.core-tension { + background: var(--ledger-paper); + border: 1px solid var(--ledger-line); + clip-path: var(--cut-lg); + display: grid; + grid-template-columns: 56% 44%; + min-height: 600px; + overflow: hidden; +} + +.tension-success-panel, +.tension-fail-panel { + min-width: 0; + padding: clamp(34px, 5vw, 74px); + position: relative; +} + +.tension-success-panel { + background: + linear-gradient(rgb(7 70 62 / 88%), rgb(7 70 62 / 88%)), + radial-gradient( + circle at center, + rgb(66 198 167 / 22%) 0 1px, + transparent 1.3px + ); + background-size: + auto, + 18px 18px; + color: var(--ledger-sheet); +} + +.tension-success-panel .section-index { + color: var(--ledger-mint); +} + +.chapter-title { + border-bottom: 1px solid rgb(255 254 250 / 32%); + margin-bottom: 48px; + padding-bottom: 14px; +} + +.tension-success-panel h3 { + font-family: var(--display-font); + font-size: 4rem; + letter-spacing: 0; + line-height: 0.92; + margin: 0 0 38px; + text-transform: uppercase; +} + +.tension-success-panel h3 span { + color: var(--ledger-mint); +} + +.tension-amounts { + display: grid; + margin: 0; +} + +.tension-amounts > div { + align-items: center; + border-bottom: 1px solid rgb(255 254 250 / 18%); + display: grid; + gap: 20px; + grid-template-columns: 92px minmax(0, 1fr); + padding: 13px 0; +} + +.tension-amounts dt { + color: rgb(255 254 250 / 60%); +} + +.tension-amounts dd { + display: grid; + gap: 3px; + justify-items: end; +} + +.tension-amounts strong { + font-family: var(--mono-font); + font-size: 1rem; +} + +.tension-amounts small { + color: rgb(255 254 250 / 42%); + font-family: var(--mono-font); + font-size: 0.52rem; + overflow-wrap: anywhere; +} + +.tension-statement { + border: 1px solid rgb(255 254 250 / 45%); + clip-path: var(--cut-sm); + font-family: var(--display-font); + font-size: 2.4rem; + line-height: 1; + margin: 40px 0 0; + padding: 18px; + text-transform: uppercase; +} + +.tension-provenance { + bottom: 22px; + color: var(--ledger-mint); + font-family: var(--mono-font); + font-size: 0.58rem; + left: clamp(34px, 5vw, 74px); + letter-spacing: 0; + position: absolute; +} + +.tension-fail-panel { + align-content: center; + background: + linear-gradient(rgb(244 242 236 / 88%), rgb(244 242 236 / 88%)), + url("/visuals/warm-paper-seamless.webp"); + color: var(--ledger-red); + display: grid; + justify-items: center; +} + +.tension-fail-heading { + border-bottom: 1px solid currentColor; + font-family: var(--display-font); + font-size: 3.5rem; + letter-spacing: 0; + padding-bottom: 18px; + text-align: center; + text-transform: uppercase; + width: 100%; +} + +.tension-ratio { + color: rgb(199 66 58 / 25%); + font-family: var(--display-font); + font-size: 14rem; + letter-spacing: 0; + line-height: 0.78; + margin-left: -0.08em; +} + +.tension-fail-code { + border: 1px solid var(--ledger-red); + font-family: var(--mono-font); + font-size: 0.65rem; + letter-spacing: 0; + padding: 8px 10px; + text-transform: uppercase; +} + +.section-heading { + align-items: start; + display: grid; + gap: 20px; + grid-template-columns: auto minmax(0, 1fr) auto; +} + +.section-index { + color: var(--ledger-teal); + font-family: var(--display-font); + font-size: 3.5rem; + font-weight: 800; + letter-spacing: 0; + line-height: 0.8; +} + +.section-heading h3 { + font-family: var(--display-font); + font-size: 3rem; + letter-spacing: 0; + line-height: 0.9; + margin: 0 0 10px; + text-transform: uppercase; +} + +.section-heading p { + font-family: var(--mono-font); + font-size: 0.67rem; + line-height: 1.45; + margin: 0; +} + +.comparison-strip, +.alignment-list, +.quote-comparison, +.report-intent, +.evidence-section, +.stop-details { + border: 1px solid var(--ledger-line); + border-radius: 0; + clip-path: var(--cut-lg); + margin: 0; + overflow: hidden; +} + +.comparison-strip { + background: + linear-gradient(rgb(244 242 236 / 90%), rgb(244 242 236 / 90%)), + url("/visuals/warm-paper-seamless.webp"); + padding: clamp(34px, 5vw, 72px); + position: relative; +} + +.comparison-strip::before { + background-image: + linear-gradient(rgb(12 117 101 / 10%) 1px, transparent 1px), + linear-gradient(90deg, rgb(12 117 101 / 10%) 1px, transparent 1px); + background-size: 24px 24px; + content: ""; + inset: 0; + mask-image: linear-gradient(to bottom, #000, transparent 48%); + pointer-events: none; + position: absolute; +} + +.comparison-strip > * { + position: relative; +} + +.comparison-columns { + display: grid; + gap: 34px; + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin-top: 68px; +} + +.comparison-column { + background: rgb(255 254 250 / 83%); + border: 1px solid rgb(24 35 31 / 40%); + clip-path: var(--cut-lg); + display: flex; + flex-direction: column; + min-height: 520px; + padding: 34px 30px 24px; + position: relative; + transition: + transform 180ms ease, + box-shadow 180ms ease; +} + +.comparison-column + .comparison-column { + border-left: 1px solid rgb(24 35 31 / 40%); +} + +.comparison-column:hover { + box-shadow: 0 18px 34px rgb(9 11 16 / 11%); + transform: translateY(-4px); +} + +.comparison-column.prepared { + background: rgb(241 216 210 / 68%); + border-color: rgb(199 66 58 / 55%); +} + +.comparison-column.prepared:hover { + transform: translateY(-4px); +} + +.comparison-column > header { + align-items: center; + border-bottom: 1px solid currentColor; + display: grid; + gap: 14px; + grid-template-columns: auto minmax(0, 1fr); + padding-bottom: 18px; +} + +.comparison-column-index { + background: var(--ledger-moss); + border-radius: 50%; + color: var(--ledger-sheet); + font-family: var(--mono-font); + font-size: 0.8rem; + height: 48px; + width: 48px; +} + +.comparison-column.prepared .comparison-column-index { + background: var(--ledger-red); +} + +.comparison-column.simulation .comparison-column-index { + background: #274b45; +} + +.comparison-column h4 { + font-family: var(--mono-font); + font-size: 0.78rem; + letter-spacing: 0; + text-transform: uppercase; +} + +.comparison-column header p { + font-family: var(--mono-font); + font-size: 0.58rem; +} + +.comparison-primary-value { + align-content: center; + display: grid; + flex: 1; + justify-items: center; + min-height: 260px; + padding: 28px 0 22px; + text-align: center; +} + +.comparison-primary-label, +.comparison-primary-unit { + font-family: var(--mono-font); + font-style: normal; + letter-spacing: 0; + text-transform: uppercase; +} + +.comparison-primary-label { + color: rgb(24 35 31 / 55%); + font-size: 0.58rem; +} + +.comparison-primary-value > strong { + color: var(--ledger-moss); + font-family: var(--display-font); + font-size: 6rem; + letter-spacing: 0; + line-height: 0.9; + margin: 18px 0 12px; + max-width: 100%; + overflow-wrap: anywhere; +} + +.comparison-column.prepared .comparison-primary-value > strong, +.comparison-column.prepared .comparison-primary-unit { + color: var(--ledger-red); +} + +.comparison-primary-unit { + font-size: 1.2rem; + font-weight: 800; +} + +.comparison-primary-raw { + color: rgb(24 35 31 / 64%); + font-family: var(--mono-font); + font-size: 0.58rem; + margin-top: 8px; + max-width: 100%; + overflow-wrap: anywhere; +} + +.comparison-primary-value .comparison-sources { + margin-top: 14px; +} + +.comparison-details { + border-top: 1px solid var(--ledger-line); + font-family: var(--mono-font); + margin-top: auto; +} + +.comparison-details > summary { + cursor: pointer; + font-size: 0.6rem; + letter-spacing: 0; + list-style: none; + padding: 15px 0 4px; + text-transform: uppercase; +} + +.comparison-details > summary::after { + content: "+"; + float: right; +} + +.comparison-details[open] > summary::after { + content: "−"; +} + +.comparison-details > summary::-webkit-details-marker { + display: none; +} + +.comparison-footer { + align-items: center; + display: flex; + font-family: var(--mono-font); + font-size: 0.67rem; + justify-content: space-between; + letter-spacing: 0; + margin-top: 40px; + text-transform: uppercase; +} + +.comparison-footer a, +.comparison-provenance { + border: 1px solid rgb(12 117 101 / 42%); + color: var(--ledger-teal); + padding: 14px 18px; + text-decoration: none; +} + +.comparison-column dl > div { + border-color: var(--ledger-line); + padding: 14px 0; +} + +.comparison-column dd > strong { + font-family: var(--mono-font); + font-size: 0.72rem; +} + +.comparison-sources a, +.alignment-sources a, +.stop-sources a, +.evidence-sources a, +.source-pointer { + color: var(--ledger-teal); + font-family: var(--mono-font); + font-size: 0.57rem; + text-decoration-thickness: 1px; + text-underline-offset: 3px; +} + +.comparison-sources a:hover, +.alignment-sources a:hover, +.stop-sources a:hover, +.evidence-sources a:hover, +.source-pointer:hover { + background: rgb(66 198 167 / 16%); + color: var(--ledger-moss); +} + +.alignment-list { + background: var(--ledger-sheet); + padding: clamp(28px, 4vw, 54px); +} + +.alignment-count, +.stop-reason-count, +.quote-count { + background: transparent; + border: 1px solid currentColor; + border-radius: 50%; + font-family: var(--mono-font); +} + +.alignment-list > ol, +.stop-details > ol { + border-top: 1px solid var(--ledger-line); + margin-top: 32px; +} + +.alignment-check { + border-color: var(--ledger-line); + padding: 22px 0; +} + +.alignment-check-heading > strong, +.alignment-status, +.critical-label { + font-family: var(--mono-font); +} + +.alignment-icon { + border-radius: 0; + clip-path: var(--cut-sm); +} + +.alignment-check.fail { + background: rgb(199 66 58 / 6%); + box-shadow: inset 4px 0 0 var(--ledger-red); + padding-inline: 18px; +} + +.result-facts { + background: var(--ledger-moss); + border: 1px solid rgb(9 11 16 / 28%); + clip-path: var(--cut-sm); + color: var(--ledger-sheet); + grid-template-columns: repeat(4, minmax(0, 1fr)); + padding: 16px 22px; +} + +.result-facts > div { + border-bottom: 0; + border-right: 1px solid var(--ledger-line-dark); + display: block; + min-width: 0; + padding: 8px 18px; +} + +.result-facts > div:last-child { + border-right: 0; +} + +.result-facts dt { + color: rgb(255 254 250 / 45%); + margin-bottom: 7px; +} + +.result-facts dd { + font-family: var(--mono-font); + font-size: 0.66rem; +} + +.credential-heading { + grid-template-columns: minmax(0, 1fr) auto; +} + +.credential-kind { + justify-self: end; + max-width: 100%; + min-width: 0; + overflow-wrap: anywhere; +} + +.report-intent, +.quote-comparison { + background: + linear-gradient(rgb(255 254 250 / 90%), rgb(255 254 250 / 90%)), + url("/visuals/warm-paper-seamless.webp"); + padding: clamp(28px, 4vw, 54px); +} + +.report-intent-facts, +.quote-facts { + gap: 0; + margin-top: 26px; +} + +.report-intent-facts > div, +.quote-facts > div { + border-color: var(--ledger-line); + padding: 13px 0; +} + +.report-intent-facts dd, +.quote-facts dd { + font-family: var(--mono-font); + font-size: 0.68rem; +} + +.quote-list { + margin-top: 30px; +} + +.quote-row { + background: rgb(255 254 250 / 72%); + border-color: var(--ledger-line); + border-radius: 0; + clip-path: var(--cut-sm); +} + +.quote-title, +.quote-status, +.selection-code, +.selection-rule { + font-family: var(--mono-font); +} + +.selection-rule { + background: rgb(12 117 101 / 7%); + border-color: var(--ledger-line); + clip-path: var(--cut-sm); +} + +.capability-inspector { + background: linear-gradient( + 90deg, + var(--ledger-paper) 0 42%, + var(--ledger-dark) 42% 100% + ); + color: var(--ledger-sheet); + display: grid; + gap: 28px; + grid-template-areas: + "heading artifact" + "metadata artifact" + "metadata raw"; + grid-template-columns: 42% 58%; + grid-template-rows: auto minmax(260px, 1fr) auto; + min-height: 660px; + padding: 0; +} + +.capability-inspector > .section-heading { + color: var(--ledger-ink); + grid-area: heading; + padding: 54px 40px 0; +} + +.capability-inspector > .capability-tree, +.capability-inspector > .evidence-empty { + grid-area: artifact; + margin: 54px 42px 0; +} + +.capability-inspector > .evidence-metadata-grid { + align-self: stretch; + color: var(--ledger-ink); + grid-area: metadata; + margin: 0; + padding: 0 40px 50px; +} + +.capability-inspector > .raw-drawer-control { + align-self: end; + grid-area: raw; + justify-self: stretch; + margin: 0 42px 42px; +} + +.capability-inspector .availability-badge { + color: var(--ledger-teal); +} + +.capability-tree, +.capability-children { + border-color: var(--ledger-line-dark); +} + +.capability-node-row { + background: rgb(255 254 250 / 8%); + border: 1px solid var(--ledger-line-dark); + border-radius: 0; + clip-path: var(--cut-sm); + color: var(--ledger-sheet); + padding: 18px; +} + +.capability-node-row:hover { + background: rgb(66 198 167 / 10%); +} + +.node-role, +.node-facts dt, +.node-facts dd, +.node-identity strong { + font-family: var(--mono-font); +} + +.node-facts dt { + color: rgb(255 254 250 / 42%); +} + +.node-facts dd { + color: var(--ledger-sheet); +} + +.evidence-metadata-grid { + grid-template-columns: 1fr; +} + +.risk-labels, +.evidence-limitations { + border-color: var(--ledger-line); + border-radius: 0; +} + +.risk-labels h4, +.evidence-limitations h4, +.risk-labels p, +.evidence-limitations p, +.evidence-limitations li > strong { + font-family: var(--mono-font); +} + +.capability-inspector .evidence-empty { + background: rgb(255 254 250 / 8%); + border-color: var(--ledger-line-dark); + color: var(--ledger-sheet); +} + +.capability-inspector .capability-raw-summary { + align-content: start; + background: transparent; + border: 0; + gap: 16px; + padding: 0; +} + +.capability-summary-heading, +.capability-highlight-list > div, +.capability-preserved-artifact { + clip-path: var(--cut-sm); +} + +.capability-summary-heading { + align-items: center; + background: var(--ledger-paper); + color: var(--ledger-ink); + display: flex; + font-family: var(--mono-font); + justify-content: space-between; + padding: 18px 20px; + text-transform: uppercase; +} + +.capability-inspector .capability-summary-heading span { + font-size: 0.76rem; + letter-spacing: 0; +} + +.capability-summary-heading strong { + color: var(--ledger-teal); + font-size: 0.7rem; +} + +.capability-highlight-list { + display: grid; + gap: 12px; +} + +.capability-highlight-list > div { + align-items: center; + background: rgb(255 254 250 / 8%); + border: 1px solid var(--ledger-line-dark); + display: grid; + gap: 14px; + grid-template-columns: minmax(90px, 0.36fr) minmax(0, 1fr); + padding: 16px 18px; +} + +.capability-highlight-list > .amount-highlight { + border-color: rgb(199 66 58 / 65%); + box-shadow: inset 4px 0 0 var(--ledger-red); +} + +.capability-highlight-list dt { + color: rgb(255 254 250 / 45%); +} + +.capability-highlight-list dd { + display: grid; + gap: 7px; + min-width: 0; +} + +.capability-highlight-list dd > strong { + font-family: var(--mono-font); + font-size: 0.8rem; + overflow-wrap: anywhere; +} + +.capability-preserved-artifact { + border: 1px dashed rgb(255 254 250 / 34%); + display: grid; + gap: 8px; + padding: 18px; +} + +.capability-inspector .capability-preserved-artifact > span, +.capability-inspector .capability-preserved-artifact > strong { + font-family: var(--mono-font); +} + +.capability-inspector .capability-preserved-artifact > span { + color: var(--ledger-mint); + font-size: 0.66rem; + letter-spacing: 0; + text-transform: uppercase; +} + +.capability-inspector .capability-preserved-artifact > strong { + font-size: 0.78rem; +} + +.capability-inspector .command-button.secondary { + border-color: var(--ledger-mint); + color: var(--ledger-mint); + width: 100%; +} + +.evidence-timeline { + background: + linear-gradient(rgb(9 11 16 / 94%), rgb(9 11 16 / 94%)), + radial-gradient(circle, rgb(255 254 250 / 18%) 0 1px, transparent 1.3px); + background-size: + auto, + 22px 22px; + border-color: rgb(255 254 250 / 20%); + color: var(--ledger-sheet); + padding: clamp(36px, 5vw, 72px); +} + +.evidence-timeline .section-heading p, +.evidence-timeline dt { + color: rgb(255 254 250 / 48%); +} + +.evidence-timeline .section-index, +.evidence-timeline .section-heading h3 { + color: var(--ledger-paper); +} + +.evidence-status-badges { + align-items: center; +} + +.simulation-stage-rail { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + list-style: none; + margin: 52px 0 56px; + padding: 0 0 30px; + position: relative; +} + +.simulation-stage-rail::before { + background: rgb(225 209 179 / 72%); + bottom: 9px; + content: ""; + height: 2px; + left: 2.5%; + position: absolute; + right: 2.5%; +} + +.simulation-stage-rail li { + display: grid; + gap: 5px; + min-width: 0; + position: relative; +} + +.simulation-stage-rail li::after { + background: var(--ledger-dark); + border: 4px solid rgb(225 209 179 / 70%); + border-radius: 50%; + bottom: -30px; + content: ""; + height: 20px; + left: 8px; + position: absolute; + width: 20px; + z-index: 1; +} + +.simulation-stage-rail li.boundary::after { + background: var(--ledger-red); + border-color: var(--ledger-paper); + box-shadow: 0 0 0 5px rgb(225 209 179 / 18%); +} + +.simulation-stage-rail li > span { + color: rgb(225 209 179 / 42%); + font-family: var(--display-font); + font-size: 3.75rem; + font-weight: 800; + letter-spacing: 0; + line-height: 0.85; +} + +.simulation-stage-rail li.boundary > span { + color: var(--ledger-paper); +} + +.simulation-stage-rail li > strong, +.simulation-stage-rail li > small { + font-family: var(--mono-font); + overflow: hidden; + text-overflow: ellipsis; + text-transform: uppercase; + white-space: nowrap; +} + +.simulation-stage-rail li > strong { + color: var(--ledger-paper); + font-size: 0.62rem; +} + +.simulation-stage-rail li > small { + color: rgb(255 254 250 / 42%); + font-size: 0.5rem; +} + +.simulation-summary-board { + align-items: stretch; + display: grid; + gap: clamp(28px, 5vw, 74px); + grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.3fr); +} + +.simulation-receipt-sheet, +.simulation-outcome-sheet, +.simulation-boundary-note { + background: + linear-gradient(rgb(225 209 179 / 90%), rgb(225 209 179 / 90%)), + url("/visuals/warm-paper-seamless.webp"); + box-shadow: 0 22px 34px rgb(0 0 0 / 24%); + clip-path: var(--cut-lg); + color: var(--ledger-ink); +} + +.simulation-receipt-sheet { + min-height: 360px; + padding: 34px 38px; +} + +.simulation-receipt-sheet > header { + align-items: center; + border-bottom: 1px solid var(--ledger-line); + display: flex; + font-family: var(--mono-font); + justify-content: space-between; + padding-bottom: 18px; + text-transform: uppercase; +} + +.simulation-receipt-sheet > header span { + font-size: 0.74rem; +} + +.simulation-receipt-sheet > header strong { + font-size: 0.64rem; +} + +.simulation-sheet-footer { + align-items: center; + display: flex; + font-family: var(--mono-font); + font-size: 0.64rem; + justify-content: space-between; + margin-top: 20px; + text-transform: uppercase; +} + +.simulation-outcome-stack { + display: grid; + gap: 28px; + grid-template-rows: minmax(0, 1fr) auto; +} + +.simulation-outcome-sheet { + align-content: center; + display: grid; + min-height: 220px; + padding: clamp(32px, 5vw, 58px); +} + +.simulation-outcome-label, +.simulation-outcome-copy { + font-family: var(--mono-font); +} + +.simulation-outcome-label { + font-size: 0.72rem; + text-transform: uppercase; +} + +.simulation-outcome-sheet > strong { + border: 4px solid var(--ledger-teal); + color: var(--ledger-teal); + font-family: var(--display-font); + font-size: 3.4rem; + line-height: 0.92; + margin: 24px 0 16px; + padding: 18px 22px; + text-align: center; + text-transform: uppercase; +} + +.simulation-outcome-copy { + font-size: 0.64rem; + margin: 0; +} + +.simulation-boundary-note { + align-items: end; + border: 2px solid var(--ledger-red); + display: grid; + gap: 12px; + grid-template-columns: minmax(0, 1fr) auto; + margin: 0; + padding: 24px 30px; + transform: rotate(-0.5deg); +} + +.simulation-boundary-note strong { + color: var(--ledger-red); + font-family: var(--display-font); + font-size: 2.8rem; + line-height: 0.9; + text-transform: uppercase; +} + +.simulation-boundary-provenance { + font-family: var(--mono-font); + font-size: 0.68rem; + font-weight: 800; +} + +.timeline-inspector { + border-bottom: 1px solid var(--ledger-line-dark); + border-top: 1px solid var(--ledger-line-dark); + margin-top: 46px; +} + +.timeline-inspector > summary { + align-items: center; + cursor: pointer; + display: flex; + font-family: var(--mono-font); + font-size: 0.67rem; + justify-content: space-between; + letter-spacing: 0; + list-style: none; + padding: 16px 0; + text-transform: uppercase; +} + +.timeline-inspector > summary span { + border: 1px solid var(--ledger-line-dark); + color: var(--ledger-mint); + padding: 5px 10px; +} + +.timeline-inspector > summary::-webkit-details-marker { + display: none; +} + +.availability-badge, +.execution-badge, +.record-status { + border-radius: 0; + font-family: var(--mono-font); + letter-spacing: 0; +} + +.verification-statuses { + border-bottom: 1px solid var(--ledger-line-dark); + border-top: 1px solid var(--ledger-line-dark); + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + margin: 42px 0 0; +} + +.verification-statuses > div { + border-right: 1px solid var(--ledger-line-dark); + padding: 19px; +} + +.verification-statuses > div:last-child { + border-right: 0; +} + +.verification-statuses dd, +.verification-statuses strong { + color: var(--ledger-sheet); + font-family: var(--mono-font); +} + +.simulation-verification-statuses { + border: 0; + display: grid; + grid-template-columns: 1fr; + margin: 20px 0 0; +} + +.simulation-verification-statuses > div { + align-items: center; + border-bottom: 1px dashed var(--ledger-line); + border-right: 0; + display: grid; + gap: 14px; + grid-template-columns: minmax(0, 1fr) auto; + padding: 13px 0; +} + +.simulation-verification-statuses dd { + align-items: end; + color: var(--ledger-ink); + display: grid; + justify-items: end; +} + +.simulation-verification-statuses strong { + color: var(--ledger-teal); +} + +.timeline-list { + border-top: 0; + margin-top: 48px; + position: relative; +} + +.timeline-list::before { + background: var(--ledger-paper); + content: ""; + height: 2px; + left: 35px; + opacity: 0.55; + position: absolute; + right: 0; + top: 30px; +} + +.timeline-inspector-list { + margin-bottom: 42px; + margin-top: 34px; +} + +.timeline-entry { + grid-template-columns: 72px minmax(0, 1fr); + position: relative; +} + +.timeline-entry:not(:last-child)::before { + background: var(--ledger-line-dark); +} + +.timeline-index { + background: var(--ledger-dark); + border: 4px solid var(--ledger-paper); + box-shadow: 0 0 0 1px var(--ledger-dark); + color: var(--ledger-paper); + font-family: var(--display-font); + font-size: 2rem; + height: 64px; + width: 64px; + z-index: 1; +} + +.timeline-content { + background: var(--ledger-paper); + border: 1px solid rgb(255 254 250 / 28%); + clip-path: var(--cut-lg); + color: var(--ledger-ink); + padding: 28px; +} + +.timeline-title, +.timeline-type, +.timeline-title strong, +.timeline-transaction-facts dd, +.timeline-artifacts, +.timeline-warnings { + font-family: var(--mono-font); +} + +.timeline-record-grid { + gap: 14px; +} + +.timeline-artifacts, +.timeline-warnings { + border-color: var(--ledger-line); + border-radius: 0; +} + +.evidence-timeline > .raw-drawer-control { + margin-top: 34px; +} + +.evidence-timeline .command-button.secondary { + border-color: var(--ledger-mint); + color: var(--ledger-mint); +} + +.stop-details { + background: + linear-gradient(rgb(244 242 236 / 90%), rgb(244 242 236 / 90%)), + url("/visuals/warm-paper-seamless.webp"); + border-color: rgb(199 66 58 / 48%); + min-height: 580px; + padding: clamp(42px, 6vw, 88px); + position: relative; +} + +.stop-details::before { + border: 3px solid var(--ledger-red); + clip-path: polygon( + 22% 0, + 78% 0, + 100% 22%, + 100% 78%, + 78% 100%, + 22% 100%, + 0 78%, + 0 22% + ); + color: var(--ledger-red); + content: "STOP"; + font-family: var(--display-font); + font-size: 8rem; + font-weight: 800; + left: 50%; + letter-spacing: 0; + line-height: 1; + opacity: 0.1; + padding: 20px 54px; + position: absolute; + top: 34px; + transform: translateX(-50%); +} + +.stop-details .section-heading { + margin-top: 190px; + position: relative; +} + +.stop-details .section-index, +.stop-details .section-heading h3, +.stop-reason-heading strong, +.stop-reason-icon { + color: var(--ledger-red); +} + +.stop-boundary-strip { + align-items: center; + border-block: 1px solid var(--ledger-line); + display: grid; + gap: 12px 18px; + grid-template-columns: auto minmax(0, 1fr) auto auto; + margin-top: 30px; + padding: 18px 0; +} + +.stop-details .stop-boundary-strip span, +.stop-details .stop-boundary-strip strong { + font-family: var(--mono-font); + font-size: 0.68rem; + letter-spacing: 0; + text-transform: uppercase; +} + +.stop-details .stop-boundary-strip span { + color: rgb(24 35 31 / 58%); +} + +.stop-details .stop-boundary-strip strong:first-of-type { + color: var(--ledger-red); +} + +.stop-details .stop-boundary-strip strong:last-child { + color: var(--ledger-teal); +} + +.stop-details li { + border-color: var(--ledger-line); + display: grid; + gap: 18px; + grid-template-columns: minmax(0, 1fr) auto; + padding: 25px 0; +} + +.stop-reason-heading, +.stop-details li > p { + grid-column: 1 / -1; +} + +.stop-reason-heading strong, +.stop-details li > p { + font-family: var(--mono-font); +} + +.stop-reason-icon { + border-radius: 0; +} + +.stop-sources { + align-items: stretch; + display: grid; + gap: 10px; + grid-column: 1 / -1; + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.stop-sources a { + border: 1px solid var(--ledger-teal); + clip-path: var(--cut-sm); + padding: 14px; +} + +.raw-drawer-control { + display: flex; + justify-content: flex-end; +} + +.raw-evidence-drawer { + background: + linear-gradient(rgb(9 11 16 / 96%), rgb(9 11 16 / 96%)), + radial-gradient(circle, rgb(66 198 167 / 18%) 0 1px, transparent 1.3px); + background-size: + auto, + 20px 20px; + border: 1px solid var(--ledger-mint); + border-radius: 0; + clip-path: var(--cut-lg); + color: var(--ledger-sheet); + width: min(900px, calc(100vw - 32px)); +} + +.raw-evidence-drawer::backdrop { + background: rgb(9 11 16 / 76%); +} + +.raw-drawer-eyebrow, +.raw-drawer-header h3, +.raw-json, +.icon-button { + font-family: var(--mono-font); +} + +.raw-drawer-eyebrow { + color: var(--ledger-mint); +} + +.raw-json { + background: rgb(255 254 250 / 6%); + border-color: var(--ledger-line-dark); + color: var(--ledger-sheet); +} + +.icon-button { + border-color: var(--ledger-line-dark); + color: var(--ledger-sheet); +} + +@media (max-width: 1400px) { + .intro-copy h1 { + font-size: 6rem; + } +} + +@media (max-width: 1180px) { + .intro-evidence-spine { + display: none; + } + + .workbench-grid { + grid-template-columns: minmax(300px, 340px) minmax(0, 1fr); + } + + .decision-banner { + grid-template-columns: 48px minmax(0, 1fr); + } + + .decision-value { + grid-column: 2; + } + + .core-tension { + grid-template-columns: minmax(0, 1fr); + } + + .tension-fail-panel { + min-height: 440px; + } + + .comparison-columns { + grid-template-columns: 1fr; + } + + .comparison-column, + .comparison-column.prepared { + min-height: 0; + transform: none; + } + + .comparison-column.prepared:hover, + .comparison-column:hover { + transform: translateY(-4px); + } + + .capability-inspector { + background: var(--ledger-dark); + grid-template-areas: + "heading" + "artifact" + "metadata" + "raw"; + grid-template-columns: 1fr; + grid-template-rows: auto; + } + + .capability-inspector > .section-heading, + .capability-inspector > .evidence-metadata-grid { + background: var(--ledger-paper); + } + + .capability-inspector > .section-heading { + padding-bottom: 34px; + } + + .capability-inspector > .capability-tree, + .capability-inspector > .evidence-empty { + margin-bottom: 30px; + } +} + +@media (max-width: 900px) { + .app-bar { + position: absolute; + } + + .forensic-intro { + min-height: 540px; + } + + .workbench-grid { + grid-template-columns: 1fr; + } + + .control-pane { + max-height: none; + position: relative; + top: auto; + } + + .result-facts { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .result-facts > div:nth-child(2) { + border-right: 0; + } + + .result-facts > div:nth-child(-n + 2) { + border-bottom: 1px solid var(--ledger-line-dark); + } + + .verification-statuses { + grid-template-columns: 1fr; + } + + .verification-statuses > div { + border-bottom: 1px solid var(--ledger-line-dark); + border-right: 0; + } + + .verification-statuses > div:last-child { + border-bottom: 0; + } + + .simulation-summary-board { + grid-template-columns: 1fr; + } +} + +@media (max-width: 620px) { + html { + scroll-padding-top: 20px; + } + + .app-bar { + align-items: flex-start; + flex-direction: column; + gap: 10px; + min-height: 134px; + padding: 10px 16px 12px; + } + + .brand-logo { + height: 54px; + width: 55px; + } + + .environment-list { + justify-content: flex-start; + width: 100%; + } + + .environment-item { + font-size: 0.55rem; + } + + .forensic-intro { + background-image: url("/visuals/forensic-desk-mobile.webp"); + background-position: center; + min-height: 560px; + padding: 170px 20px 38px; + } + + .forensic-intro::before { + background: + linear-gradient(180deg, rgb(9 11 16 / 35%) 0%, transparent 32%), + linear-gradient( + 0deg, + rgb(9 11 16 / 94%) 0%, + rgb(9 11 16 / 20%) 62%, + transparent 78% + ); + } + + .intro-technical-frame { + inset: 152px 10px 10px; + } + + .intro-provenance { + left: 20px; + top: 34%; + transform: none; + } + + .intro-copy { + max-width: 100%; + } + + .intro-copy h1 { + font-size: 4.6rem; + line-height: 0.82; + white-space: normal; + } + + .intro-copy p { + font-size: 0.74rem; + margin: 20px 0 26px; + max-width: 34ch; + } + + .intro-cta { + min-width: 220px; + } + + .workbench-main { + padding: 68px 12px 80px; + } + + .workbench-heading { + align-items: start; + gap: 20px; + margin-bottom: 22px; + } + + .workbench-heading h2 { + font-size: 3.1rem; + } + + .workbench-heading p { + max-width: 30ch; + } + + .state-badge { + min-width: auto; + } + + .control-pane { + clip-path: var(--cut-sm); + padding: 20px 18px; + } + + .field-row { + grid-template-columns: 1fr; + } + + .result-header { + align-items: flex-start; + gap: 12px; + padding: 16px; + } + + .provenance-badge { + max-width: 48%; + text-align: right; + white-space: normal; + } + + .empty-state, + .loading-state, + .error-state { + min-height: 520px; + padding: 28px 22px; + } + + .empty-evidence-track { + gap: 0; + grid-template-columns: 1fr; + margin-top: 48px; + } + + .empty-evidence-track li { + border-left: 1px solid var(--ledger-teal); + border-top: 0; + padding: 8px 0 8px 18px; + } + + .empty-evidence-track li::before { + left: -7px; + top: 10px; + } + + .decision-banner { + gap: 16px; + grid-template-columns: 42px minmax(0, 1fr); + min-height: 0; + padding: 26px 20px 30px; + } + + .decision-icon { + font-size: 1rem; + height: 36px; + width: 36px; + } + + .decision-copy > strong { + font-size: 4.6rem; + margin-bottom: 20px; + } + + .decision-limitations { + margin-left: -58px; + } + + .decision-value { + grid-column: 2; + } + + .tension-success-panel, + .tension-fail-panel, + .comparison-strip, + .alignment-list, + .quote-comparison, + .report-intent, + .evidence-timeline, + .stop-details { + padding: 30px 20px; + } + + .tension-success-panel { + min-height: 570px; + } + + .tension-success-panel h3 { + font-size: 3.1rem; + overflow-wrap: anywhere; + } + + .tension-amounts > div { + grid-template-columns: 72px minmax(0, 1fr); + } + + .tension-statement { + font-size: 1.55rem; + } + + .tension-provenance { + left: 20px; + } + + .tension-fail-panel { + min-height: 360px; + } + + .section-heading { + gap: 12px; + grid-template-columns: auto minmax(0, 1fr); + } + + .section-heading > :last-child:not(:nth-child(2)) { + grid-column: 2; + } + + .section-index { + font-size: 2.5rem; + } + + .comparison-columns { + margin-top: 34px; + } + + .comparison-column { + padding: 22px 18px; + } + + .comparison-primary-value { + min-height: 230px; + } + + .comparison-primary-value > strong { + font-size: 4.8rem; + } + + .comparison-footer { + align-items: stretch; + flex-direction: column; + gap: 10px; + } + + .comparison-footer a, + .comparison-provenance { + text-align: center; + } + + .result-facts { + grid-template-columns: 1fr; + } + + .result-facts > div { + border-bottom: 1px solid var(--ledger-line-dark); + border-right: 0; + padding: 12px 0; + } + + .result-facts > div:last-child { + border-bottom: 0; + } + + .report-intent-facts, + .quote-facts { + grid-template-columns: 1fr; + } + + .capability-inspector > .section-heading { + padding: 34px 20px 26px; + } + + .capability-inspector > .capability-tree, + .capability-inspector > .evidence-empty { + margin: 28px 20px; + } + + .capability-inspector > .evidence-metadata-grid { + padding: 0 20px 34px; + } + + .capability-inspector > .raw-drawer-control { + margin: 0 20px 28px; + } + + .simulation-stage-rail { + gap: 8px; + grid-template-columns: 1fr; + margin: 38px 0 42px; + padding: 0; + } + + .simulation-stage-rail::before { + bottom: 20px; + height: auto; + left: 20px; + right: auto; + top: 20px; + width: 2px; + } + + .simulation-stage-rail li { + gap: 2px; + min-height: 58px; + padding-left: 56px; + } + + .simulation-stage-rail li::after { + bottom: auto; + left: 11px; + top: 10px; + } + + .simulation-stage-rail li > span { + font-size: 1.9rem; + } + + .simulation-stage-rail li > strong, + .simulation-stage-rail li > small { + white-space: normal; + } + + .simulation-receipt-sheet { + min-height: 0; + padding: 24px 20px; + } + + .simulation-receipt-sheet > header { + align-items: flex-start; + flex-direction: column; + gap: 8px; + } + + .simulation-verification-statuses > div { + gap: 8px; + grid-template-columns: 1fr; + } + + .simulation-verification-statuses dd { + justify-items: start; + } + + .simulation-outcome-sheet { + min-height: 0; + padding: 28px 20px; + } + + .simulation-outcome-sheet > strong { + border-width: 2px; + font-size: 2rem; + padding: 15px 12px; + } + + .simulation-boundary-note { + align-items: start; + grid-template-columns: 1fr; + padding: 22px 18px; + } + + .simulation-boundary-note strong { + font-size: 2rem; + } + + .timeline-entry { + grid-template-columns: 46px minmax(0, 1fr); + } + + .timeline-index { + border-width: 3px; + font-size: 1.3rem; + height: 42px; + width: 42px; + } + + .timeline-list::before { + left: 21px; + top: 20px; + } + + .timeline-content { + padding: 20px 16px; + } + + .timeline-title, + .timeline-record-grid { + grid-template-columns: 1fr; + } + + .stop-details::before { + font-size: 5rem; + padding: 12px 24px; + } + + .stop-details .section-heading { + margin-top: 130px; + } + + .stop-sources { + grid-template-columns: 1fr; + } + + .stop-boundary-strip { + grid-template-columns: 1fr; + } +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } + + .result-content > *, + .intro-cta, + .intro-cta svg, + .comparison-column { + animation: none; + transition: none; + } +} diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index e5692dc..9e75e69 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -1,10 +1,11 @@ import type { Metadata } from "next"; import "./globals.css"; +import "./forensic-ledger.css"; export const metadata: Metadata = { title: "AnteSig | Preflight Workbench", description: - "Inspect structured Monad preflight evidence before wallet review.", + "Inspect intent, prepared capability, simulation evidence, and bounded decisions before signer review.", applicationName: "AnteSig", icons: { icon: "/brand/antesig-logo.png", diff --git a/apps/web/e2e/responsive-accessibility.e2e.ts b/apps/web/e2e/responsive-accessibility.e2e.ts index d7f13e4..4ae37ab 100644 --- a/apps/web/e2e/responsive-accessibility.e2e.ts +++ b/apps/web/e2e/responsive-accessibility.e2e.ts @@ -5,6 +5,8 @@ import { expect, type Locator, type Page, test } from "@playwright/test"; const SCREENSHOTS = resolve("artifacts/screenshots"); const DESKTOP = { width: 1440, height: 900 }; const MOBILE = { width: 390, height: 844 }; +const COMPACT_DESKTOP = { width: 1280, height: 720 }; +const COMPACT_MOBILE = { width: 360, height: 800 }; const WRITE_SCREENSHOTS = process.env.E2E_WRITE_SCREENSHOTS !== "0"; async function runFixture(page: Page, name: "Happy path" | "Amount mismatch") { @@ -120,6 +122,13 @@ async function assertColumnOrder(page: Page, mobile: boolean) { } } +async function assertWorkbenchHintInFirstViewport(page: Page) { + const top = await page + .locator("#preflight-workbench") + .evaluate((element) => element.getBoundingClientRect().top); + expect(top).toBeLessThan(await page.evaluate(() => window.innerHeight)); +} + async function contrastRatio(locator: Locator): Promise { return locator.evaluate((element) => { const rgb = (value: string) => { @@ -192,6 +201,14 @@ async function assertKeyboardDrawer(page: Page) { async function captureScreenshot(page: Page, filename: string) { if (!WRITE_SCREENSHOTS) return; + await page.evaluate(() => window.scrollTo({ left: 0, top: 0 })); + await page.evaluate(async () => { + await Promise.all( + document + .getAnimations() + .map((animation) => animation.finished.catch(() => undefined)), + ); + }); const runIds = page.locator('[data-run-id="true"]'); for (let index = 0; index < (await runIds.count()); index += 1) { await runIds.nth(index).evaluate((element) => { @@ -221,7 +238,9 @@ test("responsive desktop happy", async ({ page }) => { ).toBe(true); await assertColumnOrder(page, false); await assertNoOverflowOrOverlap(page); + await assertWorkbenchHintInFirstViewport(page); await captureScreenshot(page, "mini-desktop-happy.png"); + await captureScreenshot(page, "visual-1440x900-happy.png"); }); test("responsive desktop stop", async ({ page }) => { @@ -244,7 +263,9 @@ test("responsive mobile happy", async ({ page }) => { await runFixture(page, "Happy path"); await assertColumnOrder(page, true); await assertNoOverflowOrOverlap(page); + await assertWorkbenchHintInFirstViewport(page); await captureScreenshot(page, "mini-mobile-happy.png"); + await captureScreenshot(page, "visual-390x844-happy.png"); }); test("responsive mobile stop", async ({ page }) => { @@ -261,6 +282,24 @@ test("responsive mobile stop", async ({ page }) => { await captureScreenshot(page, "mini-mobile-stop.png"); }); +test("fixed compact desktop stop", async ({ page }) => { + await page.setViewportSize(COMPACT_DESKTOP); + await runFixture(page, "Amount mismatch"); + await assertColumnOrder(page, false); + await assertNoOverflowOrOverlap(page); + await assertWorkbenchHintInFirstViewport(page); + await captureScreenshot(page, "visual-1280x720-stop.png"); +}); + +test("fixed compact mobile stop", async ({ page }) => { + await page.setViewportSize(COMPACT_MOBILE); + await runFixture(page, "Amount mismatch"); + await assertColumnOrder(page, true); + await assertNoOverflowOrOverlap(page); + await assertWorkbenchHintInFirstViewport(page); + await captureScreenshot(page, "visual-360x800-stop.png"); +}); + test("accessibility controls, keyboard, focus, contrast and reduced motion", async ({ page, }) => { diff --git a/apps/web/public/visuals/forensic-desk-desktop.webp b/apps/web/public/visuals/forensic-desk-desktop.webp new file mode 100644 index 0000000..304853d Binary files /dev/null and b/apps/web/public/visuals/forensic-desk-desktop.webp differ diff --git a/apps/web/public/visuals/forensic-desk-mobile.webp b/apps/web/public/visuals/forensic-desk-mobile.webp new file mode 100644 index 0000000..b899537 Binary files /dev/null and b/apps/web/public/visuals/forensic-desk-mobile.webp differ diff --git a/apps/web/public/visuals/warm-paper-seamless.webp b/apps/web/public/visuals/warm-paper-seamless.webp new file mode 100644 index 0000000..9dc835c Binary files /dev/null and b/apps/web/public/visuals/warm-paper-seamless.webp differ diff --git a/apps/web/src/client/core-tension.ts b/apps/web/src/client/core-tension.ts new file mode 100644 index 0000000..0ea6155 --- /dev/null +++ b/apps/web/src/client/core-tension.ts @@ -0,0 +1,63 @@ +import type { Provenance } from "@moss-mini-demo/report-schema"; +import type { ComparisonStripInput } from "./comparison-strip"; +import { comparisonColumns } from "./comparison-strip"; + +export type CoreTensionModel = Readonly<{ + observed: string; + prepared: string; + requested: string; +}>; + +function amountValue( + input: ComparisonStripInput, + columnKey: "intent" | "prepared" | "simulation", + label: "Amount in" | "Amount in observed", +): string { + const column = comparisonColumns(input).find( + (candidate) => candidate.key === columnKey, + ); + return ( + column?.items.find((item) => item.label === label)?.value ?? "not present" + ); +} + +export function formatNativeAmount(value: string): string { + if (!/^\d+$/.test(value)) return value; + const padded = value.padStart(19, "0"); + const whole = padded.slice(0, -18); + const fraction = padded.slice(-18).replace(/0+$/, ""); + return `${BigInt(whole)}.${fraction || "0"} NATIVE`; +} + +export function ratioLabel(requested: string, observed: string): string { + if (!/^\d+$/.test(requested) || !/^\d+$/.test(observed)) return "not exact"; + const requestedAmount = BigInt(requested); + const observedAmount = BigInt(observed); + if (requestedAmount === 0n || observedAmount % requestedAmount !== 0n) { + return "not exact"; + } + return `${observedAmount / requestedAmount}x`; +} + +export function tensionProvenanceLabel(provenance: Provenance): string { + return `${provenance} / SOURCE-BOUND`; +} + +export function coreTensionModel( + input: ComparisonStripInput, +): CoreTensionModel | undefined { + const requested = amountValue(input, "intent", "Amount in"); + const prepared = amountValue(input, "prepared", "Amount in"); + const observed = amountValue(input, "simulation", "Amount in observed"); + const valuesAreExact = [requested, prepared, observed].every((value) => + /^\d+$/.test(value), + ); + const simulationSucceeded = + input.simulation.availability === "AVAILABLE" && + input.simulation.executionStatus === "SUCCESS"; + const hasMismatch = requested !== prepared || requested !== observed; + + return valuesAreExact && simulationSucceeded && hasMismatch + ? { observed, prepared, requested } + : undefined; +} diff --git a/apps/web/src/client/evidence-model.ts b/apps/web/src/client/evidence-model.ts index 88c2265..971b182 100644 --- a/apps/web/src/client/evidence-model.ts +++ b/apps/web/src/client/evidence-model.ts @@ -59,6 +59,12 @@ export type CapabilityRiskLabel = Readonly<{ sourceReference: string; }>; +export type CapabilityRawHighlight = Readonly<{ + label: string; + value: string; + sourceReference: string; +}>; + export type CapabilityNodeModel = Readonly<{ kind: "CAPABILITY" | "TRANSACTION" | "UNKNOWN"; role: @@ -81,6 +87,7 @@ export type CapabilityInspectorModel = Readonly<{ availability: Capability["availability"]; sourceReference: string; root?: CapabilityNodeModel; + rawHighlights: readonly CapabilityRawHighlight[]; riskLabels: readonly CapabilityRiskLabel[]; limitations: readonly Limitation[]; failureCode?: string; @@ -171,6 +178,34 @@ function riskLabels(raw: unknown): CapabilityRiskLabel[] { ); } +function jsonPointerSegment(value: string): string { + return value.replaceAll("~", "~0").replaceAll("/", "~1"); +} + +function rawHighlights(raw: unknown): CapabilityRawHighlight[] { + const parsed = record(raw); + if (parsed === undefined) return []; + + return Object.entries(parsed) + .flatMap(([label, value]) => + typeof value === "string" || + typeof value === "number" || + typeof value === "boolean" + ? [ + { + label, + value: String(value), + sourceReference: `/capability/raw/${jsonPointerSegment(label)}`, + }, + ] + : [], + ) + .sort((left, right) => + left.label === "amountIn" ? -1 : right.label === "amountIn" ? 1 : 0, + ) + .slice(0, 4); +} + export function capabilityInspectorModel( capability: Capability, limitations: readonly Limitation[], @@ -179,6 +214,7 @@ export function capabilityInspectorModel( return { availability: capability.availability, sourceReference: "/capability", + rawHighlights: [], riskLabels: [], limitations, failureCode: capability.failure.code, @@ -208,6 +244,7 @@ export function capabilityInspectorModel( { transactionIndex: 0 }, ), }), + rawHighlights: rawHighlights(capability.raw), riskLabels: riskLabels(capability.raw), limitations, failureSourceReferences: [], diff --git a/apps/web/src/components/capability-inspector.tsx b/apps/web/src/components/capability-inspector.tsx index c3831bd..ad7d7f4 100644 --- a/apps/web/src/components/capability-inspector.tsx +++ b/apps/web/src/components/capability-inspector.tsx @@ -102,6 +102,9 @@ export function CapabilityInspector({ aria-labelledby="capability-heading" >
+

Capability inspector

Hierarchy and transaction order from API evidence

@@ -118,9 +121,32 @@ export function CapabilityInspector({ ) : model.availability === "AVAILABLE" ? ( -
- Structured hierarchy not present - +
+
+ Agent prepared + {model.availability} +
+
+ {model.rawHighlights.map((highlight) => ( +
+
{highlight.label}
+
+ {highlight.value} + +
+
+ ))} +
+
+ Original raw artifact preserved + Structured hierarchy not present + +
) : (
diff --git a/apps/web/src/components/comparison-strip.tsx b/apps/web/src/components/comparison-strip.tsx index 5e27ae7..2957c3a 100644 --- a/apps/web/src/components/comparison-strip.tsx +++ b/apps/web/src/components/comparison-strip.tsx @@ -1,3 +1,4 @@ +import type { Provenance } from "@moss-mini-demo/report-schema"; import type { ComparisonStripInput } from "../client/comparison-strip"; import { comparisonColumns } from "../client/comparison-strip"; @@ -15,51 +16,112 @@ function SourceReferences({ ); } -export function ComparisonStrip(props: ComparisonStripInput) { +function formattedAmount(value: string): string { + if (!/^\d+$/.test(value)) return value; + const padded = value.padStart(19, "0"); + const whole = padded.slice(0, -18); + const fraction = padded.slice(-18).replace(/0+$/, ""); + return `${BigInt(whole)}.${fraction || "0"}`; +} + +function columnTitle(key: "intent" | "prepared" | "simulation"): string { + if (key === "intent") return "User intent"; + if (key === "prepared") return "Agent prepared"; + return "Simulation observed"; +} + +export function ComparisonStrip({ + provenance, + ...props +}: ComparisonStripInput & Readonly<{ provenance: Provenance }>) { const columns = comparisonColumns(props); return (
+
-

Three-way comparison

+

+ Source-bound values +

Request, prepared Capability and observed Simulation stay distinct

- {columns.map((column) => ( -
-
- -
-

{column.title}

-

{column.description}

-
-
-
- {column.items.map((entry) => ( -
-
{entry.label}
-
- {entry.value} - -
+ {columns.map((column) => { + const amountLabel = + column.key === "simulation" ? "Amount in observed" : "Amount in"; + const amount = column.items.find( + (entry) => entry.label === amountLabel, + ); + const detailItems = column.items.filter( + (entry) => entry.label !== amountLabel, + ); + + return ( +
+
+ +
+

{columnTitle(column.key)}

+

{column.description}

- ))} -
-
- ))} + + +
+ + {amount?.label ?? amountLabel} + + + {amount === undefined + ? "not present" + : formattedAmount(amount.value)} + + {amount === undefined ? null : ( + <> + NATIVE + + {amount.value} base units + + + + )} +
+ +
+ Inspect source-bound fields +
+ {detailItems.map((entry) => ( +
+
{entry.label}
+
+ {entry.value} + +
+
+ ))} +
+
+
+ ); + })}
+ ); } diff --git a/apps/web/src/components/core-tension.tsx b/apps/web/src/components/core-tension.tsx new file mode 100644 index 0000000..e8ea6db --- /dev/null +++ b/apps/web/src/components/core-tension.tsx @@ -0,0 +1,68 @@ +import type { Provenance } from "@moss-mini-demo/report-schema"; +import type { ComparisonStripInput } from "../client/comparison-strip"; +import { + coreTensionModel, + formatNativeAmount, + ratioLabel, + tensionProvenanceLabel, +} from "../client/core-tension"; + +export function CoreTension({ + input, + provenance, +}: Readonly<{ + input: ComparisonStripInput; + provenance: Provenance; +}>) { + const model = coreTensionModel(input); + if (model === undefined) return null; + + return ( +
+
+ +

Core tension

+

+ Simulation Success +

+
+
+
Requested
+
+ {formatNativeAmount(model.requested)} + {model.requested} base units +
+
+
+
Prepared
+
+ {formatNativeAmount(model.prepared)} + {model.prepared} base units +
+
+
+
Observed
+
+ {formatNativeAmount(model.observed)} + {model.observed} base units +
+
+
+

Success is not authorization.

+ + {tensionProvenanceLabel(provenance)} + +
+ +
+ Alignment · Fail + + {ratioLabel(model.requested, model.observed)} + + AMOUNT IN MISMATCH +
+
+ ); +} diff --git a/apps/web/src/components/decision-banner.tsx b/apps/web/src/components/decision-banner.tsx index 146dbe0..a3b709e 100644 --- a/apps/web/src/components/decision-banner.tsx +++ b/apps/web/src/components/decision-banner.tsx @@ -28,14 +28,20 @@ export function DecisionBanner({ ) : null} {model.limitations.length > 0 ? ( -
    - {model.limitations.map((limitation) => ( -
  • - {limitation.code} - {limitation.description} -
  • - ))} -
+
+ + Limitations ·{" "} + {model.limitations.length.toString().padStart(2, "0")} + +
    + {model.limitations.map((limitation) => ( +
  • + {limitation.code} + {limitation.description} +
  • + ))} +
+
) : null}
{model.status} diff --git a/apps/web/src/components/evidence-timeline.tsx b/apps/web/src/components/evidence-timeline.tsx index 5d23552..00c2304 100644 --- a/apps/web/src/components/evidence-timeline.tsx +++ b/apps/web/src/components/evidence-timeline.tsx @@ -51,6 +51,45 @@ export function EvidenceTimeline({ simulation, }: Readonly<{ provenance: Provenance; simulation: Simulation }>) { const model = evidenceTimelineModel(simulation); + const receipts = model.transactions.flatMap( + (transaction) => transaction.receipts, + ); + const outcomes = model.transactions.flatMap( + (transaction) => transaction.outcomes, + ); + const warnings = model.transactions.flatMap( + (transaction) => transaction.warnings, + ); + const receiptStatus = + receipts.length > 0 && + receipts.every((artifact) => artifact.status === "SUCCESS") + ? "SUCCESS" + : (receipts[0]?.status ?? "NOT PRESENT"); + const stages = [ + { + label: "Coverage", + value: + model.statuses.find((status) => status.label === "Coverage")?.value ?? + model.availability, + }, + { + label: "Ordering", + value: + model.statuses.find((status) => status.label === "Ordering")?.value ?? + model.availability, + }, + { + label: "Continuity", + value: + model.statuses.find((status) => status.label === "State continuity") + ?.value ?? model.availability, + }, + { + label: "Execution", + value: model.executionStatus ?? model.availability, + }, + { label: "Boundary", value: "NOT AUTHORIZATION" }, + ]; return (
+

Simulation evidence

Indexed records in API evidence order

@@ -78,112 +120,175 @@ export function EvidenceTimeline({
- {model.statuses.length > 0 ? ( -
- {model.statuses.map((status) => ( -
-
{status.label}
-
- {status.value} - {status.availability} - -
-
- ))} -
- ) : null} +
    + {stages.map((stage, index) => ( +
  1. + {String(index + 1).padStart(2, "0")} + {stage.label} + {stage.value} +
  2. + ))} +
{model.transactions.length > 0 ? ( -
    - {model.transactions.map((transaction) => ( -
  1. -
- - -
- - -
- -
-
Warnings
- {transaction.warnings.length > 0 ? ( -
    - {transaction.warnings.map((warning) => ( -
  • - {warning.code} -

    {warning.message}

    - -
  • - ))} -
- ) : ( -

None present.

- )} -
-
- - ))} - + + ))} + + + ) : (
) { + provenance, +}: Readonly<{ + presentation: PreflightPresentation; + provenance: Provenance; +}>) { if (presentation.decision.status !== "STOP") return null; return (
+

Why STOP

Exact reason codes and source references from the API Decision

@@ -29,6 +37,12 @@ export function StopDetails({ {presentation.decision.reasons.length}
+
+ Action boundary + {presentation.decision.actionBoundary} + Provenance + {provenance} +
    {presentation.decision.reasons.map((reason) => (
  1. diff --git a/apps/web/src/components/workbench-shell.tsx b/apps/web/src/components/workbench-shell.tsx index 8f90874..deaca7f 100644 --- a/apps/web/src/components/workbench-shell.tsx +++ b/apps/web/src/components/workbench-shell.tsx @@ -23,6 +23,7 @@ import type { PreflightRequest, RunId } from "../contracts/preflight"; import { AlignmentList } from "./alignment-list"; import { CapabilityInspector } from "./capability-inspector"; import { ComparisonStrip } from "./comparison-strip"; +import { CoreTension } from "./core-tension"; import { CredentialActions } from "./credential-actions"; import { DecisionBanner } from "./decision-banner"; import { EvidenceTimeline } from "./evidence-timeline"; @@ -125,11 +126,15 @@ export function WorkbenchShell({ >(undefined); const nextToken = useRef(0); const controlPane = useRef(null); - const resultRegion = useRef(null); + const resultRegion = useRef(null); useEffect(() => { if (state.status === "RESULT" || state.status === "ERROR") { - resultRegion.current?.focus(); + const target = resultRegion.current; + requestAnimationFrame(() => { + target?.focus({ preventScroll: true }); + target?.scrollIntoView({ block: "start" }); + }); } }, [state.status]); @@ -280,9 +285,9 @@ export function WorkbenchShell({ : undefined; return ( -
    +
    - + +
      -
    • +
    • Network: {report?.network ?? "awaiting run"}
    • -
    • +
    • Optional profile:{" "} {clear402Enabled ? "Clear402 enabled" : "disabled"}
    -
    +
    +
    + +
    -

    Exact-input Swap preflight

    + Evidence ledger / v0.1 +

    Exact-input Swap preflight

    Structured intent, protocol quotes and evidence provenance

    @@ -370,7 +434,7 @@ export function WorkbenchShell({
-
+

Run result

{stateDescription(state.status)}

@@ -386,8 +450,6 @@ export function WorkbenchShell({ aria-busy={state.status === "RUNNING"} aria-live="polite" className="result-surface" - ref={resultRegion} - tabIndex={-1} > {liveRecovery ? ( +

No result

Run identifiers and bounded Decision output appear here.

+
) : null} @@ -414,12 +485,25 @@ export function WorkbenchShell({ ) : null} {state.status === "RESULT" ? ( -
+
+ + {credentialExtension ? ( ) : null} @@ -427,6 +511,7 @@ export function WorkbenchShell({ - -
Run ID
@@ -533,6 +616,11 @@ export function WorkbenchShell({ provenance={state.response.report.provenance} simulation={state.response.report.simulation} /> + +
) : null} diff --git a/apps/web/test/capability-inspector.test.tsx b/apps/web/test/capability-inspector.test.tsx index b5a55da..75cfaa6 100644 --- a/apps/web/test/capability-inspector.test.tsx +++ b/apps/web/test/capability-inspector.test.tsx @@ -116,6 +116,32 @@ describe("Capability inspector model", () => { expect(model.limitations).toEqual([LIMITATION]); }); + it("surfaces source-bound scalar raw fields without inventing hierarchy", () => { + const capability = CapabilitySchema.parse({ + availability: "AVAILABLE", + raw: { + origin: "synthetic-development-fixture", + amountIn: "10000000000000000000", + nested: { retained: true }, + }, + }); + + const model = capabilityInspectorModel(capability, []); + expect(model.root).toBeUndefined(); + expect(model.rawHighlights).toEqual([ + { + label: "amountIn", + value: "10000000000000000000", + sourceReference: "/capability/raw/amountIn", + }, + { + label: "origin", + value: "synthetic-development-fixture", + sourceReference: "/capability/raw/origin", + }, + ]); + }); + it("serializes the untouched raw object including unknown fields", () => { const serialized = serializeRawArtifact(CAPABILITY); expect(JSON.parse(serialized)).toEqual(CAPABILITY); diff --git a/apps/web/test/core-tension.test.ts b/apps/web/test/core-tension.test.ts new file mode 100644 index 0000000..dc35654 --- /dev/null +++ b/apps/web/test/core-tension.test.ts @@ -0,0 +1,43 @@ +import { PreflightReportSchema } from "@moss-mini-demo/report-schema"; +import { describe, expect, it } from "vitest"; +import amountMismatchReport from "../../../packages/report-schema/fixtures/amount-in-mismatch.v0.1.json"; +import manualReviewReport from "../../../packages/report-schema/fixtures/manual-review-success.v0.1.json"; +import { + coreTensionModel, + formatNativeAmount, + ratioLabel, + tensionProvenanceLabel, +} from "../src/client/core-tension"; + +function input(report: ReturnType) { + return { + capability: report.capability, + intent: report.intent, + quotes: report.quotes, + selection: report.selection, + simulation: report.simulation, + }; +} + +describe("Core tension", () => { + it("retains exact mismatch amounts and derives the provenance label", () => { + const report = PreflightReportSchema.parse(amountMismatchReport); + expect(coreTensionModel(input(report))).toEqual({ + requested: "1000000000000000000", + prepared: "10000000000000000000", + observed: "10000000000000000000", + }); + expect(formatNativeAmount("1000000000000000")).toBe("0.001 NATIVE"); + expect(ratioLabel("1000000000000000000", "10000000000000000000")).toBe( + "10x", + ); + expect(tensionProvenanceLabel("LIVE_SOURCE")).toBe( + "LIVE_SOURCE / SOURCE-BOUND", + ); + }); + + it("stays absent when the source-bound amounts align", () => { + const report = PreflightReportSchema.parse(manualReviewReport); + expect(coreTensionModel(input(report))).toBeUndefined(); + }); +}); diff --git a/artifacts/screenshots/mini-desktop-happy.png b/artifacts/screenshots/mini-desktop-happy.png index 7a519fb..4ecdaea 100644 Binary files a/artifacts/screenshots/mini-desktop-happy.png and b/artifacts/screenshots/mini-desktop-happy.png differ diff --git a/artifacts/screenshots/mini-desktop-stop.png b/artifacts/screenshots/mini-desktop-stop.png index fd1d1c6..0a0f28a 100644 Binary files a/artifacts/screenshots/mini-desktop-stop.png and b/artifacts/screenshots/mini-desktop-stop.png differ diff --git a/artifacts/screenshots/mini-mobile-happy.png b/artifacts/screenshots/mini-mobile-happy.png index e7e066a..e75f485 100644 Binary files a/artifacts/screenshots/mini-mobile-happy.png and b/artifacts/screenshots/mini-mobile-happy.png differ diff --git a/artifacts/screenshots/mini-mobile-stop.png b/artifacts/screenshots/mini-mobile-stop.png index 2860c65..aba4935 100644 Binary files a/artifacts/screenshots/mini-mobile-stop.png and b/artifacts/screenshots/mini-mobile-stop.png differ diff --git a/artifacts/screenshots/visual-1280x720-stop.png b/artifacts/screenshots/visual-1280x720-stop.png index a4b393f..b54dfa3 100644 Binary files a/artifacts/screenshots/visual-1280x720-stop.png and b/artifacts/screenshots/visual-1280x720-stop.png differ diff --git a/artifacts/screenshots/visual-1440x900-happy.png b/artifacts/screenshots/visual-1440x900-happy.png index 55a10ec..aeb618f 100644 Binary files a/artifacts/screenshots/visual-1440x900-happy.png and b/artifacts/screenshots/visual-1440x900-happy.png differ diff --git a/artifacts/screenshots/visual-360x800-stop.png b/artifacts/screenshots/visual-360x800-stop.png index 4c682c6..fabb711 100644 Binary files a/artifacts/screenshots/visual-360x800-stop.png and b/artifacts/screenshots/visual-360x800-stop.png differ diff --git a/artifacts/screenshots/visual-390x844-happy.png b/artifacts/screenshots/visual-390x844-happy.png index 959d809..c413cfb 100644 Binary files a/artifacts/screenshots/visual-390x844-happy.png and b/artifacts/screenshots/visual-390x844-happy.png differ diff --git a/docs/visual-qa-report.md b/docs/visual-qa-report.md index c48878f..51a665a 100644 --- a/docs/visual-qa-report.md +++ b/docs/visual-qa-report.md @@ -3,7 +3,7 @@ ## Conclusion SINGLE_OPERATOR_QA was executed on 2026-08-09 against exact candidate -`1842769004c044efa9e4414aa1c2df1ca0123629`. +`4e4611eaf6e2b761a68ab9111289df1c50456308`. Conclusion: PASS @@ -16,13 +16,13 @@ single-operator workflow. ## Candidate and method - Repository: -- Exact checkout: `1842769004c044efa9e4414aa1c2df1ca0123629` -- Deployed RC: +- Exact product-code checkout: `4e4611eaf6e2b761a68ab9111289df1c50456308` +- Deployment: pending merge; screenshots use the exact local production build - Node.js: `v22.23.1` - pnpm: `11.16.0` - Runtime profile: `CLEAR402_ENABLED=false` -The application was built and served from the exact local checkout. Browser +The application was built and served from the exact product-code checkout. Browser screenshots were captured from that production build, not from a prior build or an unrelated URL. Dynamic run IDs were replaced with the stable `run_redacted-for-stable-qa` marker before capture; no other visible evidence @@ -42,11 +42,12 @@ result viewport and the long-form evidence below it. ## Automated evidence -`pnpm test:e2e --grep 'visual|responsive'` passed against the production server: -8 tests passed and 2 Clear402-enabled-only tests were skipped as required by -the disabled runtime profile. The matched suite covered desktop/mobile happy -and STOP paths, accessible controls, loading stability, disabled-profile -isolation, and explicit Live-failure recovery. +`pnpm test:e2e --grep 'responsive|fixed compact|accessibility'` passed against +the production server: 10 tests passed and 2 Clear402-enabled-only tests were +skipped as required by the disabled runtime profile. The matched suite covered +desktop/mobile happy and STOP paths, all four fixed screenshot viewports, +accessible controls, loading stability, disabled-profile isolation, and +explicit Live-failure recovery. An additional exact-candidate Playwright audit covered all four fixed viewports. Every viewport reported: diff --git a/tests/e2e/gate-a-smoke.e2e.mjs b/tests/e2e/gate-a-smoke.e2e.mjs index bcf6191..4eeb705 100644 --- a/tests/e2e/gate-a-smoke.e2e.mjs +++ b/tests/e2e/gate-a-smoke.e2e.mjs @@ -67,9 +67,9 @@ async function assertNoHorizontalOverflow(page) { async function assertJudgeComprehension(page, decision) { const headings = page.locator(".comparison-column h4"); await expect(headings).toHaveText([ - "User request", + "User intent", "Agent prepared", - "Simulation occurred", + "Simulation observed", ]); await expect(page.locator(".decision-banner")).toContainText(decision); if (decision === "MANUAL_REVIEW") { @@ -80,6 +80,17 @@ async function assertJudgeComprehension(page, decision) { await expect(page.locator(".stop-details")).toContainText( "CRITICAL_ALIGNMENT_FAIL", ); + await expect(page.locator(".comparison-primary-value > strong")).toHaveText( + ["1.0", "10.0", "10.0"], + ); + await expect(page.locator(".comparison-primary-raw")).toHaveText([ + "1000000000000000000 base units", + "10000000000000000000 base units", + "10000000000000000000 base units", + ]); + await expect(page.locator(".simulation-boundary-note")).toContainText( + "Success is not permission to sign", + ); } }