From 71ae6937bb355876dff1fdd150cb3adc52d2018d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Onat=20=C3=96zmen?= <212128834+onatozmenn@users.noreply.github.com> Date: Sat, 15 Aug 2026 15:47:38 +0300 Subject: [PATCH] Lead the site with review receipts --- README.md | 9 +- assets/style.css | 571 ++++++++++++++++++++++++++++++++++++++++++++++- index.html | 229 +++++++++++++------ tools/check.mjs | 16 ++ 4 files changed, 745 insertions(+), 80 deletions(-) diff --git a/README.md b/README.md index b5b82cf..8fa54c3 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,9 @@ Concretely, that rules out a few things that would otherwise be tempting: pinned tag, and the summary under each one is the comment at the top of the file. Which fourteen of them the picker offers is a choice made here, and it is the only one: choosing what to show is not writing it, and every file is - still served. The landing page's program is the exception, and it is short - and its refusal was still produced by running it. + still served. The landing page's program and compact review receipt are the + exceptions. Both are short, and `tools/check.mjs` holds their answers against + the pinned compiler before they can ship. The split exists because the two repositories have different constraints. The compiler's has no dependencies on purpose and its tests are strict in ways @@ -119,7 +120,7 @@ checked through the pinned artifact and every one of them is clean. ## Layout ``` -index.html what the language is +index.html what the language reviews and why that matters play/ the playground agents/ review receipts and what `deed mcp` hands back, asked in the tab errors/ every diagnostic code, read out of the compiler @@ -153,5 +154,5 @@ request too. ## Licence -Apache-2.0, matching the compiler. +MIT OR Apache-2.0, matching the compiler. diff --git a/assets/style.css b/assets/style.css index 7e37a08..c77091e 100644 --- a/assets/style.css +++ b/assets/style.css @@ -232,13 +232,23 @@ nav.site { position: sticky; top: 0; z-index: 1; + display: flex; + justify-content: center; + gap: 0 1rem; margin: 0 0 1.5rem; padding: 0 4px; text-align: center; + white-space: nowrap; + overflow-x: auto; + scrollbar-width: none; background: var(--transwhite); backdrop-filter: blur(1px); } +nav.site::-webkit-scrollbar { + display: none; +} + nav.site a { display: inline-block; margin: 0.75rem 0; @@ -246,7 +256,7 @@ nav.site a { } nav.site a:not(:last-child) { - padding-right: 12px; + padding-right: 0; } nav.site a.active { @@ -256,8 +266,12 @@ nav.site a.active { } @media (max-width: 600px) { - nav.site a:not(:last-child) { - padding-right: 0; + nav.site { + justify-content: flex-start; + margin-right: -20px; + margin-left: -20px; + padding-right: 20px; + padding-left: 20px; } } @@ -277,6 +291,542 @@ main h1 { /* Landing */ +.home .wrap { + max-width: 90rem; + margin: 0 auto; +} + +.landing .eyebrow { + margin: 0 0 0.75rem; + color: var(--gray); + font-size: 0.78rem; + font-weight: 700; + text-transform: uppercase; +} + +.hero { + display: grid; + grid-template-columns: minmax(0, 0.92fr) minmax(30rem, 1.08fr); + gap: 4rem; + align-items: center; + min-height: 34rem; + padding: 2rem 0 4rem; +} + +.hero-copy, +.receipt-shell { + min-width: 0; +} + +.hero-copy { + animation: landing-rise 0.45s ease-out both; +} + +.hero h1 { + max-width: 14ch; + margin: 0 0 1.25rem; + font-size: 3.25rem; + line-height: 1.04; +} + +.hero-lede { + max-width: 62ch; + margin: 0; + color: var(--gray); + font-size: 1.05rem; + line-height: 1.65; +} + +.hero-actions { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + margin: 1.75rem 0 0; +} + +.hero-actions .action { + display: inline-block; + padding: 0.65rem 1rem; + border: 1px solid var(--black); + color: var(--black); + text-decoration: none; + transition: + transform 0.16s ease, + box-shadow 0.16s ease; +} + +.hero-actions .action.primary { + background: var(--black); + color: var(--white); +} + +.hero-actions .action:hover { + transform: translateY(-2px); + box-shadow: 3px 3px 0 var(--blue); +} + +.proof-strip { + display: flex; + flex-wrap: wrap; + gap: 0.35rem 0; + margin: 1.5rem 0 0; + padding: 0; + color: var(--gray); + font-size: 0.78rem; + list-style: none; +} + +.proof-strip li + li::before { + content: "/"; + margin: 0 0.65rem; + color: var(--pink); +} + +.receipt-shell { + border: 1px solid var(--gray); + background: var(--console); + color: var(--white); + box-shadow: 10px 10px 0 var(--moretransblack); + animation: landing-rise 0.55s 0.08s ease-out both; +} + +.receipt-bar { + display: flex; + justify-content: space-between; + gap: 1rem; + padding: 0.55rem 0.8rem; + border-bottom: 1px solid #555; + color: #a4a4a4; + font-size: 0.78rem; +} + +.receipt-policy { + color: #f2777a; +} + +.patch-lines { + padding: 1rem; + border-bottom: 1px solid #555; + background: #1f1f1f; + font-size: 0.85rem; + overflow-x: auto; +} + +.patch-lines p { + width: max-content; + min-width: 100%; + margin: 0; +} + +.patch-lines .removed { + color: #f2777a; +} + +.patch-lines .added { + color: #24d05a; +} + +.receipt-result { + padding: 1rem; +} + +.receipt-status { + display: flex; + align-items: center; + gap: 0.6rem; + margin: 0 0 1rem; + color: var(--white); + font-size: 1.2rem; +} + +.receipt-status span { + width: 0.7rem; + height: 0.7rem; + flex: 0 0 auto; + background: #f2777a; +} + +.receipt-result dl { + margin: 0; +} + +.receipt-result dl > div { + display: grid; + grid-template-columns: minmax(8rem, 0.75fr) minmax(0, 1.25fr); + gap: 1rem; + padding: 0.6rem 0; + border-top: 1px solid #555; +} + +.receipt-result dt { + color: #a4a4a4; +} + +.receipt-result dd { + margin: 0; + color: var(--white); + overflow-wrap: anywhere; +} + +.receipt-result dd[data-review-authority] { + color: #24d05a; +} + +.receipt-result dd[data-review-tier] { + color: #e6b455; +} + +.receipt-foot { + margin: 0; + padding: 0.7rem 1rem; + border-top: 1px solid #555; + color: #a4a4a4; + font-size: 0.72rem; +} + +.receipt-foot a { + color: var(--white); +} + +@keyframes landing-rise { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.landing-band { + padding: 4.5rem 0; + border-top: 1px solid var(--gray); +} + +.landing-band > h2, +.landing-band > .eyebrow { + text-align: left; +} + +.landing-band h2 { + max-width: 24ch; + margin: 0; + color: var(--black); + font-size: 2rem; + line-height: 1.15; +} + +.signal-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 2rem; + margin-top: 2.75rem; +} + +.signal-grid article { + min-width: 0; + padding-top: 1rem; + border-top: 3px solid var(--green); +} + +.signal-grid article:nth-child(2) { + border-top-color: var(--pink); +} + +.signal-grid article:nth-child(3) { + border-top-color: var(--blue); +} + +.signal-grid .signal-mark { + margin: 0 0 1.5rem; + color: var(--gray); + font-size: 2rem; + line-height: 1; +} + +.signal-grid h3 { + margin: 0 0 0.65rem; + color: var(--black); + font-size: 1rem; +} + +.signal-grid article > p:last-child { + margin: 0; + color: var(--gray); +} + +.workflow { + display: grid; + grid-template-columns: minmax(0, 0.8fr) minmax(32rem, 1.2fr); + gap: 4rem; + align-items: start; +} + +.workflow-steps { + margin: 0; + padding: 0; + list-style: none; +} + +.workflow-steps li { + display: grid; + grid-template-columns: 2.5rem minmax(0, 1fr); + gap: 1rem; + padding: 1rem 0; + border-top: 1px solid var(--gray); +} + +.workflow-steps li > span { + color: var(--syntax-string); + font-size: 0.8rem; +} + +.workflow-steps strong { + display: block; + font-weight: 400; +} + +.workflow-steps p { + margin: 0.35rem 0 0; + color: var(--gray); + font-size: 0.9rem; +} + +.language { + display: grid; + grid-template-columns: minmax(0, 0.8fr) minmax(30rem, 1.2fr); + gap: 4rem; + align-items: center; +} + +.language-copy p:not(.eyebrow) { + color: var(--gray); +} + +.language-sample { + width: 100%; + min-width: 0; +} + +.landing-next { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 2rem; + padding: 1rem 0 2rem; + border-top: 1px solid var(--gray); +} + +.landing-next .card { + min-width: 0; + margin: 0; +} + +@media (max-width: 56rem) { + .hero, + .workflow, + .language { + grid-template-columns: 1fr; + gap: 2.5rem; + } + + .hero { + min-height: 0; + padding-top: 1rem; + } + + .hero h1 { + max-width: 18ch; + } + + .receipt-shell { + width: 100%; + max-width: 44rem; + justify-self: center; + } +} + +@media (min-width: 56.01rem) and (max-width: 70rem) { + .hero { + grid-template-columns: minmax(0, 1fr) minmax(30rem, 1.08fr); + gap: 2rem; + min-height: 32rem; + padding: 1rem 0 2rem; + } + + .hero h1 { + max-width: none; + font-size: 2.35rem; + } + + .hero-lede { + font-size: 0.92rem; + } + + .proof-strip { + font-size: 0.7rem; + } +} + +@media (max-width: 42rem) { + .home header.site h1 { + font-size: 1.55rem; + } + + .hero { + gap: 0.5rem; + padding: 0.5rem 0 0.75rem; + } + + .hero h1 { + max-width: none; + margin-bottom: 0.9rem; + font-size: 1.9rem; + } + + .hero-lede { + font-size: 0.88rem; + line-height: 1.55; + } + + .hero-actions { + flex-wrap: nowrap; + gap: 0.5rem; + margin-top: 1.1rem; + } + + .hero-actions .action { + min-width: 0; + flex: 1 1 0; + padding: 0.55rem 0.3rem; + font-size: 0.8rem; + text-align: center; + } + + .proof-strip { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0; + margin-top: 0.8rem; + font-size: 0.65rem; + text-align: center; + } + + .proof-strip li { + padding: 0 0.35rem; + } + + .proof-strip li + li { + border-left: 1px solid var(--pink); + } + + .proof-strip li + li::before { + content: none; + } + + .receipt-shell { + box-shadow: 6px 6px 0 var(--moretransblack); + } + + .receipt-bar { + padding: 0.4rem 0.65rem; + font-size: 0.68rem; + } + + .patch-lines { + padding: 0.6rem 0.7rem; + font-size: 0.72rem; + } + + .receipt-result { + padding: 0.65rem 0.75rem; + } + + .receipt-status { + margin-bottom: 0.5rem; + font-size: 0.95rem; + } + + .receipt-result dl > div { + grid-template-columns: minmax(7rem, 0.8fr) minmax(0, 1.2fr); + gap: 0.5rem; + padding: 0.38rem 0; + font-size: 0.72rem; + } + + .receipt-foot { + display: none; + } + + .landing-band { + padding: 3.25rem 0; + } + + .landing-band h2 { + font-size: 1.6rem; + } + + .signal-grid, + .landing-next { + grid-template-columns: 1fr; + } + + .signal-grid { + gap: 2.25rem; + } + + .signal-grid .signal-mark { + margin-bottom: 0.75rem; + } + + .workflow, + .language { + gap: 2rem; + } +} + +@media (max-width: 42rem) and (max-height: 40rem) { + .home nav.site { + margin-bottom: 0.5rem; + } + + .hero-copy > .eyebrow, + .hero-lede, + .proof-strip, + .patch-lines { + display: none; + } + + .hero h1 { + margin-bottom: 0.5rem; + font-size: 1.5rem; + } + + .hero-actions { + margin-top: 0.5rem; + } + + .hero-actions .action { + padding: 0.4rem 0.1rem; + font-size: 0.7rem; + } + + .receipt-result dl > div { + padding: 0.28rem 0; + } +} + +@media (prefers-reduced-motion: reduce) { + .hero-copy, + .receipt-shell { + animation: none; + } + + .hero-actions .action { + transition: none; + } +} + /* The header above is centred, and main used to start by going back to the left edge. Headings and the short lines that report something are centred with it; the prose and the source under them are not, because a centred @@ -1072,6 +1622,21 @@ input[type="search"] { background: var(--moretranswhite); } + body .landing-band h2, + body .signal-grid h3 { + color: var(--white); + } + + body .hero-actions .action { + border-color: var(--white); + color: var(--white); + } + + body .hero-actions .action.primary { + background: var(--white); + color: var(--black); + } + .editor { background: var(--black); } diff --git a/index.html b/index.html index ddaf9e9..4676d76 100644 --- a/index.html +++ b/index.html @@ -3,18 +3,18 @@ - Deed + Deed | Review what an agent patch is allowed to do - + - +
@@ -66,90 +66,173 @@

