WIP: Redesign livepeer.org - #93
Draft
adamsoffer wants to merge 92 commits into
Draft
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
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>
`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>
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>
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>
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.
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:
//agent/ecosystem/[slug],/submit— markdown-driven catalog/compute/token/foundation/blog/blog/brand/roadmap/primerstays 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 incontent/.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.tsreads it over plainfetchwith ISR at a minute;content/roadmap/*.mdis 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.css1209 → 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 bycomponents/livepeer-ui/*), the PascalCasecomponents/ui/*visuals,lib/constants.ts, and the fiveapp/use-cases/*routes — redirects innext.config.tsalready served those URLs, so the pages were unreachable.Share images
Rebuilt on the registry's own
ogitem./renders the brand card verbatim — the lockup centred on#000000. Every other page renders the same canvas with a page title, from one sharedlib/og.tsx. Two bugs fixed along the way:twitter-image, and metadata files cascade — so/foundationand/blogserved their ownog:imagebut the root's Twitter card. Every segment now has a re-export beside itsopengraph-image.generateMetadatadeclares anopenGraphobject withoutimages, which drops the inherited card. All 12 projects now get their own titled card.Incidental fixes
app/not-found.tsxstyled its linktext-green— green as an affordance colour, which the design system explicitly rules out. Rebuilt on the registry type scale.sitemap.tsadvertised the five dead/use-casesURLs and never listed/agentor/compute. Rewritten, with ecosystem projects added.package-lock.jsondeleted. Six deps had been added vianpmin a pnpm-pinned repo, leavingpnpm-lock.yamlstale —pnpm install --frozen-lockfilewas failing, which would have broken the Vercel build. Lockfile regenerated and verified.Verification
pnpm typecheck,pnpm lint(0 warnings) andpnpm 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
agentAppOriginstill points atlivepeer.peaceno.de(lib/site.ts) — this feeds the most prominent CTA on every page. One edit, needs the real URL.forum.livepeer.org/or similar rather than a permalink.🤖 Generated with Claude Code