Redesign livepeer.org - #93
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
🔵 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.twitterwithoutimages, and unlike most other top-level pages it has noapp/ecosystem/submit/opengraph-image.tsx. In Next metadata merging, that typically drops the inherited share image entirely, so/ecosystem/submitmay 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
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>
There was a problem hiding this comment.
🟡 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 frompost.author.slugwithout 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
| 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>
There was a problem hiding this comment.
🔵 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,/changelogcan 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
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>
There was a problem hiding this comment.
🟡 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
| 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>
There was a problem hiding this comment.
🟡 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
Bearerprefix). 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
| 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" | ||
| ); | ||
| } |
- 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>
There was a problem hiding this comment.
🔵 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
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>
There was a problem hiding this comment.
🔵 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
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>
There was a problem hiding this comment.
🟡 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)"]; |
There was a problem hiding this comment.
🟡 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
renderTitledCardwith 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
| month: r.month, | ||
| title: r.title, | ||
| current: r.current, | ||
| url: `${SITE}/changelog/${r.month}`, | ||
| shipped: r.shipped.map(({ commitment, update }) => ({ |
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>
There was a problem hiding this comment.
🔵 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.jsondefaults to the current (in-progress) month, but/changelog/<month>intentionally 404s for the current month. As written, theurlfield will therefore point consumers at a 404 for the default case; consider pointing to the index whenr.currentis 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>
There was a problem hiding this comment.
🔵 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.slugand rendersrenderArtCard(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
There was a problem hiding this comment.
🔵 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
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
agentAppOriginis 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 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