Prerender the site, say what the App can access, and measure the funnel
The landing page shipped as one line of HTML, "Loading…", until its script ran. Google runs the script; Bing, link unfurlers and the LLM crawlers mostly do not. The release notes were worse off: they lived under #/blog, and a fragment never reaches a crawler, so none of them could be indexed at all.
The viewer build now runs the page's own script in jsdom once per route and writes the result into viewer/dist/, which wrangler serves: /, /blog/, one page per release at /blog/<version>/, and /installed/. Each gets its own title, canonical and structured data, and the sitemap is generated from the release list. Old #/blog links redirect. A <head> flag hides the prerendered landing page on PR routes, so a guided-review link does not flash the marketing page while the document loads. The build fails on a page-script error or an empty #app, which is the failure this step exists to prevent.
The landing page now leads with what a reviewer gets, then names the six parts of a guided review with excerpts from the example document. The install is three steps, because the App alone posts "No Intent Document" on every pull request. A Permissions and data section sits under the App's button, each claim checked against the App's own record on GitHub (checks:write, contents:read, metadata:read, pull_requests:write; pull_request events) and the worker source. The footer's "read live from GitHub in your browser" was not what happens, and is corrected.
/installed/ is the target for the App's Setup URL: GitHub returns people there after an install, and it tells them the step the App cannot do for them, registering the MCP server.
Funnel events go to Clarity and to GA4, both gated on reviewassist.dev and never loaded on a PR route, because a guided review's address names the repository. Pasting a review link into an open tab only changes the hash, so a capture-phase listener switches both tags off and reloads before the PR view renders. GitHub's installation_id is stripped from /installed/ before either tag can read it, and a page counts as viewed once, not on every hash change. The Worker reports the steps no browser sees (App installed or removed, repositories changed, a review posted, a pull request with no document or an unparseable one) over the Measurement Protocol: counts and GitHub's own settings only, with each installation behind an HMAC keyed from SESSION_SECRET. Not the GA API secret, which Google receives in every request; with it and the small id space the pseudonym could be reversed. Nothing is sent unless GA_API_SECRET is set, so self-hosted copies stay silent.
Release notes for 1.0.1 to 1.0.5 are added; the site stopped at 1.0.0.
Two things a real browser found: a manual theme choice is now saved and re-applied before first paint, since the release notes are separate pages rather than views of one; and richHtml sets code spans aside before emphasis, so a glob like intent-*.md keeps its asterisks instead of pairing with the next one and italicising everything between.
Also carries the demo fixes that were already uncommitted in the tree: the demo pauses off screen and under the pointer, no longer scrolls the page to itself on every dwell, and holds its height so stops do not reflow the page.
Also
- •Add Review Assist blog and architecture article.
- •Split blog from releases and add conversation spine article.
Commits for this release: v1.0.5…v1.0.6