Redesign livepeer.org - #93
adamsoffer wants to merge 124 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The path strip ends on a rule and the ladder drew another under it, with dead air between. The ladder's rule dates from when it sat under the hero; the strip has that seat now, so the ladder's goes. Reporting guide (fallback copy; the page is in Notion): no phrase assumes the reader can see the Notion register. The board is shared in the proposal thread and the roadmap links to it; Notion is named outright where access to it is the condition. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Everything between the hero and the contributors strip is now one document in the reading column, set the way a Notion page is set — headings, paragraphs, a numbered list — with the ladder's grid embedded where a page would embed a database. It was three designed sections and read as three pages under one hero. Copy stays in the repo. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Four columns — the path with its "best for" line beneath, who decides, the ceiling, the link — smallest rung first, hairlines between rows, folding to a labelled stack per rung below sm. The two-column grid of cells was the pricing-tier idiom: right for three options weighed against each other, odd for five rungs read in order, and the treasury spanning the width made the top rung a special case. Inside a document a table is the quiet form, and what Notion would show for the database. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A second labelled block with a sentence made the sticky rail 864px tall under a 64px header, so on an 800px viewport its last line sat below the fold until the end of the register. One link under "Not on the roadmap?" brings the rail back to 688px. Contribute and the record's empty states carry the sentence. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This reverts commit 3648f2e.
Sticky under the header, the rail hid whatever fell below the fold once it grew taller than the window — at 800px, the owners' prompt at its foot. Now the sticky offset shrinks by the distance scrolled until the rail's foot meets the bottom edge and grows until its head is back under the header, the way GitHub's and MDN's sidebars move. A rail that fits keeps the plain pin. Re-measured on resize and whenever the rail's height changes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
One or more issues must be addressed before approval.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (10)
app/api/notion-webhook/route.ts:23
- The webhook now assumes
/roadmapis the only Notion-backed surface, but this PR routes the same token through the blog, funding paths, people, organizations, updates, changelog entries, and the reporting guide. Events for those databases (and the record/feed/share-image routes) will keep serving stale cached data because only this path is cleared. Invalidate tags or all affected route patterns, including dynamic records, instead of assuming only the index reads Notion.
app/api/revalidate/route.ts:25 - The authenticated revalidation endpoint repeats the same incomplete
/roadmap-only invalidation. Calling it after an edit to the blog, funding ladder, people, organizations, updates, changelog entries, or guide cannot make those pages or feeds current, and it does not cover dynamic roadmap record routes. Reuse the same tag/path invalidation strategy as the webhook.
app/ecosystem/[slug]/opengraph-image.tsx:16 - The route never reads
slugor the project record, so every project gets the same untitled ecosystem artwork. These are the pages whose project-specific titled cards the PR adds; load the app by slug and render it withrenderTitledCardandapp.name.
app/globals.css:25 - This leaves a second
--color-*token layer in the global stylesheet, contrary to the registry-only theme contract, and the symbol has no consumers anywhere in the repository. Remove this unused legacy token (and its explanatory block) rather than retaining a parallel color namespace.
app/globals.css:477 - These
--color-brand*variables introduce a local token layer outside the registry theme, and all of the newbrandutilities depend on it. The site's design-system contract says the registry theme is the only token layer and semantic registry roles are the source of truth; use an existing registry role or add the needed registry item instead of defining local theme tokens here.
app/organizations/[slug]/page.tsx:25 - This page also sets
dynamicParams = false, so a newly added Notion organization cannot be opened from a newly revalidated owner link until another build. Organizations are read from a live Notion database; allow dynamic params and retain thenotFound()guard for unknown slugs.
app/people/[slug]/page.tsx:19 - This page also sets
dynamicParams = false, so a person added to the Notion people database cannot be opened from a newly revalidated credit until another build. Allow dynamic params and retain thenotFound()guard for unknown slugs.
app/roadmap/[slug]/page.tsx:32 dynamicParams = falsemakes any commitment added to the Notion Roadmap commitments database after the build return 404, even after the revalidated index links to it. That defeats the no-deploy Notion workflow; allow dynamic params and keep the existingnotFound()lookup for unknown slugs.
components/canvas-theme.ts:19- This hard-codes the brand green a second time instead of reading the theme value. The CSS already exposes the brand token and the other canvas consumers read computed theme values, so this can drift from the light/dark theme source of truth and violates the semantic-token-only design rule. Read the CSS token here instead.
components/livepeer-ui/contribute-graph.tsx:260 - When the tab becomes visible, this handler starts the frame loop without checking whether the canvas is still intersecting the viewport. If a user backgrounds the tab while this section is off-screen, returning to the tab causes continuous animation until another intersection event, defeating the 'only while on screen' guard and wasting GPU/CPU time. Track the latest intersection state and require it here.
- Files reviewed: 127/351 changed files
- Comments generated: 4
- Review effort level: Lite
"Ideas start on the forum and land here once they are owned and dated." and "An update every month, and a retrospective at the end." — the sentences said the same in twice the height. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The field sat 10px below the view tabs it opens beside; the rail's top padding now centres it on them. The lone "In progress" box read as loose under no heading; it sits under Status like Health and Workstream. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Under a heading, the lone "In progress" box read as a list missing its other member. Both states the Roadmap view holds are boxes now, on the same rule as the other facets — none ticked is everything — and in the URL as ?state=building,next. Counts leave their own facet out; the health counts drop to zero when only Planned is ticked, since health is a fact about work under way. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
One or more issues must be addressed before approval.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (12)
app/api/notion-webhook/route.ts:103
- This only invalidates the exact
/roadmaproute. The webhook is subscribed to the Notion integration as a whole, so edits to the blog, funding paths, updates/changelog entries, guides, people, organizations, and dynamic record pages can remain on their old route/data cache after an event. Revalidate the shared layout (or explicitly invalidate every Notion-backed route) so the webhook actually provides the immediate refresh described here.
app/api/revalidate/route.ts:25 - This endpoint only clears
/roadmap, but the Notion reader now supplies/blog,/contribute,/changelog,/roadmap/reporting, people, and organization records as well. A successful refresh call after editing any of those databases leaves the affected page stale until its normal cache window or a deployment; invalidate the relevant routes/data tags instead of treating the roadmap as the only Notion surface.
app/changelog/feed.xml/route.ts:112 - Escaping the complete HTML string converts every
<p>,<h3>,<ul>, and<a>into visible text for Atom readers, even though the content element is declared astype="html". Put the markup in CDATA (escaping only]]>) or escape only the text nodes so feed readers render the roundup as HTML.
app/ecosystem/[slug]/opengraph-image.tsx:16 - Every ecosystem detail page now gets this route's image, but it renders the generic ecosystem artwork and never includes the project's name. Shared links therefore show an untitled card despite the page metadata being project-specific; load the slug's app and use the titled-card renderer (and pass the app image if that is the intended design).
app/organizations/[slug]/page.tsx:25 - Organizations are read from the live Notion database, but
dynamicParams = falsemakes a newly added owner page 404 until the next deployment. This breaks links from newly edited roadmap records; allow dynamic params here.
app/people/[slug]/page.tsx:19 - People are read from the live Notion database, but
dynamicParams = falsemeans a newly related person cannot be opened until a deployment regenerates the static params. This prevents the live register from linking to newly added people; allow dynamic params here.
app/roadmap/[slug]/page.tsx:58 - This child
openGraphobject replaces the parent roadmap metadata, including itsimages, but there is no[slug]/opengraph-imageroute to provide a replacement. Roadmap record URLs will consequently lose their share image; add a record image route or explicitly preserve an image in this metadata.
app/roadmap/[slug]/page.tsx:32 - The register is live Notion data when
NOTION_TOKENis set, butdynamicParams = falsemakes any commitment added after the build return 404 until the next deployment. This contradicts the one-minute ISR/live-register behavior; allow dynamic params (or otherwise rebuild the route set on each data change).
app/roadmap/opengraph-image.tsx:20 - Record share images are dynamic for blog, changelog, ecosystem, people, and organizations, but this parent image has no slug/record input and is inherited by
/roadmap/<slug>. Sharing a commitment therefore produces the generic roadmap artwork/title instead of the commitment's title, even though the record route's metadata is specific. Add a dynamic roadmap image route (and its Twitter re-export) or otherwise supply the record title here.
components/canvas-theme.ts:19 - The contribution graph claims to derive its palette from theme tokens, but the green entry is hard-coded here. Any registry/theme change to the brand color will leave the WebGPU graph out of sync with the CSS and other theme-driven surfaces. Read the brand value from the computed token like the neutral colors.
components/livepeer-ui/agent-runtime-preview.tsx:141 - When the card leaves the viewport, this effect cancels the animation but leaves
frameat its last phase. Re-entering starts a new elapsed clock while rendering the stale phase (including a visibledonestate), so the promised replay from the initial typing state does not occur. Reset the frame when inactive.
components/livepeer-ui/contribute-graph.tsx:261 - The visibility handler calls
start()unconditionally when the tab becomes visible. If the graph was already scrolled offscreen before the tab was hidden, this resumes the GPU loop without an intersection event, contradicting the on-screen-only guard and wasting a frame loop until visibility changes again. Retain the current intersection state and gate resume on it.
- Files reviewed: 127/351 changed files
- Comments generated: 1
- Review effort level: Lite
The record saying under way and the post saying finished and judged cannot both be true, so the reader fails the build rather than showing both, the way it does when Shipped and its date disagree. Drafts are exempt: moving a commitment back to In progress is done by setting its retrospective to Draft in the same edit. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The roadmap's intercepting route catches every client-side navigation to /roadmap/<segment>, and under the production router a static sibling gets no precedence over it, so "How to report" 404'd on the preview and rendered only after a refresh. There is no opting a segment out of an interceptor, so it now knows the guides by name (GUIDE_NAMES in lib/guides.ts) and renders one in the RecordSheet, with the expand control to its page; a direct load still gets the page itself. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`display: "block"` on Inter and Geist Mono: text held for the face instead of painting in the size-adjusted stand-in and changing typeface a beat in, which every cold load and hard refresh showed. Inter subset to the Latin ranges with both axes and every feature kept (352KB to 213KB, the italic 388KB to 236KB), so the wait is shorter. The primer's Raleway is as it was. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Vercel's automatic deploy did not run for 140a721. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Stretched, rotated, outlined and recoloured were self-evident, and the sentence beneath them already carries the one rule a designer would get wrong: the mark is black or white, never green. The section is now clear space, minimum size and that sentence. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
One or more issues must be addressed before approval.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 21
Open (73)
The ecosystem frontmatter is contributor-controlled, butapp.url(and the social/maker URLs used… This hostname-only check treats URLs such asjavascript://livepeer.orgas first-party and skips…remarkParseleaves the tree in MDAST, but this pipeline sends it directly to rehype plugins and… This shared Markdown renderer feedsdangerouslySetInnerHTML, but the pipeline enables raw HTML… Organization links are copied from markdown without protocol validation, then parsed and rendered…pathis imported as the filesystem module, but this declaration shadows it for the entire… The workflow runs this checker on pull-request-controlled frontmatter, but every non-self URL is… The fallback behavior described here is not guaranteed when the Discord request hangs:fetchhas… Every failure or invalid-widget path serves this copied invite, so/discordstill depends on a… The explicithrefbranch bypassesisLinkable, so a contributor-suppliedmadeByUrlsuch as… These contributor-edited roadmap links are passed directly to anchors bycommitment-record.tsx,… The ecosystem frontmatter is contributor-controlled, buturland the other URL fields are…org.linkis parsed withnew URL, but its protocol is never restricted before this raw anchor is…relatedlinks are rendered as raw hrefs, while both the markdown and Notion readers only require… The organizationLinkproperty is passed through without validating its scheme, then… These links come from Notion and are returned without a scheme check, thenCommitmentRecord…fileContentshas already been read from a path built directly from the dynamic slug, with no… The Notion HMAC verification token is logged in plaintext, even though it becomes… This component is imported by client components (e.g. components/livepeer-ui/latest-nav.tsx) but… This file is imported by client components (e.g. components/copy-button.tsx) but lacks a "use…
And 53 more that still need to be addressed.
There was a problem hiding this comment.
Copilot review overview
🔵 Needs a closer look
One or more issues must be addressed before approval.
Review effort: Lite
Findings: 21
Open (73)
The ecosystem frontmatter is contributor-controlled, butapp.url(and the social/maker URLs used… This hostname-only check treats URLs such asjavascript://livepeer.orgas first-party and skips…remarkParseleaves the tree in MDAST, but this pipeline sends it directly to rehype plugins and… This shared Markdown renderer feedsdangerouslySetInnerHTML, but the pipeline enables raw HTML… Organization links are copied from markdown without protocol validation, then parsed and rendered…pathis imported as the filesystem module, but this declaration shadows it for the entire… The workflow runs this checker on pull-request-controlled frontmatter, but every non-self URL is… The fallback behavior described here is not guaranteed when the Discord request hangs:fetchhas… Every failure or invalid-widget path serves this copied invite, so/discordstill depends on a… The explicithrefbranch bypassesisLinkable, so a contributor-suppliedmadeByUrlsuch as… These contributor-edited roadmap links are passed directly to anchors bycommitment-record.tsx,… The ecosystem frontmatter is contributor-controlled, buturland the other URL fields are…org.linkis parsed withnew URL, but its protocol is never restricted before this raw anchor is…relatedlinks are rendered as raw hrefs, while both the markdown and Notion readers only require… The organizationLinkproperty is passed through without validating its scheme, then… These links come from Notion and are returned without a scheme check, thenCommitmentRecord…fileContentshas already been read from a path built directly from the dynamic slug, with no… The Notion HMAC verification token is logged in plaintext, even though it becomes… This component is imported by client components (e.g. components/livepeer-ui/latest-nav.tsx) but… This file is imported by client components (e.g. components/copy-button.tsx) but lacks a "use…
And 53 more that still need to be addressed.
Readers: every link from Notion or markdown is held to http(s) (or a site path where the register allows one); every cover to https on cdn.sanity.io, a commitment's required, an upload refused with the reason; Outcome required on Notion rows as on markdown; duplicate changelog periods and a week 53 the ISO year does not have fail the build; ecosystem slugs checked against the catalogue before a path is built, and every ecosystem URL field validated at ingestion; people mapped through one function in both passes; Notion bodies read four at a time; the Discord widget fetch bounded to five seconds. Routes: no `dynamicParams = false` on any Notion-backed route, so a row added after the last build is served on first request; the revalidate endpoint and the webhook clear one shared list of every Notion-rendered address (app/api/notion-surfaces.ts) and the bearer is compared in constant time; titled share cards for commitment records, the reporting guide, ecosystem projects, the submit page and blog categories, a frame of its own for people, and 404s for unknown slugs on the image routes; feed slugs encoded; the URL checker validates first-party paths against the app's routes and refuses non-http(s) and private destinations. Components: rel="noopener noreferrer" on external links, canvas-theme marked client and reading --color-brand instead of a literal, a no-op Tailwind token dropped and the avatar alt made informative; the maker link on an ecosystem page gated by isLinkable. Declined with reasons in the review: the Tailwind v4 syntax Copilot called invalid compiles (checked in the built stylesheet); the registry's badge, button and input need no "use client"; the markdown pipeline has remarkRehype and the ecosystem module has no TDZ. The brand token stays, documented in CLAUDE.md as the one named addition to the registry theme. README says Next.js 16. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The invite served when the widget cannot be read is now NEXT_PUBLIC_DISCORD_FALLBACK_INVITE, validated to the invite shape, with the invite the widget answered on 2026-09-06 as the default for a clone with nothing set. NEXT_PUBLIC_ because lib/site.ts puts it in the header's content, which the client renders too. The ecosystem listing reads ?q= and ?categories= on the server, so a shared narrowed view renders narrowed, and mirrors the filter back into the URL with history.replaceState as it changes. The route is dynamic for it; a markdown catalogue of thirteen entries costs nothing to render per request, and reading the query on the client behind a Suspense boundary would have kept the catalogue out of the prerendered page. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
One or more issues must be addressed before approval.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 5
Open (5)
Dynamic detail pages are not invalidated by layout revalidation · New Unused blog categories still generate successful Open Graph images · New Unpublished roundup periods still generate successful share cards · New Invalid ISO week 53 causes a server error · New Reporting guide is missing from the sitemap · New
Resolved since last review (73)
The ecosystem frontmatter is contributor-controlled, butapp.url(and the social/maker URLs used… This hostname-only check treats URLs such asjavascript://livepeer.orgas first-party and skips…remarkParseleaves the tree in MDAST, but this pipeline sends it directly to rehype plugins and… This shared Markdown renderer feedsdangerouslySetInnerHTML, but the pipeline enables raw HTML… Organization links are copied from markdown without protocol validation, then parsed and rendered…pathis imported as the filesystem module, but this declaration shadows it for the entire… The workflow runs this checker on pull-request-controlled frontmatter, but every non-self URL is… The fallback behavior described here is not guaranteed when the Discord request hangs:fetchhas… Every failure or invalid-widget path serves this copied invite, so/discordstill depends on a… The explicithrefbranch bypassesisLinkable, so a contributor-suppliedmadeByUrlsuch as… These contributor-edited roadmap links are passed directly to anchors bycommitment-record.tsx,… The ecosystem frontmatter is contributor-controlled, buturland the other URL fields are…org.linkis parsed withnew URL, but its protocol is never restricted before this raw anchor is…relatedlinks are rendered as raw hrefs, while both the markdown and Notion readers only require… The organizationLinkproperty is passed through without validating its scheme, then… These links come from Notion and are returned without a scheme check, thenCommitmentRecord…fileContentshas already been read from a path built directly from the dynamic slug, with no… The Notion HMAC verification token is logged in plaintext, even though it becomes… This component is imported by client components (e.g. components/livepeer-ui/latest-nav.tsx) but… This file is imported by client components (e.g. components/copy-button.tsx) but lacks a "use…
And 53 more resolved.
| ["/organizations", "layout"], | ||
| ["/people", "layout"], |
| const { slug } = await params; | ||
| const category = categoryFromSlug(slug); | ||
| if (!category) notFound(); | ||
| return renderTitledCard(ogArt.blog, category, "Blog"); |
| const { period } = await params; | ||
| if (!PERIOD.test(period)) notFound(); | ||
| // The period over the changelog's own art: an entry has no cover of its | ||
| // own, and one is not wanted for a page generated from the register. | ||
| return renderTitledCard( |
| if (wanted && !PERIOD.test(wanted)) { | ||
| return Response.json( | ||
| { | ||
| error: | ||
| "period must be a month (2026-08), quarter (2026-Q3), week (2026-W36) or year (2026)", |
| { url: `${BASE_URL}/blog`, changeFrequency: "weekly", priority: 0.8 }, | ||
| { url: `${BASE_URL}/changelog`, changeFrequency: "weekly", priority: 0.7 }, | ||
| { url: `${BASE_URL}/compute`, changeFrequency: "weekly", priority: 0.8 }, | ||
| { url: `${BASE_URL}/roadmap`, changeFrequency: "weekly", priority: 0.7 }, |



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.Open items
None of these block the merge; each is a content or configuration change rather than code.
agentAppOrigin(lib/site.ts) points atearlyaccess.livepeer.org, the console's own address. The Agent playbooks link is the one product link left onlivepeer.peaceno.de: the console has no playbooks page, and a mockup beats a 404.forum.livepeer.org/or similar rather than a permalink.🤖 Generated with Claude Code