Skip to content

Redesign livepeer.org - #93

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

Redesign livepeer.org#93
adamsoffer wants to merge 61 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 is the console's Vercel preview (lib/site.ts) — livepeer-console.vercel.app, not its final address. It feeds the most prominent CTA on every page, so it still needs one edit before launch. 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 9, 2026 10:05pm 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

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.

🔵 Needs a closer look

There are a few correctness/security issues in newly introduced route metadata and auth handling that should be addressed before approval.

Review details

Suppressed comments (3)

Previously missed (1) — in code that hasn't changed since the last review.

app/ecosystem/submit/page.tsx:47

  • This route sets metadata.openGraph/metadata.twitter without images, and unlike most other top-level pages it has no app/ecosystem/submit/opengraph-image.tsx. In Next metadata merging, that typically drops the inherited share image entirely, so /ecosystem/submit may render without an OG/Twitter card image.

app/api/revalidate/route.ts:44

  • The bearer token check uses a normal string comparison (offered !== secret). Since this is an auth gate, prefer a timing-safe comparison to reduce information leakage via timing side-channels.
    components/canvas-theme.ts:12
  • This module calls getComputedStyle(document.documentElement) and will throw if it’s ever imported + executed in a server context. Marking it as a client module prevents accidental server imports and makes the DOM dependency explicit.
  • Files reviewed: 127/337 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

adamsoffer and others added 2 commits September 9, 2026 17:05
After Linear's sidebar: one line per event, newest first, with the
health as the icon and the text a click away in a native <details>. The
record's own milestones — committed, shipped — sit in the same log
around the updates, so the whole history of a commitment reads top to
bottom in one column. The latest update keeps its card above the
write-up and appears in the log as well; a log missing its newest entry
is not one.

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.

🟡 Changes recommended

There are confirmed UI/feed correctness issues in the new tooltip styling and Atom feed URL construction that should be fixed before merge.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

app/blog/feed.xml/route.ts:43

  • Similarly, the author <uri> is built from post.author.slug without URL encoding. This can break the XML (or produce an invalid link) if the slug contains characters outside the safe path set.
  • Files reviewed: 127/337 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +27 to +35
