Skip to content

Landing: show the finished app first, then explain it - #136

Merged
adnaan merged 6 commits into
mainfrom
landing-ia
Aug 8, 2026
Merged

Landing: show the finished app first, then explain it#136
adnaan merged 6 commits into
mainfrom
landing-ia

Conversation

@adnaan

@adnaan adnaan commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #135. That one changed how the site looks; this changes what the landing says and in what order.

Why

The six-step spine asked the reader to travel five steps before seeing what the thing is. This leads with the finished app — a shared greeting wall, live on the page — and its code, then annotates the parts of that code worth a second look.

Each section below the fold points back at lines you have already read rather than adding a new one: the button name is the action, the same form works with scripting off, the HTML rule re-runs in Go, and two Publish calls are the whole difference between "my tabs" and "everyone". Every claim keeps its live app, now as evidence rather than as a step to climb.

This touches a shipped example, not just copy

examples/greet-wall gains real behavior, because the alternative was worse. The hero is meant to be the app the rest of the page annotates — so a breakdown pointing at code the demo does not run is a lie, not a shortcut. Rather than adding the missing lines to the snippet, the features went into the app:

  • the input carries required, and Greet calls ctx.ValidateForm() to re-run exactly that rule server-side, so a client that skipped it — scripting off, or a direct POST — gets the same answer;
  • a reserved-name rule HTML cannot state ("admin"), which also stops a visitor posing as the server on a public wall.

Verified in a browser rather than asserted: admin comes back as an inline field error with aria-invalid="true" and the headline unchanged; Ada goes through.

The wall's own presentation also moved into wall.tmpl (list markers, timestamp colour), so it travels with the app to the recipe pages that embed it.

What is honest about the hero, and what isn't

  • The template is verbatim — 9 lines, only the server-heartbeat markup dropped because it is explained further down.
  • The Go is 28 lines against a real file of 249. The old step-1 hero could honestly say "this is the whole app" about a 20-line greet; the wall cannot. The paragraph under it names what is missing — sanitize, the map writes behind saveName, a 20-line cap, a per-session throttle — and links the real file.
  • Pending state is the one section that does not point back. The wall answers instantly, so it has no pending state to render, and the page says so outright. Making it Async to fake one was considered and rejected: apply receives no *Context (reference/api.md:391), so the two ctx.Publish calls could not run inside it, and with no live connection apply never runs at all — the plain-HTTP path would drop greetings silently.
  • No lvt-* attributes in the hero. It briefly carried two, directly above a section titled "No attributes" calling them an escape hatch. That argued against the page's own point.

Highlighting

The four-hue palette from #135 was too timid to read: #5A5560 is R90 G85 B96, all but neutral, so names rendered as "dark" rather than as a colour. Names are now plum #6B4A7A and built-in types and literals get amber #7D5226, which also stops Go's string/error reading as string literals. Six hues, each mapping to a distinct token class, all still darker than --lt-meta so the ≤13px contrast floor holds. builtin also had to come out of the string rule — it was listed in both, and the later one was winning.

e2e is untouched

Every test that loads / uses scoped embed selectors, .hero, or the iframe sandbox string; the ones with bare input[name="name"] navigate to standalone app URLs. All five embed paths, both nojs-frame iframes and .hero survive the reorder, so the whole TestSpine* suite passes without modification.

Verification

  • Full go test ./e2e against the pinned v0.3.9 image: green, 161s.
  • examples/greet-wall unit tests: green.
  • make sweep: 196 page-viewport visits, 0 overflow. Two flags remain on /recipes/ui-patterns/lists/large-table, a pre-existing 4.9 MB page that renders in 9.6s on production and ~7s here.
  • A symbol audit over the rendered page: every "you already saw" claim resolves to the hero snippet.

That last check is currently a script, not a test. The failure mode is silent and returns every time the hero code is edited, so it is worth promoting to calm_theme_test.go — happy to do that here or in a follow-up.

🤖 Generated with Claude Code

https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ

adnaan and others added 6 commits August 8, 2026 01:22
The six-step spine asked the reader to travel five steps before seeing
what the thing is. This leads with the finished app — a shared greeting
wall, live on the page — and its code, then annotates the parts of that
code worth a second look.

Each section below the fold points back at lines already read rather than
adding a new one: the button name is the action, the same form works with
scripting off, the HTML rule re-runs in Go, slow work has a pending state,
and two Publish calls are the whole difference between "my tabs" and
"everyone". Every claim keeps its live app, now as evidence rather than as
a step to climb.

The hero's Go is 28 lines against a real file of 249, so the page says so
in the paragraph under it and links the file. Overclaiming "this is the
whole app" is what the old step-1 hero could honestly say about a 20-line
greet; the wall cannot. The template, by contrast, is verbatim: 9 lines,
with only the server-heartbeat markup dropped because it is explained
further down.

The hero's two snippets stack full width instead of sitting side by side.
Read in sequence rather than compared, and at 1440px a .pair column is
~420px against signatures past 60 characters — every interesting line was
clipping.

