carlos / contra Public

web: stop the front page saying things that stopped being true

It read "there is no signup, no vault and no stored data behind it" on a
build with all three. That was true when it was written, at web/1, and
nobody re-read it as the app grew underneath it.

For most products that is a stale sentence. For this one it is the product
failing at the only thing it claims: that its statements are literally true
rather than carefully worded. A page that says a feature does not exist, on a
build where it does, is worse than no page.

So the page now says what this actually is — a preview you can really use and
should not really trust — and names the two gaps that matter at the moment
somebody would need them: the Emergency Kit prints as text rather than the
two PDFs, and a browser that has never seen your Account Key cannot yet be
given it.

Two tests keep it honest rather than trusting the next person to remember.
One fails if the page claims there is no signup while /signup answers. The
other fails if any page offers no way onward — which is the second half of
what the screenshot showed: the navigation rail was empty, so a visitor
landing on the front page could not reach signup at all. The rail is now
defined once in the layout, because a page that forgets to define it renders
nothing and says nothing about having done so.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev 1e27375f5544dc5edee213b3b514d75c44ce19de parent 3ab64a3
3 files changed, +75 −17
  • internal/contra/templates/index.html +27 −16
  • internal/contra/templates/layout.html +8 −1
  • internal/contratest/index_test.go +40 −0
diff --git a/internal/contra/templates/index.html b/internal/contra/templates/index.html
index 76d170a..25e35ef 100644
--- a/internal/contra/templates/index.html
+++ b/internal/contra/templates/index.html
@@ -3,26 +3,37 @@
{{define "brand"}}<a rst-shell-brand href="/">CONTRA</a>{{end}}
{{/* The prose here is lifted verbatim from README.md rather than written
- fresh. Copy is a reviewed artifact in this repo, and the bootstrap
- canary needed a page before that review could happen — so it says only
- what the README already says, and the real console copy arrives with
- the screens it belongs to. */}}
+ fresh. Copy is a reviewed artifact in this repo, and the canary needed a
+ page before that review could happen — so it says only what the README
+ already says. The build note below is operational, not marketing, and it
+ has to stay true as the app grows: it was wrong once already, claiming
+ there was no signup on a build that had one. */}}
{{define "content"}}
<h1>A password manager where the server knows nothing about you.</h1>
<p>Not &ldquo;we promise not to look.&rdquo; Not &ldquo;we encrypt at
-rest.&rdquo; The server that holds your vault cannot open it, cannot tell
-you apart from anyone else, and does not know your email address. If it is
-stolen tomorrow, the thief gets a pile of ciphertext and the times of day
-you connected.</p>
-
-<h2>This instance is not open yet</h2>
-
-<p>What you are looking at is the bootstrap canary: the instance, its
-database and its release pipeline, deployed so the rest can be reviewed on
-a real URL rather than on somebody&rsquo;s laptop. There is no signup, no
-vault and no stored data behind it — the schema that will hold your items
-lands with the tests that prove it holds nothing readable.</p>
+rest.&rdquo; The server that holds your vault cannot open it, cannot tell you
+apart from anyone else, and does not know your email address. If it is stolen
+tomorrow, the thief gets a pile of ciphertext and the times of day you
+connected.</p>
+
+<h2>This is a preview, not a product</h2>
+
+<p>You can create an account, sign in and keep items here, and all of it is
+sealed in your browser before it reaches the server. But this is a review
+build on its way to being finished, not something to trust with the passwords
+you actually rely on. Nothing here has been audited, and there is no promise
+that data on this instance survives the next deploy.</p>
+
+<p>What is missing matters most at the moment you would need it: the
+Emergency Kit still prints as text on screen rather than the two PDFs it will
+be, and a browser that has never seen your Account Key cannot yet be given
+it — so treat every account here as one you could lose.</p>
+
+<p>
+ {{if .SignedIn}}<a href="/vault">Open your vault</a>{{else}}<a href="/signup">Create an account</a> &middot;
+ <a href="/signin">Sign in</a>{{end}}
+</p>
<p>Build <code>{{.Version}}</code>.</p>
{{end}}
diff --git a/internal/contra/templates/layout.html b/internal/contra/templates/layout.html
index 002783b..1d6bee2 100644
--- a/internal/contra/templates/layout.html
+++ b/internal/contra/templates/layout.html
@@ -93,7 +93,14 @@
a nav that sticks: see the max-block-size in tokens.css, which is
measured against the viewport and gated against the viewport. */}}
<aside rst-shell-rail>
-<nav rst-shell-nav>{{block "nav" .}}{{end}}</nav>
+{{/* The default navigation, defined once here rather than per page: a
+ page that forgets to define it would render an empty rail, which is
+ what the canary shipped with until somebody looked at it. Any page
+ can still override the block. */}}
+<nav rst-shell-nav>{{block "nav" .}}<a href="/">Home</a>
+{{if .SignedIn}}<a href="/vault">Your vault</a>
+<a href="/signup">Add another account</a>{{else}}<a href="/signup">Create an account</a>
+<a href="/signin">Sign in</a>{{end}}{{end}}</nav>
</aside>
<main rst-shell-main id="main">
<div rst-page>
diff --git a/internal/contratest/index_test.go b/internal/contratest/index_test.go
index f3d4049..dd4c468 100644
--- a/internal/contratest/index_test.go
+++ b/internal/contratest/index_test.go
@@ -69,3 +69,43 @@ func TestErrorPageRendersFrameworkPartial(t *testing.T) {
t.Errorf("ErrorPage body missing the ref:\n%s", body)
}
}
+
+// The home page shipped for a while saying "there is no signup, no vault and
+// no stored data behind it" on a build that had all three. It was true when
+// it was written and nobody re-read it as the app grew.
+//
+// So: the claims on the front page are pinned against the routes that exist.
+// A page that says a thing does not exist, on a build where it does, is worse
+// than no page — this is a product whose entire pitch is that its statements
+// are literally true.
+func TestHomePageDoesNotContradictTheApp(t *testing.T) {
+ h := newApp(t)
+ body := get(t, h, "/").Body.String()
+
+ // If /signup answers, the home page must not claim there is no signup.
+ if get(t, h, "/signup").Code == http.StatusOK {
+ for _, lie := range []string{
+ "no signup", "no vault and no stored data", "not open yet",
+ } {
+ if strings.Contains(strings.ToLower(body), lie) {
+ t.Errorf("the home page still says %q, but /signup works", lie)
+ }
+ }
+ }
+}
+
+// Every page must offer a way onward. An empty navigation rail is how the
+// canary shipped, and a visitor who cannot reach signup from the front page
+// has been handed a dead end.
+func TestEveryPageOffersAWayOnward(t *testing.T) {
+ h := newApp(t)
+ for _, path := range []string{"/", "/signup", "/signin"} {
+ body := get(t, h, path).Body.String()
+ if !strings.Contains(body, `href="/signup"`) && !strings.Contains(body, `href="/vault"`) {
+ t.Errorf("%s offers no way onward: no link to signup or the vault", path)
+ }
+ if !strings.Contains(body, `href="/"`) {
+ t.Errorf("%s has no link home", path)
+ }
+ }
+}