Update Livepeer Early Access console UI - #41
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
81f0a98 to
b13cef5
Compare
eliteprox
left a comment
There was a problem hiding this comment.
LGTM. Nice iterative UI update. See review comments below for historical context to inform next PR, none are blocking as we will draft the next change by restoring real user usage data
Reviewed PR #41 (peace/ui-updates vs main, +2744 / −2971). Findings first, highest severity first.
The rebrand, nav collapse, /settings//usage//calls redirects, and /install allowlist gating look intentional and mostly correct. The problems are fabricated history media, wrong numbers/copy, and a referral CTA that does nothing.
High
1. History detail shows fabricated media as the user’s output
AccountActivityRow / SignedTicketRequestRow only have ticket metadata (id, pipeline, model, fee, time). There is no output URL. The new viewer invents the rest:
function previewImageUrl(row: AccountActivityRow, size = "1200/900"): string {
return `https://picsum.photos/seed/${previewSeed(row)}/${size}`;
}mediaSpecForRow then calls samplePayload() — hardcoded prompts, fake gateway.livepeer URLs, and picsum images — and renders that as the request result.
Video and audio are non-playable chrome (poster + fake 0:00 controls; no <video> / <audio>):
function VideoPreview({ poster }: { poster?: string }) {
return (
<div className="relative h-full w-full overflow-hidden bg-background">
{poster && (
<img
src={poster}
...
/>
)}
...
<Play className="h-4 w-4 fill-current" aria-hidden="true" />
<span>0:00</span>Clicking a real history row on Early Access will show a random stock photo (or a dead video skin) as if it were that run. That violates the console’s honest-UI rule and will get reported as “my generations look wrong.”
Fix before merge: keep the metadata pane (capability, cost, time — those fields are real) and ship an explicit “Preview unavailable” stage until an output-URL API exists. Do not show picsum or sample JSON as the result.
2. Recent history timestamps are hardcoded to 30s
function formatHistoryRelativeTime(iso: string): string {
const then = new Date(iso).getTime();
if (Number.isFinite(then)) {
const seconds = Math.round((Date.now() - then) / 1000);
if (seconds < 60) return "30s";
}
return formatRunRelativeTime(iso).replace(/ ago$/, "");
}A 3s-old row and a 59s-old row both read 30s. formatRunRelativeTime already returns "just now" under a minute — this special case is strictly worse. Looks like a leftover placeholder.
Medium
3. Balance sublabel says “remaining” for the total issued
Hero number is remaining (included left + credits + overage). issuedTotal is consumed + remaining. Visible copy then says / $X remaining:
{runway && (
<p className="text-[12px] font-normal text-fg-faint">
/ {fmtUsd(issuedTotal)} remaining
</p>
)}aria-label is correct (remaining of … total issued). On screen, $200 / $250 remaining reads as $250 still left. Should be / $X issued (or drop the denominator).
4. “Refer a friend” copies a URL nothing handles
function referralUrlFor(user: ConsoleUser): string {
return `${new URL(MCP_SERVER_URL).origin}?ref=${encodeURIComponent(user.id)}`;
}
function copyReferralUrl(referralUrl: string) {
void navigator.clipboard.writeText(referralUrl).then(() => {
toast("copied to clipboard");Nothing in the app reads ?ref=. The value is a PymtHouse eu_<sha256> id, not a referral code. Users get a success toast for a no-op link. Hide the card until a referral endpoint exists, or wire one.
Clipboard rejection is also ignored (same in CopyButton’s new feedback="toast" path).
5. Signed-out chrome still sells API keys
Pilot comments and MCP_SERVER_URL say there is no key to provision. The signed-out sidebar still says “5 demo calls per app”, “Spin up in 30 seconds with an API key”, and Get an API key (desktop + mobile). That contradicts Home/Install and /signup.
6. ChatGPT / Hermes install steps are not a real connector flow
Claude and Claude Code look plausible (paste URL / claude mcp add --transport http). ChatGPT and Hermes tell the user to paste a Codex prompt and “approve the connector changes.” ChatGPT desktop MCP setup is Settings → Connectors, not asking Codex to edit config. Those two tabs will fail in the field.
7. ?request= deep links only resolve against the first loaded page
CallsSection does allRows.find((r) => r.id === requestId). First fetch is limit=50. An older /calls?request= bookmark (or a row not in the first page) leaves open={false} with the query param still in the URL and no error. No “load until found” and no “not found” state.
8. Auth hydration blanks the page
if (isLoading) return null;Same on /install. The route Suspense fallback (ConsolePageSkeleton) never shows because the client gate returns null. Cold load is an empty main column until Auth0 + externalUserId resolve.
9. Checkout return URLs were pointed at /home with no consumer
successUrl / cancelUrl defaults and useOwnerWallet now land on /home?topup=… / ?checkout=…. WalletPanel / BillingSection / PlansPanel are unmounted and nothing on Home reads those params. Today the checkout UI is gone, so this is latent — any leftover or API-triggered Stripe session returns to a silent Home.
Low
| Item | Where | Why |
|---|---|---|
| Personal Tailscale host in Next config | next.config.ts allowedDevOrigins: ["studio.tail0de21e.ts.net"] |
Dev-only, but a machine-specific hostname should not ship. |
/home no longer middleware-gated |
middleware.ts isSessionOnlyPath |
Intentional in-shell SignInWall. Weaker defense-in-depth; APIs still requireConsoleSession(). |
| Allowlist test not updated | lib/console/email-allowlist.test.ts |
/install is gated in code; test still only asserts / and /home. |
| Cmd+K still listed | KeyboardShortcuts.tsx |
File was edited (G+I added); palette is still unmounted. Pre-existing, but this PR touched the overlay. |
| Metadata still says “manage API access” | app/layout.tsx / app/(app)/layout.tsx |
Copy leftover after keys left the product. |
Inter loaded from rsms.me with no SRI |
app/layout.tsx |
Third-party webfont; availability + supply-chain. Prefer self-host. |
| Dual animation deps | package.json |
Sidebar uses motion; Dialog/Login/Tooltip still framer-motion. Extra bundle, not a functional bug. |
Missing tests
No new tests. Highest-value coverage for this diff:
formatHistoryRelativeTime— sub-60s must not be"30s"isAllowlistGatedPath("/install")referralUrlFor/ any futurerefhandler/calls?request=redirect preserves and encodes the id- Balance copy: remaining vs issued (once the label is fixed)
Existing *.test.ts files still have no runner (pnpm test is not wired).
What looks fine
/settings,/usage,/callsredirects (page +next.config;/callskeeps?request=)/installadded to allowlist +isSessionOnlyPath(signed-out →/login)- Payment/wallet routes still session-gated
- No new XSS: drawer text is React-escaped; only
dangerouslySetInnerHTMLis the static theme bootstrap MCP_SERVER_URL→https://earlyaccess.livepeer.org/api/mcpmatches robots/sitemap/metadata- Theme wipe to system-only is consistent with removing Appearance settings
Merge bar
I would not merge until #1 and #2 are fixed. #1 is the one users will hit on every history click. #3–#6 are the next tier if this is the public Early Access shell.
Summary
Verification
pnpm run typecheckpnpm run lintpnpm run buildBuild passed with existing Next/Auth0 warnings about workspace root inference and dynamic dependency import.