const items = posts
.map((post) => {
const url = `${SITE}/blog/${post.slug}`;
return [
" <entry>",
` <title>${escape(post.title)}</title>`,
` <link href="${url}"/>`,
` <id>${url}</id>`,
` <updated>${post.date}T00:00:00Z</updated>`,
Health is colour-coded the way Linear's is — green, yellow, red, and a
hollow grey ring for nothing said. The one deliberate exception to
green-is-not-a-status-colour, beside the In progress dot it sits next
to; no token is added: green is the brand colour with the roadmap's
light-mode mix, red is destructive, and yellow is the oklch point
between the two.

The Health row is back beside Status, with the date the word is good as
of. It went when the latest-update card arrived, on the grounds that
Linear has no such property, and that was a step too far: a reader
scanning the facts looks for the one-word answer there.

The write-up now comes straight after the properties, and the latest
update and the activity log sit together beneath it. Above the write-up
the card left the record's own text sandwiched between two kinds of
update; Linear gets away with that order because its activity is a
sidebar.

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.

🔵 Needs a closer look

The PR is a broad redesign with many new routes/data sources, and there are still correctness gaps in share-image generation and revalidation behavior that should be resolved and then re-verified end-to-end.

Review details

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

app/api/revalidate/route.ts:50

  • This revalidation endpoint only invalidates /roadmap, but other routes in this PR (e.g. /changelog) also render from the same Notion-backed register/updates. If an agent calls this after writing to Notion, /changelog can stay stale until its ISR window expires.

app/ecosystem/[slug]/opengraph-image.tsx:17

  • Ecosystem project pages are still generating the same OG image for every project (renderArtCard(ogArt.ecosystem)), so shares won’t be project-titled. This contradicts the PR description’s claim that each project gets its own titled card, and it also makes it hard to tell which project a link points to in social previews.
  • Files reviewed: 126/337 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

adamsoffer and others added 2 commits September 9, 2026 17:17
A tinted disc with a line glyph inside — rising for On track, a wobble
for At risk, falling for Off track, flat for No update — and the word
beside it in the same colour. The colour is set once as --health on the
mark and the disc and the word both read it. The glyphs are drawn, not
taken from an icon set: no set has Linear's three.

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.

🟡 Changes recommended

components/ui/input.tsx is missing a "use client" directive but is imported by client components, which will cause a Next.js Server/Client boundary build error.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 126/337 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +18 to +23
const person = (await getPeopleRegister()).find((p) => p.slug === slug);
return renderTitledCard(
ogArt.organizations,
person?.name ?? "People",
"Person"
);
Each row after Linear's project list: the health first, in a column of
its own, so the eye runs down the colour and stops where it changes;
the title and the lead's line beside it, clamped to two lines so a
wordy update does not stall the scan; the owner and, for shipped work,
the date on the right. Shipped rows get the same idiom, a disc with a
tick. Under the month's name in the rail, a tally — 1 shipped, 2 on
track, 1 at risk, 1 no update — with the rows' own icons, so it is also
the key and a month can be read from the rail alone.

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

The revalidation endpoint and people OG-image route have correctness issues that can lead to inconsistent/stale or misleading output.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (2)

app/api/revalidate/route.ts:17

  • The curl example doesn’t match the handler’s auth logic (it strips a Bearer prefix). As written, it’s ambiguous and likely to be copy/pasted incorrectly.
    app/api/revalidate/route.ts:50
  • revalidatePath(PATH) defaults to revalidating only the page at /roadmap. Since this segment has a layout and nested routes that also depend on the Notion register, use layout revalidation so dependent pages update too.
  • Files reviewed: 126/337 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +1 to +24
import { renderTitledCard, ogArt, OG_SIZE, OG_CONTENT_TYPE } from "@/lib/og";
import { getPeopleRegister } from "@/lib/register";

export const alt = "Livepeer — People";
export const size = OG_SIZE;
export const contentType = OG_CONTENT_TYPE;

export async function generateStaticParams() {
return (await getPeopleRegister()).map((p) => ({ slug: p.slug }));
}

export default async function OpengraphImage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const person = (await getPeopleRegister()).find((p) => p.slug === slug);
return renderTitledCard(
ogArt.organizations,
person?.name ?? "People",
"Person"
);
}
adamsoffer and others added 3 commits September 9, 2026 17:26
- A shipped row in the roundup carries the lead's last line before it
  shipped, where there was one, in the page and in the feed. Shipping
  was a title and a date.
- /changelog/roundup.json serves one month's roundup — shipped, reported
  with health, and the silent — for whatever closes the loop: a weekly
  Discord post, a Notion reminder. The month under way by default, or
  ?month=yyyy-mm.
- On a roadmap card the health disc takes the state dot's place, so a
  green dot no longer sits beside a green disc saying the same thing.
- The latest-update card shows the date rather than "4 days ago": a
  page rendered on request and cached for a minute can carry a relative
  time for as long as nobody opens it, and a date is never wrong.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Folding the two into one icon read worse.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Section labels, per-row hairlines, counts and an owner column made
three items look like a spreadsheet. A month is now one list: shipped
first, then work under way with what needs attention first, then the
silent. The coloured mark in a gutter on the left is the grouping, the
title carries its state word, and one muted line beneath changes by
state — the date and the lead's last line for shipped, the lead's line
for work under way, and "Owner has not posted this month" for the
silent, which is the only place the owner is named, because it is the
one line that is about them. Rows are separated by space; the only rule
is between months. The tally in the rail is where the counts live.

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.

🔵 Needs a closer look

The PR is a broad redesign touching routing, metadata/OG generation, new content ingestion paths (Notion + fallbacks), and shared UI primitives, which warrants full human validation across key user journeys and deployments.

Review details

Suppressed comments (1)

app/ecosystem/[slug]/opengraph-image.tsx:17

  • This opengraph image is still the generic art-only card, but the PR description says ecosystem project pages now get a titled share card per project. If the intent is a per-project title card, this route should render a titled card using the project’s name (read from the same markdown source as the page).
  • Files reviewed: 127/338 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

adamsoffer and others added 2 commits September 9, 2026 17:31
A month in which nothing shipped is a fact the changelog exists to
state, so the shipped slot says so. A month in which everyone posted
was a sentence about an absence the tally already shows, and the only
row that was not a commitment; it is gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
With quarter-sized targets that is most months, and a line saying so
read as a confession rather than a calendar. The list speaks.

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.

🔵 Needs a closer look

It introduces at least one confirmed styling bug (top-1/2! Tailwind class in components/ui/tooltip.tsx) and a security-hardening gap (non-constant-time secret comparison in app/api/revalidate/route.ts) that should be addressed before merge.

Review details

Suppressed comments (2)

app/api/revalidate/route.ts:16

  • The curl example in the docblock is misleading: the handler expects a Bearer token (it strips Bearer ... from the Authorization header), but the comment shows a placeholder value without indicating the required scheme. Readers copy/pasting this will likely get a 401.
    app/api/revalidate/route.ts:44
  • The bearer token is compared with a normal string equality check. For consistency with the Notion webhook signature verification (and to reduce timing signal during brute force attempts), compare secrets in constant time.
  • Files reviewed: 127/338 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

adamsoffer and others added 3 commits September 9, 2026 17:38
A roundup is a report on a finished month, and one that changed under
the reader as the month went on would be a dashboard, not a log. The
list stops at last month; the month under way 404s until it closes; a
site with no finished month yet says when the first one lands. The feed
dates each entry to the month's last day, so a subscriber gets one entry
a month and never a draft. The JSON route keeps serving the month under
way by default, because a nudge is only useful before the month closes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Beside Roadmap commitments, and named for what they are updates on.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Live Runner and Go SDK on the 12th, Delegator UX analysis on the 21st,
matching the register, so the August roundup has a shipped list to
develop against.

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

components/ui/badge.tsx uses a hook (useRender) without being a Client Component, which can cause an App Router RSC build/runtime failure.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 127/338 changed files
  • Comments generated: 1
  • Review effort level: Lite

neutralColors[3] = styles.getPropertyValue("--secondary").trim();
}

return [...neutralColors, "color(display-p3 0.04 0.74 0.49)"];

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

The ecosystem project OG image currently renders the same generic card for all projects (contradicting the PR’s “titled per-project cards” claim) and /changelog/roundup.json returns a URL that intentionally 404s for the current month.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

app/ecosystem/[slug]/opengraph-image.tsx:17

  • This opengraph image is identical for every ecosystem project (it renders the generic ecosystem art card), but the PR description says project pages should get their own titled cards. Switching to renderTitledCard with the project’s name would make each project’s share image distinct and match the stated behavior.
  • Files reviewed: 127/338 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment on lines +50 to +54
month: r.month,
title: r.title,
current: r.current,
url: `${SITE}/changelog/${r.month}`,
shipped: r.shipped.map(({ commitment, update }) => ({
adamsoffer and others added 3 commits September 9, 2026 17:58
So August lists Validating Livepeer 2.0 under No update and the roadmap
card shows the same after six weeks — one record that demonstrates the
accountability half.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The muted line under each title now opens with the organization, then
what the state has to say — the day it shipped and the lead's last
word, the update, or that nothing was posted. Shipped rows named the
owner and in-progress rows did not, which read as an omission rather
than a rule.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
In-progress rows now carry the day the update was posted, as shipped
rows carry theirs. Owner and date are set in the foreground and the
update in the muted voice, so the break in colour is the boundary
between the record and the words.

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.

🔵 Needs a closer look

The change set is very large and includes new routes/security-sensitive behaviors; it needs final human review even though only a few specific issues were identified here.

Review details

Suppressed comments (2)

app/changelog/roundup.json/route.ts:54

  • roundup.json defaults to the current (in-progress) month, but /changelog/<month> intentionally 404s for the current month. As written, the url field will therefore point consumers at a 404 for the default case; consider pointing to the index when r.current is true.
    app/api/revalidate/route.ts:44
  • This endpoint compares the bearer token with a normal string equality check. Using a timing-safe comparison avoids leaking information via response timing and is a common hardening for shared-secret auth.
  • Files reviewed: 127/338 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Owner and date keep theirs; the words follow after a wider gap in the
muted voice. A third middot gave them the weight of a third field.

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.

🔵 Needs a closer look

The ecosystem project OG image route currently renders the same non-titled card for every project (contradicting the stated behavior) and the revalidation endpoint should use constant-time token comparison for a safer auth gate.

Review details

Suppressed comments (2)

app/ecosystem/[slug]/opengraph-image.tsx:16

  • This dynamic OG image is identical for every ecosystem project (it ignores params.slug and renders renderArtCard(ogArt.ecosystem)), which contradicts the PR description that project pages get their own titled card and makes all shares from /ecosystem/ indistinguishable.
    app/api/revalidate/route.ts:46
  • Bearer token validation uses a direct string comparison (offered !== secret). For an authentication gate, prefer a constant-time comparison to reduce exposure to timing attacks (even if low-risk over the network).
  • Files reviewed: 127/338 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

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.

🔵 Needs a closer look

Several new UI primitive modules are missing an explicit "use client" directive despite being imported by client components and/or using hooks, which can break Next.js App Router client/server boundary rules.

Review details
  • Files reviewed: 127/338 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

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