e2e is untouched and passes unchanged. Every test that loads "/" uses
scoped embed selectors, .hero, or the iframe sandbox string; the ones with
bare input[name=name] selectors navigate to standalone app URLs. All five
embed paths, both nojs iframes and .hero survive the reorder.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ
"including anyone else reading this page right now" is unverifiable for
the usual visitor, who is alone on the page. The lead now links a second
tab of the same page, so the claim is something you can test in one click
rather than take on trust.
…true

Two reports, both fair.

"Still no highlight": the fourth hue was #5A5560, which is R90 G85 B96 —
all but neutral. Its luminance distance from plain text was fine, the
same as the keyword blue's; what it lacked was chroma, so it read as
"dark" rather than as a colour. Names are now plum #6B4A7A and built-in
types and literals get their own amber #7D5226, which also stops Go's
`string`/`error` reading as string literals. Six hues, each mapping to a
distinct token class, all still darker than --lt-meta so the <=13px
contrast floor holds. `builtin` had to come out of the string rule too —
it was listed in both, and the later one was winning.

"The final app doesn't have the snippets the later breakdowns call out":
correct, and it was worse than the one instance. Five referenced symbols
were absent from the hero — lvtClientScriptURL, ValidateForm, EqualFold,
lvt.Pending, lvt.Async — while the section intro promised that everything
below pointed back at lines already read. That promise is the entire
premise of showing the app first, so:

  - the hero template gains its <script> line, which is genuinely in
    wall.tmpl and is what the no-JavaScript section points at;
  - the validation section now leads with the NewFieldError the wall
    really does enforce, and introduces ValidateForm as the second half,
    attributed to greet-validate rather than implied to be in the hero;
  - the pending section says outright that the wall answers instantly and
    these are two different apps;
  - the intro no longer claims everything points backwards, and names the
    two sections that don't.

An audit over the rendered symbols now shows every already-seen claim
resolving to the hero snippet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ
The hero is supposed to be the app the rest of the page annotates, so a
breakdown pointing at code that isn't there breaks the premise. Five
symbols were only in the prose. The fix belongs in the app, not the
snippet: if the code shows ValidateForm, the demo has to do it.

greet-wall gains what it was missing and now genuinely has it:

  - the input carries `required`, and Greet calls ctx.ValidateForm() to
    re-run exactly that rule server-side, so a client that skipped it —
    scripting off, or a direct POST — gets the same answer;
  - a reserved-name rule HTML cannot state ("admin"), which also stops a
    visitor posing as the server on a public wall;
  - the button carries the two lvt-el:*:on:pending/done attributes, so
    pending is real behaviour rather than a claim. No server state and no
    spinner element: on a fast action it is a brief dip, which is honest.

Verified in a browser: "admin" comes back as an inline field error with
aria-invalid set and the headline unchanged, "Ada" goes through.

The hero snippet now mirrors that, and grows a main() carrying the
WithTopicACL the multi-user section had been restating separately. Twelve
referenced symbols, all resolving to the hero. The sections stop
disclaiming — validation and pending point back at lines above instead of
introducing other apps, and the intro no longer needs its caveat.

Full e2e green, greet-wall unit tests green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ
The hero was carrying two lvt-el:*:on:pending/done attributes directly
above a section titled "No attributes" that calls lvt-* an escape hatch.
Leading with the escape hatch argues against the page's own point.

The server-owned form is the one to show, but it cannot go in the wall.
Async's apply callback receives no *Context (api.md:391), so the two
ctx.Publish calls could not run inside it — cross-user fan-out would have
to capture the session and TriggerAction from the closure. And with no
live connection apply never runs at all, so the plain-HTTP path would
drop greetings silently. Rewriting the flagship app that way to display
an idiom is a bad trade.

So the wall's button goes back to plain HTML, and the pending section
owns the topic outright: it now says plainly that this is the one thing
the app above cannot demonstrate, because the wall answers instantly and
has no pending state to render. Variant A leads as the one to reach for —
the flag is a template variable, so the spinner is ordinary Go and
ordinary HTML — and B is named as the escape hatch, for when the Go
should not change, with A's live-session requirement stated against it.

The hero is now free of lvt-* attributes entirely, which is what the
section below it claims. Eleven referenced symbols still resolve to it.

Full e2e green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166MK1arBYbVZq6wfm8EsQZ
A reader asked what `a.record(...)` and the state assignment beside it
were for, which is the snippet failing at the one job it has.

`record` was a name invented for the page that conveys nothing. It stood
for three writes — the throttle stamp, the per-group name Refresh
re-reads, and the append to the shared ring buffer. It is now two calls
named for their effects, `saveName` and `appendWall`, the latter a real
method on the controller.

The state assignment is the line worth explaining and had no comment at
all. It looks redundant next to the two Publish calls below it, and is
not: a publish skips the connection that called it, so without this the
person who clicked would be the only one who did not see their own
greeting. That reason is now on the page.
@adnaan
adnaan merged commit edd471b into main Aug 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant