Skip to content

Redesign livepeer.org - #93

Open
adamsoffer wants to merge 124 commits into
mainfrom
claude/livepeer-redesign-plan-110a98
Open

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

Conversation

@adamsoffer

@adamsoffer adamsoffer commented Aug 8, 2026 •

Copy link
Copy Markdown
Collaborator

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.

Open items

None of these block the merge; each is a content or configuration change rather than code.

  • agentAppOrigin (lib/site.ts) points at earlyaccess.livepeer.org, the console's own address. The Agent playbooks link is the one product link left on livepeer.peaceno.de: the console has no playbooks page, and a mockup beats a 404.
  • 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

@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
website Ready Ready Preview Sep 23, 2026 7:00pm UTC

Request Review

@adamsoffer adamsoffer changed the title WIP: Redesign livepeer.org on the Livepeer UI design system WIP: Redesign livepeer.org Aug 8, 2026
adamsoffer and others added 2 commits September 14, 2026 15:22
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

adamsoffer and others added 4 commits September 14, 2026 15:38
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>
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 /roadmap is 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 slug or 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 with renderTitledCard and app.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 new brand utilities 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 the notFound() 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 the notFound() guard for unknown slugs.
    app/roadmap/[slug]/page.tsx:32
  • dynamicParams = false makes 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 existing notFound() 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

Comment thread lib/blog.ts
Comment thread lib/blog.ts
Comment thread scripts/check-ecosystem-urls.mjs Outdated
Comment thread lib/period.ts
adamsoffer and others added 2 commits September 14, 2026 16:16
"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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

adamsoffer and others added 3 commits September 14, 2026 16:22
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 /roadmap route. 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 as type="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 = false makes 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 = false means 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 openGraph object replaces the parent roadmap metadata, including its images, but there is no [slug]/opengraph-image route 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_TOKEN is set, but dynamicParams = false makes 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 frame at its last phase. Re-entering starts a new elapsed clock while rendering the stale phase (including a visible done state), 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

Comment thread components/livepeer-ui/ecosystem-detail.tsx

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

adamsoffer and others added 2 commits September 15, 2026 15:14
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

adamsoffer and others added 2 commits September 15, 2026 16:49
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

adamsoffer and others added 3 commits September 15, 2026 17:36
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 High severity · 42 Medium severity · 10 Low severity

Open (73)

And 53 more that still need to be addressed.

Comment thread app/ecosystem/submit/page.tsx
Comment thread app/roadmap/reporting/page.tsx
Comment thread lib/guides.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

One or more issues must be addressed before approval.

Review effort: Lite
Findings: 21 High severity · 42 Medium severity · 10 Low severity

Open (73)

And 53 more that still need to be addressed.

adamsoffer and others added 2 commits September 23, 2026 14:42
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Medium severity

Open (5)
Resolved since last review (73)

And 53 more resolved.

Comment on lines +30 to +31
["/organizations", "layout"],
["/people", "layout"],
Comment on lines +27 to +30
const { slug } = await params;
const category = categoryFromSlug(slug);
if (!category) notFound();
return renderTitledCard(ogArt.blog, category, "Blog");
Comment on lines +15 to +19
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(
Comment on lines +41 to +45
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)",
Comment thread app/sitemap.ts
{ 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 },

This branch was successfully deployed

1 active deployment
Preview — 2863035d Deployed Sep 23, 2026 by vercel[bot]
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.

2 participants