Source -
-

A function can only do what its signature admits to.

-

- Not a lint, not a convention. Deed reads the body, and a function that - reaches for something its signature did not mention is not a program. -

+
+
+
+

Contract-first language / local review tool

+

Deed tells you when an agent patch gains power.

+

+ Deed compares the checked contract before and after a patch. It + flags new authority, weaker guarantees, and new runtime checks + even when the tests stay green. +

+ +
    +
  • Runs locally
  • +
  • Works in CI
  • +
  • Available over MCP
  • +
+
- -
-
-

This function saves a file.

-
fn keep(files: Dir, name: String, text: String) -> Result<(), String>
-  uses
-    Io.read,
-{
-    match Io.read(files, name) {
-        ok(_) => ok(()),
-        err(_) => Io.save(files, name, text),
-    }
-}
+ +
+
+ deed review + policy: blocked +
+
+

- uses Store.read

+

+ uses Store.read, Store.write

+
+
+

Review blocked

+
+
+
New authority
+
Store.write
+
+
+
Weaker promise
+
proven -> guarded
+
+
+
Tests
+
may still pass
+
+
+
+

+ Produced from the pinned Deed compiler. + Open the full receipt. +

+
+
+ +
+

What the receipt names

+

Review the change tests do not name.

+
+
+

+

+

New authority

+

+ A function starts writing files, calling a service, or performing + any effect its previous contract did not admit. +

+
+
+

-

+

Weaker guarantees

+

+ A promise that was proven at compile time now depends on a test + or a runtime guard. +

+
+
+

?

+

New uncertainty

+

+ The patch leaves a new obligation for runtime and says why the + checker could not settle it earlier. +

+
+
+
-

Its signature only admits to reading one.

-
error[DEED5001]: `keep` performs `Io.save` without declaring it
-  --> main.deed:3:4
-  |
-3 | fn keep(files: Dir, name: String, text: String) -> Result<(), String>
-  |    ^^^^ add `Io.save` to the `uses` clause
-  |
-  = note: a function can only do what its signature admits to
+

One boundary, three readers

+

Write the rule once. Let code, CI, and agents read it.

+
    +
  1. + 01 +
    + The signature states the authority. +

    The body is refused if it reaches for anything wider.

    +
    +
  2. +
  3. + 02 +
    + deed review compares two checked trees. +

    New authority and weaker tiers become structured evidence.

    +
    +
  4. +
  5. + 03 +
    + CI chooses what must block a merge. +

    Each review signal has its own independent policy gate.

    +
    +
  6. +
-

- That list has a name, an effect row, and it is part of the type. It is - checked in both directions, so a row that claims authority the body - never uses is an error too: a signature is only worth reading if it is - tight. Nothing is ambient. If a function can touch the filesystem, it - was handed the filesystem, and you can see that from the call. -

