Skip to content

WIP: Redesign livepeer.org - #93

Draft
adamsoffer wants to merge 92 commits into
mainfrom
claude/livepeer-redesign-plan-110a98
Draft

WIP: Redesign livepeer.org#93
adamsoffer wants to merge 92 commits into
mainfrom
claude/livepeer-redesign-plan-110a98

Conversation

@adamsoffer

@adamsoffer adamsoffer commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

🚧 Work in progress — not ready to merge. Opened as a draft for review of direction and shape. See Before this can ship for the blocking items.

Rebuilds the marketing site against Peace Node's Livepeer UI registry. The registry theme is now the only token layer.

Pages

Nine linked pages, all built from the mockup set except where noted:

Page Route Notes
Home /
Agent /agent flagship product surface
Ecosystem /ecosystem /[slug], /submit — markdown-driven catalog
Provide GPU compute /compute one page for running + earning
Token /token
Foundation /foundation
Latest /blog nav says "Latest", URL stays /blog
Brand /brand no mockup — designed from design.md + registry Foundations
Roadmap /roadmap no mockup — built from the requirements doc

/primer stays intact and unlinked with its own scoped legacy slice — the one page deliberately left unmigrated.

Content is split on its shape and how often it changes. Page copy is authored in-repo as typed objects matching the registry's content contracts (lib/site.ts) — versioned and reviewed alongside the design it belongs to. Blog and ecosystem are markdown in content/.

