Landing: show the finished app first, then explain it - #136
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
Publishcalls 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-wallgains 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:required, andGreetcallsctx.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;"admin"), which also stops a visitor posing as the server on a public wall.Verified in a browser rather than asserted:
admincomes back as an inline field error witharia-invalid="true"and the headline unchanged;Adagoes 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
greet; the wall cannot. The paragraph under it names what is missing —sanitize, the map writes behindsaveName, a 20-line cap, a per-session throttle — and links the real file.Asyncto fake one was considered and rejected:applyreceives no*Context(reference/api.md:391), so the twoctx.Publishcalls could not run inside it, and with no live connectionapplynever runs at all — the plain-HTTP path would drop greetings silently.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:
#5A5560is R90 G85 B96, all but neutral, so names rendered as "dark" rather than as a colour. Names are now plum#6B4A7Aand built-in types and literals get amber#7D5226, which also stops Go'sstring/errorreading as string literals. Six hues, each mapping to a distinct token class, all still darker than--lt-metaso the ≤13px contrast floor holds.builtinalso 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 bareinput[name="name"]navigate to standalone app URLs. All five embed paths, bothnojs-frameiframes and.herosurvive the reorder, so the wholeTestSpine*suite passes without modification.Verification
go test ./e2eagainst the pinned v0.3.9 image: green, 161s.examples/greet-wallunit 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.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