+
+
+

Underneath the receipt

+

A small language, not another policy file.

+

+ Deed checks types, effects, capabilities, contracts, and the code + that must satisfy them. Nothing is ambient. If a function can + touch the filesystem, the filesystem appears in its inputs and + the operation appears in its signature. +

+ See one clause become a boundary +
+
fn keep(files: Dir, name: String, text: String)
+  -> Result<(), String>
+  uses Io.read,
+{
+    match Io.read(files, name) {
+        ok(_)  => ok(()),
+        err(_) => Io.save(files, name, text),
+    }
+}
 
-        
+
+ +

diff --git a/tools/check.mjs b/tools/check.mjs index 51a9dc6..07660dd 100644 --- a/tools/check.mjs +++ b/tools/check.mjs @@ -195,6 +195,22 @@ if (deed) { ) { complain("agents/", `the review demo says regressions are ${JSON.stringify(receipt.tier_regressions)}`); } + + const landing = await readFile(join(root, "index.html"), "utf8"); + const landingAuthority = `data-review-authority="${authority?.authority}"`; + const landingTier = `data-review-tier="${regression?.before}:${regression?.after}"`; + if (!landing.includes(landingAuthority)) { + complain("index.html", `does not show the review demo authority ${authority?.authority}`); + } + if (!landing.includes(landingTier)) { + complain( + "index.html", + `does not show the review demo tier change ${regression?.before} -> ${regression?.after}`, + ); + } + if (receipt.clean !== false || !landing.includes("Review blocked")) { + complain("index.html", "does not show that the review demo is blocked"); + } } } catch (error) { complain("agents/", `the pinned artifact could not review the demo: ${error.message}`);