Templated content that gets updated frequently moves to Notion, and the roadmap register is the first of it. It is maintained by people across several organisations who do not open pull requests, and a commitment whose state is a week stale is worse than no page at all. The blog is planned next; scope is those two for now, not a general move of the site into a CMS. So this PR is the pattern as much as the page. lib/notion.ts reads it over plain fetch with ISR at a minute; content/roadmap/*.md is the fallback when no workspace token is present, so a clone still builds. Every record carries an owner, a target and a checkable link, and anything missing one fails the build rather than rendering a card that claims less than it should.

Cleanup

globals.css 1209 → 585 lines. Removed the second --color-* token layer, the Holographik grid, hero classes, .blog-prose, 19 keyframes and 37 custom properties. Chart/sidebar/radius tokens are kept — unused today, but they're the registry's theme contract.

Deleted ~73 files: components/home/*, components/legacy/*, components/ecosystem/* (superseded by components/livepeer-ui/*), the PascalCase components/ui/* visuals, lib/constants.ts, and the five app/use-cases/* routes — redirects in next.config.ts already served those URLs, so the pages were unreachable.

Share images

Rebuilt on the registry's own og item. / renders the brand card verbatim — the lockup centred on #000000. Every other page renders the same canvas with a page title, from one shared lib/og.tsx. Two bugs fixed along the way:

  • Only the root had a twitter-image, and metadata files cascade — so /foundation and /blog served their own og:image but the root's Twitter card. Every segment now has a re-export beside its opengraph-image.
  • Ecosystem project pages had no share image at all: their generateMetadata declares an openGraph object without images, which drops the inherited card. All 12 projects now get their own titled card.

Incidental fixes

  • app/not-found.tsx styled its link text-green — green as an affordance colour, which the design system explicitly rules out. Rebuilt on the registry type scale.
  • sitemap.ts advertised the five dead /use-cases URLs and never listed /agent or /compute. Rewritten, with ecosystem projects added.
  • Instrument Serif was downloaded on every page load with zero consumers. Removed.
  • package-lock.json deleted. Six deps had been added via npm in a pnpm-pinned repo, leaving pnpm-lock.yaml stale — pnpm install --frozen-lockfile was failing, which would have broken the Vercel build. Lockfile regenerated and verified.

Verification

pnpm typecheck, pnpm lint (0 warnings) and pnpm build (130 static pages) all clean, against both the Notion register and the markdown fallback. Pages verified in light and dark at 390px, sm, md and wide desktop.

Before this can ship

  • agentAppOrigin still points at livepeer.peaceno.de (lib/site.ts) — this feeds the most prominent CTA on every page. One edit, needs the real URL.
  • Blog art hotlinks Peace Node's Sanity CDN — confirm permanence or vendor the assets.
  • Placeholder people in the register. Names, portraits and rosters are stand-ins except Doug Petkanics. Replacing them is a Notion edit, not a deploy.
  • Commitment links mostly cite venue roots — 11 of 19 point at forum.livepeer.org/ or similar rather than a permalink.
  • The Notion webhook is not subscribed yet — needs an integration admin and the production domain. Until then the one-minute window is what carries edits to the site, which is sufficient but serves one stale reload.

🤖 Generated with Claude Code

Rebuilds the marketing site against Peace Node's Livepeer UI registry. The
registry theme is now the only token layer; the old hand-rolled --color-*
system, the Holographik grid, .blog-prose and 17 bespoke keyframes are gone.
globals.css goes 1209 -> 585 lines.

Eight pages: home, /agent, /ecosystem (+ detail, submit), /compute, /token,
/foundation, /blog, /brand. /primer stays intact and unlinked with its own
scoped slice. Content is authored in-repo as typed objects matching the
registry contracts (lib/site.ts) plus markdown for blog and ecosystem — no CMS.

Share images are rebuilt on the registry's own og item: / renders the brand
card verbatim (lockup centred on black), every other page the same canvas with
a page title. Each segment carries a twitter-image beside its opengraph-image,
because metadata files cascade and a segment without one serves the root's
Twitter card.

Also fixes package-lock.json: six deps had been added via npm in a
pnpm-pinned repo, leaving pnpm-lock.yaml stale and
`pnpm install --frozen-lockfile` failing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
website Ready Ready Preview Aug 28, 2026 1:02pm

Request Review

Two notes were carrying stale expectations that would have sent the next
person looking for work that isn't there: /brand was marked as awaiting CD
review, and the Foundation page's dropped content read as migration loss
worth restoring from git. Both were deliberate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@adamsoffer adamsoffer changed the title WIP: Redesign livepeer.org on the Livepeer UI design system WIP: Redesign livepeer.org Aug 8, 2026
`hover:bg-card` reads as a state change in dark, where --card is
oklch(0.205) over a black background — a 23-byte lift. In light, --card and
--background are both oklch(1 0 0), so the fill painted white on white and
there was no hover at all.

Both card grids now use an alpha over foreground, which is relative to
whatever the page is rather than a fixed surface: measured at a 20-byte lift
in both themes, matching what card gave dark. It is also the value already
used for the header nav items and the hero's Discord button.

Same failure mode as the login dropdown painting bg-accent on a bg-secondary
panel — two tokens that happen to hold identical values in one theme.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous fix swapped `card` for a foreground alpha, which removed the
white-on-white case but picked one shared value for both themes on the
reasoning that equal sRGB byte deltas read equally. They don't — oklch L is
steep near black and flat near white, so at 0.08 dark lifts ΔL 0.191 while
light manages 0.060, and light still read as having no hover at all.

Splits the alphas: light 0.15 (ΔL 0.112), dark unchanged at 0.08. These cards
carry no border, so the fill is the only thing that materialises them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0.15 read as too dark. Bisects the two rejected ends — 0.08 (ΔL 0.060) was
absent, 0.15 (ΔL 0.112) was heavy — landing on 0.12 at ΔL 0.087. Dark is
untouched at 0.08.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
0.12 still read as too dark. Light now sits at ΔL 0.072, one step above the
0.08 that read as absent. Dark is untouched at 0.08.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The registry's ecosystem-card signals hover twice: a faint `muted/50` fill and
an arrow going muted-foreground -> foreground. An earlier pass here dropped the
arrow — the registry card links off-site so its icon is an external arrow,
while ours goes to a local detail page — and then swapped the fill to `card` to
compensate. That is what broke light mode, where `card` and `background` are
both oklch(1 0 0), and why every attempt to fix it by darkening the fill alone
read as too heavy: one signal was doing two signals' work.

Brings the arrow back as ArrowRight and restores the registry fill for light.
Dark keeps `card`, because `muted/50` there is rgb 7 on pure black and
disappears. Same treatment applied to the /brand resource cards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Favorit Pro came off all seven page heroes plus the blog card titles and
article headline, leaving Inter as the one voice for headings. The display
scale utilities only ever set size, weight and tracking — they never implied a
typeface — so text-display-* in font-sans is the normal case, and font-display
now survives only in the /brand type specimen (where the face is the subject)
and the registry's mobile nav.

Two weights moved with it, because Favorit's 300 does not translate: blog card
titles go to font-medium at 20px, where Inter's 300 stopped out-weighing the
body copy, and the article headline follows at font-medium so a title carries
the same weight on the card and on the article.

Replaces the /brand clear-space diagram with the symbol construction drawing
recovered from the old token page's specimen card, which the redesign cutover
deleted along with app/token/TokenPageContent.tsx. The old diagram asserted its
rule — a dashed box labelled "= symbol width" around a mark floating in empty
page, with no measure drawn — and sat in a grid cell stretched to 850px by the
rules list beside it. The recovered drawing shows the geometry instead: a
lattice whose every line passes through a real square edge, tick rows carrying
the actual X and 5/6X measures, and the +6u optical correction drawn as a
labelled delta between the optical and geometric centres.

Re-aimed from LPT to the mark: the chain label and live staking figure are
gone, which also drops the stats prop, so /brand no longer reads the subgraph
or needs THEGRAPH_API_KEY. Its corner annotations are hidden below sm — they
are fixed-size HTML while the footer wordmark scales with the viewBox, so at
375px the footer's flanking rules struck through them.

Foundation hero copy is now "Advancing the world's open inference network."

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The panel's cards each carried their own hover:bg-*, so moving between two of
them cross-faded: the one you left dimmed while the one you arrived at lifted,
and two adjacent cards fading in opposite directions reads as a blink rather
than a movement. One highlight now travels between them, the same device the
login dropdown uses vertically.

Both axes are tracked. The grid is 3 columns at lg and 4 at xl, so a four-item
group like Network wraps: along a row it slides laterally, to the row below it
travels down as well, and width and height come along because wrapped rows are
not always the same size.

Position is written to the node rather than held in state, and the transition
is declared unconditionally. A CSS transition only runs when the property was
already transitionable in the *previous* computed style, so a class that turns
the transition on in the same commit that moves the element makes it teleport —
React renders both together, so the declarative version could not animate at
all. Setting the style directly on an element whose transition is already
established does. First appearance still places rather than travels, or the
highlight flies in from the panel's top-left corner.

Also fixes two things found underneath it. The trigger row's hoverable box was
16px of text inside a 64px header, with ~25px of dead space below it, so the
menu closed on small vertical drift; padding makes the hit area the full row
and a negative margin keeps layout unchanged. And the panel's first open
targeted height "auto" and was then re-targeted to a measured pixel value
mid-animation, which restarts the animation — hence a stutter on the first
hover and never afterwards. It now holds "auto" until the open settles.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every card is now one of Peace Node's stock images with the mark over it. Page
cards centre the lockup and say nothing else — the art is what tells /agent from
/token in a feed, and the platform prints the title and hostname as text beside
the image anyway. Blog cards take the post's own art with the wordmark in the
corner and the headline over a scrim weighted to the foot. Wordmark rather than
the lockup there: the symbol is six small squares, and at corner size they land
around 4px each and turn to mush.

Fixes a real softness while it is in here. The art was being served through the
`?fm=webp&q=82` in the blog frontmatter, which measured less than half the
detail of the original on a Laplacian sharpness test — the grain that makes
these read as photographs was being compressed flat. Cards now pull
`fm=jpg&q=95` cropped by the CDN to exactly 1200 × 630, so Satori never
resamples: the same measure roughly doubles, at a fifth of the source PNG's
weight. Blog metadata drops its explicit `images`, which would otherwise beat
the file convention and keep serving the bare art.

The scrim is radial rather than a flat wash, and aims at 3:1. A flat tint heavy
enough to carry white over the brightest of these measured 2.2:1, and the wash
that fixed it drained the darker images and left a visible smudge on the bright
ones. Darkening only the middle clears 3.6:1 at worst with the art intact at the
edges. 3:1 is the bar that applies — the lockup is a graphic, so WCAG 1.4.11
governs, not the 4.5:1 written for body text.

Also puts the gutter and max-width on one element on /brand, /blog and
/ecosystem. Split across two, max-w-page bounds the content box instead of the
padded box, so those three ran 40px wider each side than the header, the footer
and the section rules they sit between.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reverts the shared sliding highlight: the panel's cards go back to their own
hover fill, which lifts instantly instead of moving between them. Removing it
rather than switching off its transition takes the grid wrapper, the imperative
position tracking and the handler props threaded through LivepeerOrgNavItem
with it, so nothing is left dormant behind a flag.

Keeps three things from that work, none of which were about the animation: the
trigger row's hit area stays the full 64px header rather than a 16px strip of
text, the panel still holds an "auto" height target until the open settles so it
does not re-target mid-animation, and the card fill stays a fraction of the
foreground — bg-muted sat too close to the bg-background/95 panel to register in
either theme.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The card most people see now carries the brand colour. Picked by measuring
rather than by eye: every asset in the set was sampled for pixels in the green
hue band and for pixels near the brand hue, and this frame won by a wide margin
— 48% green and 15% brand-green, against 18% for the runner-up and 8% or less
for everything else.

It was /brand's card, so the two swap rather than one being duplicated, and the
eight page cards stay one picture each. Both still clear the contrast bar for
the lockup: 4.44:1 on home, 3.67:1 on /brand.

It is also the frontmatter art for the Foundation announcement, so the home card
and that post's card share a source image. They read differently — the post
carries a headline, the corner wordmark and a much heavier bottom scrim — and
the alternative was a home card with almost no green in it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The job has been failing on every push since the redesign, for two reasons both
introduced by it. It runs `npm ci` behind `cache: npm`, which needs the
package-lock.json deleted when the lockfiles were reconciled — the repo pins
pnpm via packageManager and ships pnpm-lock.yaml. And it triggers on
data/ecosystem.json, which is the catalogue's old shape; the data moved to
markdown in content/ecosystem, so the guard had quietly stopped firing on the
files it exists to check.

The script itself was fine: it already reads content/ecosystem, and passes
locally at 12 URLs checked, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Livepeer Agent joins the catalogue, written against the contributor template
with copy drawn from the /agent page and lib/agent-capabilities.ts rather than
invented — the MCP URL, the modality list and the editing tools are all real
entries there. Made by the Livepeer Foundation, linking to /foundation.

Streamplace ships a new mark, replacing a 250x250 4-bit PNG with 412 bytes of
vector that stays crisp at the detail page's larger size.

Both marks are a single ink, which the tile could not previously accommodate.
The default tile is theme-aware — oklch 0.269 dark, 0.97 light — so a black mark
dies on one and a white mark on the other, and the only escape was logoBg, a
fixed plate that is wrong in whichever theme it was not chosen for. New optional
logoMonochrome inverts the mark under .dark instead, keyed to the site's own
class so it follows the theme toggle rather than the OS. logoBg stays for its
real case: The Lot Radio's dark logo genuinely needs a white plate.

Three fixes found along the way:

Every maker credit on the site was a broken link. MetaRow wrapped any non-email
value in an anchor, and "Made by" passes a company name — Daydream's rendered as
href="Livepeer Inc", resolving to /ecosystem/Livepeer%20Inc. Prose now renders as
text, and a new optional madeByUrl supplies a real destination when there is one.

The website line printed the host and dropped the path, so an entry living at
livepeer.org/agent was shown as bare livepeer.org — pointing somewhere it does
not go. It renders host plus path now, and the field is renamed hostname ->
displayUrl to match what it holds.

The URL checker cannot verify a page that ships from this repo: a PR adding
/agent and an entry pointing at it fails against production, where that page
does not exist yet. Same-site URLs are skipped and reported, rather than failing
a check that exists to catch third-party links rotting.

Embody is credited to DeFine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Open a commitment, follow its owner, click outside: closing revealed the
commitment again. The reader dismissed the surface, not the record inside it,
and stepping back through the chain replays a history they were never
navigating — from their side one panel changed its contents.

A panel that replaced another now closes to the page it overlays. One that
opened over the register still steps back, which is exactly right and leaves
no entry behind.

Counting the chain and walking back through it was the other option, and it
breaks the moment anyone uses the back button part way along — the count keeps
rising while the history shrinks. The page a panel sits over does not move, so
that is what it closes to.

Verified through the sequence that prompted it, and that a single panel still
closes to /roadmap by Esc and by the close control.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A deploy died on ETIMEDOUT prerendering /roadmap. Nothing was wrong at either
end but the connection: one socket, one build, gone.

A build reads Notion at least 24 times — three database queries plus a body
for every commitment and organisation — and each was a bare fetch with no
timeout and no second attempt, so any one of them could take the deploy with
it. Three attempts now, with backoff, and a 15s timeout per attempt so a hung
connection fails rather than hanging the build.

Only what a retry can fix: a transport error, a 429, a 5xx. A 401 or a "Could
not find database" is a configuration mistake, and repeating it three times
only delays the message that says so.

Failing loudly on a real outage stays deliberate. A build that quietly served
the month-old markdown fallback would publish a roadmap that looks current and
is not, so the error after three attempts says exactly that.

Verified by pointing the client at a black-holed address: nine requests, three
attempts each, TimeoutError from the abort signal, and the wrapped message a
build log would carry. A bogus token still fails on the first try with
Notion's own "API token is invalid".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Search, a state toggle, a heading and three workstream rows sat between the
headline and the first card — a screen of instruments above anything they
operate on, and the register itself below the fold. One line now until it is
asked for, and the first commitment is visible on arrival.

Only below lg. From lg up the rail has a column of its own and costs the
register nothing, so there is no toggle and nothing to open.

Two things the collapsed state has to say. The button carries a count, so a
filter that is shortening the register cannot hide behind it. And it starts
open when something is already filtering, which is what a shared filtered URL
does — a short register under a closed panel reads as a short register, and
the reason should not be a tap away.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… parent

The rows under "On the roadmap" were clickable and said nothing about it.
They take the tint now, bleeding past the text column by the 12px they are
padded so it does not read as a box drawn around the title, and the title
takes an underline — which is what the pointer is actually aimed at.

Full accent, not the /40 the cards use. Those sit on `card` inside a bordered
box; these sit on the page, where 0.97 at 40% over white measured a 1% shift:
present in the computed style, invisible to anyone. Checked in both themes
under a real pointer, since a synthetic event does not raise :hover.

And the back link is gone. A commitment lives at /roadmap/<slug> and the
register really is its parent, so naming it is true there. An organisation is
a sibling route that happens to be reached from a card, and a link home to the
roadmap claimed an ancestry it does not have.

Nothing is lost by dropping it. It only ever rendered on the full page — the
overlay has a close control — and this page still links the roadmap where that
is honest, on every row of the section above.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…a change

Seven files and no README. The register next door has one precisely so nobody
edits a fallback thinking they are editing the site, and this directory has
been sitting there without the same warning since the day it was added.

Also the rules the reader enforces and nothing else states in one place: the
filename has to match the slugified name, because a commitment's owner is
linked by that slug; covers are cdn.sanity.io only; a logo or an avatar naming
a file nobody committed fails the build rather than rendering a hole.

And what is deliberately absent — what each body owns is derived from the
register, never stored here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A credited face went to the forum where a handle existed and nowhere at all
where one did not — nine of the ten people on this register were dead text,
and the one that led anywhere led off the site.

Every person has a page now, on the same record-and-sheet treatment a
commitment and an organisation already use: a face opens it over the register,
a shared link opens it as a page, and both render one PersonRecordView. The
forum handle is a row on that page rather than the only thing a name could do.

A bio is the page body, and optional on purpose. A page without one says "No
bio yet", the way a commitment with no write-up does. These are real people:
an invented bio is a biography of someone who may not exist, published under
their own face, and it reads as true to whoever finds it. Only Doug's is
written, because only Doug's is attributable.

What a person worked on is derived by filtering the register — contributed and
accountable-for kept apart, being different claims — so nothing here stores a
second copy of a roster that a commitment already owns.

Retired with it: the push-to-record fallback an unlinked face needed, and the
href Faces took only to feed it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The portrait was 48px under a landscape banner, which is the wrong way round
for a page about a person: they have a face, and the cover was chosen so it
would not compete with one. It is 112px now, lifted over the banner's lower
edge with a ring in the page colour. An organisation's mark gets the same
treatment, square rather than round because a circular crop takes the corners
off artwork drawn to have them.

`relative` on both is load-bearing. RecordCover is positioned and the lifted
element was not, so the banner painted over it and cropped the top half —
a positioned sibling wins whatever the document order says.

Two ways to reach someone, added: an X handle and an email, each a row and
each rendered only when set.

They are not the same kind of fact. A handle is an identity a person chose to
publish, and the rule is the forum's — never guess one, because a plausible
handle belongs to a real stranger. An address usually was not published by
anyone, and this one becomes a mailto that will be scraped within days, so it
is set only for someone who has agreed to be reachable at it in public. Both
field descriptions and the skill say so.

Doug's X handle is the one the blog already bylines him with. Rich's email is
one he gave.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
He was carrying placeholder-3.jpg, which is why he read as one of the invented
rows. He is not: a real address at livepeer.foundation and a photograph he
supplied.

Taken from the underlying S3 object rather than the proxied Notion URL, so it
is the 800x800 original rather than a 280px thumbnail, and resized to 400 —
sharp at 2x for the 128px the page renders and a third the bytes.

Three places carried the old filename: his own row, the Foundation's roster,
and the 2.0 validation record. That repetition is exactly what the People
relation removes in Notion, where his portrait is stated once — the markdown
fallback embeds copies, so it is the one place a face can still drift.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…dles

Doug was committed at 96x96, from before a person had a page — it renders at
128 now and was visibly soft. His forum avatar is the same man at 288, which
is as large as Discourse holds it: the size segment in that URL is a request,
not a promise, and 288, 500 and 1000 all return the same 55KB file. Taken as
the real thing rather than upscaled into fake resolution.

Rich gets honestly_rich on both the forum and X. The forum one is confirmed
the way the skill says to confirm it — /u/honestly_rich.json answers 200 —
rather than trusted because it looks right.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eighty pixels of nothing sat between the two. The padding was the room a back
link occupied, and these pages lost theirs when an organisation and a person
stopped claiming the roadmap as their parent — the allowance stayed behind
after the thing it was for had gone.

The site header is sticky and stays in flow, so a banner can start directly
under it and nothing needs to clear anything. Measured at 0px on both.

The padding survives where there is no cover, or a title would butt into the
header. Nothing in either table is in that state today, which is exactly why
it is worth keeping rather than deleting.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The same cover was already edge-to-edge in the slide-over and inset on the
page, so expanding a record shrank its banner into a rectangle floating in the
margin. The page was the odd one out against its own overlay, and being flush
to the header but inset horizontally is what made the gap read as a mistake.

Full-bleed on all three record types. A commitment, an organisation and a
person are three views of one kind of record and had drifted to two banner
treatments between them; the reading column is unchanged at 46rem, and the
banner simply steps outside it.

Taller as the viewport widens — 160, 224, 256, 288. The art is 1456x816, and
a fixed band stretched across a wide display crops a 16:9 composition to a
letterbox.

`sizes` is now the caller's to state, because one value cannot serve both
contexts: left at 46rem the browser fetched a 736px file for a 2560px banner.
Pages ask for 100vw and get w=3840; the three sheets keep the panel hint and
get w=1920.

Organisations always render a mark. Six of the seven have no logo committed,
and rendering nothing left a title floating under a banner while the one with
a mark looked like a different template. Two-letter initials, because three of
these bodies begin with "Livepeer" and a single letter drew the same "L" on
the Foundation, Inc and the Treasury.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The back link was the only thing between the header and the cover, and the gap
it left there was the last difference between this page and the other two.

It moves below the banner, into the reading column, aligned with the title it
sits above. It once read as a caption down there — but that was when it said
"Roadmap". "All commitments" behind a left arrow is plainly a way out, not a
label for the picture above it.

Measured: 0px from the header, full-bleed, link and title sharing a left edge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
adamsoffer and others added 5 commits August 27, 2026 19:18
Scrolling down a person's page and clicking one of their commitments opened
that commitment halfway down. The rows carried scroll={false}, which is right
in the panel — the register behind it is what the reader was in the middle of,
and the link swaps the panel's contents rather than going anywhere — and wrong
on a page, where it is a real navigation and the destination inherits whatever
offset they happened to be at.

The same component renders in both places, so it now takes the context rather
than guessing: the three intercepting routes pass `overlay`, the pages do not,
and every link out of a record follows it. That covers the commitment rows on
a person and an organisation, a person's affiliation, and a commitment's owner
— all of which had the same bug waiting.

Measured both ways: from a page, scrolled to 665, the commitment opens at 0.
From the register at 392, opening a person and then following a commitment
from inside the panel leaves it at 392.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two things about a record page.

Reloading one dropped the reader into the middle of it. Opening a record from
the register deliberately does not scroll — the panel slides over the list the
reader was in the middle of — and the cost is that the new history entry keeps
the register's offset, because that is what "do not scroll" means to the
browser. Reload, and it restores that offset onto a document with entirely
different content.

The page now corrects it on mount, twice a frame apart: scroll restoration is
not guaranteed to have finished when effects run, and a single call can land
before the browser's own and be undone. Only the pages do it — the
intercepting routes render the panel and never mount it, which is what keeps
the register still while it is open.

And the cover was too tall. These are Notion page covers, so Notion is the
reference: measured off one, its cover runs about 0.165 of the viewport width
against our 0.206. The steps drop from 160/224/256/288 to 128/160/192/224,
which lands at 0.163 — it reads as a page header now rather than a banner.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three names for one field: the database called it Accountable, the record
rendered it as Contact, and neither is the word anyone says. "Contact" also
suggested a mailbox rather than someone answerable, and a reader who wanted to
find that field in Notion could not.

Lead is both things at once — accountable for the commitment, and the one to
contact about it — which is what it has always meant in practice. It pairs
with Owner without a glossary: the organisation is the Owner, the person is
the Lead.

Renamed everywhere it is named: the Notion property, its reverse relation on a
person (now Leading), the field the readers look for, the frontmatter key in
the markdown fallback, the row on a record, and the section on a person's page
beside Contributing to.

The Notion rename is the part that fails silently — the reader would have
found no Lead property and quietly dropped the value on every record. Checked
after: Anjali still leads the delegator UX analysis, and it renders.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Scrolling to the top on mount was never the deciding step. The browser
holds scroll restoration until the document is tall enough to honour it,
which on these pages is after hydration and after the cover loads — so it
arrived after the correction and undid it. Opening a record deep in the
register and reloading still landed mid-page.

Turn restoration off for as long as a record page is mounted, and back on
when it goes: the register behind it is a list whose position is worth
keeping, and the flag is global.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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