Skip to content

feat: Nevermined Tutorials showcase website - #65

Merged
aaitor merged 6 commits into
mainfrom
aaitor/tutorials-showcase-website
Sep 4, 2026
Merged

feat: Nevermined Tutorials showcase website#65
aaitor merged 6 commits into
mainfrom
aaitor/tutorials-showcase-website

Conversation

@aaitor

@aaitor aaitor commented Sep 4, 2026

Copy link
Copy Markdown
Member

Why this matters

Our tutorials repo is the public shop window for building paid AI agents on Nevermined, but today it is a folder of READMEs — a developer has to clone and read each one to grasp what is possible. This PR adds a polished website that presents every tutorial in the same clear shape and lets visitors actually run the payment flow in the browser, so the value lands in seconds instead of a git clone. It is built to become the canonical replacement for examples.nevermined.app and ships with a Docker image ready to deploy on our infrastructure.

What this is

A Next.js (App Router) website under showcase/. Every current tutorial is presented in one normalized shape — what you'll learn · how it works · under the hood · see it run — behind a persistent left sidebar grouped by protocol (Catalog · x402 HTTP · MCP · LangChain).

  • Content-driven — one typed array (content/tutorials.ts) generates every page; adding a tutorial is a data edit.
  • Live "see it run" — a real payment handshake (402 → authorize → settle) with a per-session credit balance, served by a local sandbox agent (lib/demo-agent.mjs, unit-tested). No external service, no real money, no credentials — it works the moment you open it, with a clean seam to point at a real hosted agent later.
  • Recap tier — the two catalog/ demos (real crypto, multi-chain) embed video + the on-chain receipt; Song From the Headlines also surfaces Rod's alternative take.
  • Design — light, using the official nevermined.ai/docs palette; syntax-highlighted code; per-file GitHub links; a Links section (Docs / App / Discord / GitHub).
  • Protocol-agnostic copy — framed as Nevermined agent payments across x402, MCP and MPP, not x402-only.

Deploy

showcase/Dockerfile (Next.js standalone, multi-stage, non-root, listens on $PORT on 0.0.0.0) + a repo-root .dockerignore. Build from the repo root so the committed catalog/*.mp4 demo media is in context:

docker build -f showcase/Dockerfile -t nvm-tutorials-showcase .

Test plan

  • npm run build — clean, 15 pages (gallery + 10 tutorials + API + 404).
  • node showcase/lib/demo-agent.mjs — sandbox handshake self-check passes.
  • Verified in-browser: gallery, live tutorial (402 → authorize → settle, balance ticks down), recap (video / cover / song / receipt play).
  • Docker image built and run: home / tutorials / recap video / /api/agent all serve 200; image ~254MB.
  • Reviewer sanity-check on the copy and the docs palette as the intended default.

🤖 Generated with Claude Code

https://claude.ai/code/session_011UxQXzKhM37FPvPuHUnKHh

A Next.js (App Router) website that turns the tutorials repo into a visual,
navigable showcase. Every non-deprecated tutorial is presented in one
normalized shape — what you'll learn, how it works, under the hood, and a
"see it run" panel — behind a persistent left sidebar grouped by protocol
(Catalog / x402 HTTP / MCP / LangChain).

- Content-driven: one typed array (content/tutorials.ts) renders all pages.
- Live "see it run": real x402-shaped handshake (402 -> authorize -> settle)
  with a per-session credit balance, served by a local sandbox agent
  (lib/demo-agent.mjs, unit-tested) — no external service, no real money.
- Recap tier: the two catalog demos embed video + on-chain receipt; Song
  From the Headlines also links Rod's alternative take.
- Light, docs-palette design (official nevermined.ai/docs colors), syntax-
  highlighted code, per-file GitHub links, protocol-agnostic copy.
- Dockerfile (Next.js standalone, non-root) + .dockerignore for ArgoCD/k8s.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UxQXzKhM37FPvPuHUnKHh
@r-marques

Copy link
Copy Markdown
Member

👀 Reviewing

GitHub Actions workflow that builds showcase/Dockerfile and pushes to
europe-west3-docker.pkg.dev/nevermined-eu-dev/nevermined-io/tutorials-showcase
(keyless WIF auth, matching the other Nevermined repos). Runs on pushes to
main touching showcase/**, and on manual dispatch.

Versioning: immutable sha-<short> per build (pin this in ArgoCD for prod),
latest on main, and an optional semver via the dispatch `version` input.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UxQXzKhM37FPvPuHUnKHh
@aaitor

aaitor commented Sep 4, 2026

Copy link
Copy Markdown
Member Author

Added the image CI (option A): .github/workflows/showcase-image.yml builds showcase/Dockerfile and pushes to europe-west3-docker.pkg.dev/nevermined-eu-dev/nevermined-io/tutorials-showcase via keyless Workload Identity Federation — same SA/provider as the other Nevermined repos (e.g. visa-pilot), no secrets.

Versioning: immutable sha-<short> per build (← pin this in ArgoCD for production), latest on main, and an optional semver via the manual version dispatch input.

Two infra checks before this can push on merge (both are GCP-side, not code):

  1. The WIF pool/provider …/workloadIdentityPools/github/providers/github-actions must authorize nevermined-io/tutorials (works out of the box if it's org-scoped; needs a one-line attribute mapping if it's per-repo).
  2. The …/nevermined-io/tutorials-showcase Artifact Registry path — the github-actions-service-account needs Artifact Registry Writer on the nevermined-io repo (already true for the sibling images), and AR auto-creates the image on first push.

It triggers on push to main (paths showcase/**), so it first runs when this PR merges. ArgoCD manifests (production ns, 1 pod) are next, pinned to the sha-<short> tag.

@r-marques r-marques left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 Automated PR review — 🟡 Mergeable with nits

A new Next.js showcase site for the tutorials repo (33 files, +4,356/−0, all additions). The content layer holds up well under checking, and the sandbox is honest about what it is — my main concern going in was that a "live payment sandbox" on a payments company's site might read as real money, and it does not: the panel says so in the UI, not just in a code comment.

⚠️ The head moved while I was reviewing (dbe2e47c536c49fe), so I discarded the first pass and re-ran against the new head rather than posting a review of code that had already changed. The delta added .github/workflows/showcase-image.yml and a README section — which lands directly on my main finding, so it is reviewed below in its current form, not its original one.

What I reviewed

  • Diff range origin/main...HEAD — 33 files, +4,356/−0, head 536c49fe.
  • Panel: none — stood on direct verification.

Verified

  • The sandbox does not misrepresent itself. LiveRunPanel.tsx:234 renders "Sandbox agent — real payment round-trips and a real per-session credit balance, no real money" to the visitor, and app/api/agent/route.ts:7-11 says the same to the next developer. It speaks the real x402 shape (402 → authorize → 200 + settlement) and calls no external service. That is the right way round.
  • Every per-file GitHub link resolves. repoFileUrl builds <repo>/blob/main/<repoPath>/<filePath>, so I checked them all against the tree: 20/20 file links across 8 tutorials resolve, and all 10 repoPath directories exist. No dead links — the classic defect for a generated docs surface.
  • The Docker build is coherent. next.config.mjs sets output: "standalone", which is what makes COPY .next/standalone valid; the two recap .mp4s the build pulls from catalog/ are present and git-tracked (19.8 MB + 15.4 MB); and the root .dockerignore does not exclude catalog/, so that COPY resolves. The runner stage creates nextjs:nodejs and sets USER nextjs, so the non-root claim holds.
  • The new workflow's auth and tagging shape are right — keyless WIF with no stored secret, and an immutable sha-<short> per build that the summary and README both tell you to pin in ArgoCD. That matches the direction argocd#604 is moving in.
  • No secrets. Grepped the new app/, components/, lib/ and content/ for keys, tokens and bearer literals — every hit is documentation prose about tutorial env vars, plus the nvm_demo cookie name.
  • The gitignored videos are a documented step, not an omission: showcase/.gitignore excludes public/media/**/*.mp4 and points at npm run sync:media.

Verdict: 🟡 Mergeable with nits — 0 blockers, 3 should-fix, 1 nit.

🧭 Inline comments (4)

Each is posted on its line in Files changed.

  • 🟡 SHOULD FIX — No pull_request trigger — the first next build runs after merge, inside the publish job — .github/workflows/showcase-image.yml:12
  • 🟡 SHOULD FIX — Pushing latest contradicts the immutableTags=true premise argocd#604 relies on for this same registry — .github/workflows/showcase-image.yml:52
  • 🟡 SHOULD FIX — The "make it genuinely live" instruction sits five lines above a client-controlled balance — showcase/app/api/agent/route.ts:10
  • 💡 NIT — Excludes node_modules but not Python build artifacts, in a repo that is half Python tutorials — .dockerignore:3

Comment thread .github/workflows/showcase-image.yml
Comment thread .github/workflows/showcase-image.yml Outdated
Comment thread showcase/app/api/agent/route.ts Outdated
Comment thread .dockerignore

@r-marques r-marques left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🟡 Approved with comments — no blockers; 3 should-fix, 1 nit(s) left inline.

Inline review: #65 (review)

Addresses r-marques's review on #65 (3 should-fix + 1 nit):

- ci: add a `pull_request` trigger + a credential-free `verify` job
  (npm ci · node lib/demo-agent.mjs · next build). `next build` is the
  type-check, so a broken content/tutorials.ts now fails the PR instead
  of merging and breaking `main`. `build-push` gets `needs: verify` and
  `if: github.event_name != 'pull_request'` — publishes only on main/dispatch.
- ci: drop the moving `latest` tag. The AR repo has immutableTags=true
  (verified), so the second default-branch push would be rejected and could
  fail the whole tag set. `sha-<short>` (immutable) is what ArgoCD pins.
- route.ts: warn the next developer that the nvm_demo cookie is
  client-supplied and unsigned — must move authorized/balance server-side
  before wiring a real spending backend.
- .dockerignore: exclude Python venvs/bytecode (repo is half Poetry
  tutorials); drop the duplicate `.git` line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UxQXzKhM37FPvPuHUnKHh

@eruizgar91 eruizgar91 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Code review

A brand-new Next.js 15 App Router site under showcase/, so the usual repo invariants barely apply — I weighted the review toward the simulated payment handshake, the Docker/standalone wiring, App Router correctness, and whether the hand-maintained catalogue points at things that actually exist. Five findings are posted inline (1 high, 3 medium, 1 low); the highest one is that the sandbox session cookie is not scoped per tutorial, which retires the flagship 402 demo after the visitor's first authorize.

Checked and came back clean:

  • Dockerfile / standalone wiring — three-stage deps/build/runner matches the official pattern, output: "standalone" is set, PORT/HOSTNAME are honoured by server.js, the image runs as the non-root nextjs user, and .next/static + public are both copied. Every COPY source resolves at this head, including the two gitignored .mp4s pulled from catalog/; npm ci runs before NODE_ENV=production, so typescript is present for next build. The repo-root .dockerignore excludes nothing the build needs.
  • package-lock.json — lockfileVersion 3, 62 entries, every one carrying resolved, and all five devDependencies present, so npm ci will not fail the lockfile/manifest consistency check.
  • App Router — Next 15's params is correctly awaited in both generateMetadata and the page component, generateStaticParams is present, notFound() is used properly, and only serializable data crosses the server→client boundary.
  • CodeBlock / dangerouslySetInnerHTML — no XSS path. The highlighted string comes solely from the static samples in content/tutorials.ts, never from /api/agent responses or the chat input, and Prism.highlight escapes its input before emitting token spans.
  • Catalogue links — all 10 repoPath values and all 19 tech.files[].path entries resolve to real paths at this head, including across the http-simple-agenthttp-simple-agent-ts rename and the weather-mcp src/server/main.tssrc/main.ts move. None of the six tutorials deleted in #64 are referenced.
  • Secrets / binaries — nothing credential-shaped committed; song.mp3 and album-cover.jpg are the deliberate demo assets the README documents.

Considered and deliberately discarded:

  • The session cookie is unsigned and client-forgeable. True, but nothing of value is behind it — the balance is simulated and no real money moves — so forging it only lets a visitor cheat their own demo. Not worth a signing scheme here.
  • README says both recap demos show "playable outputs, the on-chain receipt"; diligence-in-a-box has neither (RecapPanel renders both conditionally, so nothing breaks). Real, but a documentation-parity nitpick rather than a defect.
  • showcase/ is not added to the root README.md or CLAUDE.md repository-structure tree. Worth doing, but the one documentation rule in root CLAUDE.md is scoped to modifying tutorials, and showcase/ is a site, not a tutorial — no specific convention to cite.
  • npm here vs yarn/pnpm in the sibling tutorials. The repo already mixes yarn and pnpm, so a third is not a new violation.
  • Two // ponytail: authoring markers left in committed source (content/tutorials.ts, scripts/sync-media.sh) — cosmetic, no functional impact.
  • No CI covered showcase/ — addressed by the ci: build & push showcase image commit that landed mid-review.

Note: the head moved from dbe2e47 to 536c49f while this was running. That commit only adds .github/workflows/showcase-image.yml and a README section, and touches none of the files below, so every finding was re-verified against 536c49f.

Comment thread showcase/app/api/agent/route.ts
Comment thread showcase/components/RecapPanel.tsx Outdated
Comment thread showcase/content/tutorials.ts Outdated
Comment thread showcase/README.md Outdated
Comment thread showcase/lib/types.ts Outdated
…panel copy

Addresses eruizgar91's review on #65 (1 high, 3 medium, 1 low):

- HIGH: key the sandbox session per tutorial slug. The nvm_demo cookie was a
  single global {authorized,balance}, so authorizing on one live tutorial made
  every other tutorial's `ask` skip the 402 (the whole "see it run" handshake
  played once per visitor) and inherit a foreign balance across agents that
  charge different credits. route.ts now stores a { [slug]: {authorized,balance} }
  map — each tutorial gets its own handshake and its own 100 credits. respond()
  was already per-substate, so no logic change there. Old single-object cookies
  are detected by their top-level `balance` and dropped.
- MEDIUM: subtitles now render. HTML5 <track> accepts WebVTT only; content
  pointed at .srt, so both recap videos showed no captions despite advertising
  "EN/ES subtitles". sync-media.sh derives .vtt from each .srt (WEBVTT header +
  comma→dot timestamps), the four .vtt are committed, and both EN and ES tracks
  are wired (RecapPanel maps a track list; type is now an array).
- MEDIUM: rewrite 7 live-panel notes that claimed a hosted LangGraph/LangSmith
  backend and a "falls back to a recorded run" fallback — neither exists in this
  build (the panel only POSTs to /api/agent, no recorded data is read). Notes now
  describe the sandbox and point at the README for the go-live story.
- MEDIUM: soften "Every tutorial in this repo" in the showcase README —
  langchain-chat-ui-nvm is the browser buyer front-end, shown inside the LangChain
  pages rather than as its own entry.
- LOW: relabel the paymentPill docblock in types.ts — it is set in content but
  read by nothing (the panel's pill comes from the intro response); marked legacy.

Verified: node lib/demo-agent.mjs self-check passes; tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UxQXzKhM37FPvPuHUnKHh

@r-marques r-marques left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 Automated PR review (round 3) — 🟡 Mergeable with nits

All four round-2 findings are fixed, and I checked each against the code rather than the fold report. The round also brought new work — per-slug sandbox state and WebVTT captions — which I reviewed as new surface rather than waving through. One small residual nit.

Round-2 findings — status

# Round-2 finding Status
1 🟡 No pull_request trigger; first next build runs after merge Fixed
2 🟡 latest contradicts the immutableTags=true premise Fixed
3 🟡 "Make it live" instruction sits above a client-controlled balance Fixed
4 💡 .dockerignore misses Python artifacts Fixed

1 — verified. A credential-free verify job now runs on pull_request, doing npm cinode lib/demo-agent.mjsnpm run build, and build-push is correctly gated behind both needs: verify and if: github.event_name != 'pull_request'. So a PR now fails on a type error instead of main doing it, and the self-check is finally invoked by something.

2 — verified, and the fix is robust in a way the comment isn't. type=raw,value=latest is gone and the header explains why. Note the resolution is correct under either branch of the ambiguity I raised: if the AR repo really is immutableTags=true, dropping latest avoids the rejected push; if it isn't, dropping it costs nothing, since both the step summary and README already tell you to pin sha-*. The header still asserts immutableTags=true without a citation — that assertion is now load-bearing only for argocd#604, not for this workflow.

3 — verified. The warning now sits with the instruction and names the concrete attack (Cookie: nvm_demo={"x":{"authorized":true,"balance":1e9}}, "httpOnly is not integrity"), which is exactly the gap: an implementer wiring a real backend reads this line.

4 — verified. **/.venv, **/__pycache__, **/*.pyc added with a reason, and the duplicate .git entry removed too.

New surface this round, also checked

  • Per-slug cookie state. The legacy-cookie migration discriminates on typeof parsed.balance !== "number", which is the right test rather than key-presence: it survives a null cookie (short-circuited), a string/number cookie (fails the object check), and — the interesting case — a tutorial whose slug were literally balance, since the new shape stores an object there. all[slug] undefined falls through to freshState(). Sound.
  • WebVTT captions — a real bug you found yourselves. HTML5 <track> does not accept SRT, so the previous single subtitles?: string never rendered. All four new .vtt paths resolve, and I regenerated each one from its .srt with the script's own command: all four are byte-identical to the committed files, so the generated captions are genuinely in sync rather than hand-edited. The two "missing" .mp4 paths are the gitignored ones the Dockerfile copies from catalog/ — expected, and verified in round 2.

⚠️ Same coverage caveat: checks were still running on 8d8c0762 at review time, so I am not reporting them green. The new verify job means that now actually means something on this PR.

Verdict: 🟡 Mergeable with nits — 0 blockers, 0 should-fix, 1 nit.

🧭 Inline comments (1)

Each is posted on its line in Files changed.

  • 💡 NIT — The SRT→VTT sed rewrites any <digits>,<3 digits> in caption text, not just timestamps — showcase/scripts/sync-media.sh:20

Comment thread showcase/scripts/sync-media.sh Outdated

@r-marques r-marques left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🟡 Approved with comments — no blockers; 0 should-fix, 1 nit(s) left inline.

Inline review: #65 (review)

aaitor and others added 2 commits September 4, 2026 12:12
Addresses r-marques's nit on #65: the unanchored substitution could rewrite
a "1,234" in caption text to "1.234". Restrict it to lines containing "-->"
(cue timings), so dialogue is never touched. Output is byte-identical for
every .vtt in the repo today.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011UxQXzKhM37FPvPuHUnKHh
The 'verify' job spent ~420s of a ~470-510s run inside `npm ci` — while
setup-node's ~/.npm download cache was confirmed HIT (147MB restored). Same
34 packages installed in 22s on a warm run vs 421s on a slow one, identical
inputs. The variance is `npm ci` hitting the network for data it already has:
registry revalidation and the audit/fund metadata round-trips (the slow runs'
output is even missing the "audited 35 packages" line — the audit call hung).

Fix targets the actual bottleneck, not a new cache (setup-node's already hits):
- `npm ci --prefer-offline --no-audit --no-fund` — install stays offline for
  cached tarballs; no audit/fund round-trips to stall on. Takes effect on the
  first run, no second-run warm-up needed.
- `concurrency` group per ref with cancel-in-progress scoped to pull_request —
  superseded PR pushes stop burning a full runner; push/dispatch runs (which
  publish the immutable image) are never cancelled.

Left untouched deliberately: the `npm run build` type-check, the demo-agent
self-check, and the entire build-push job (WIF auth, immutable sha-<short>
tags, no `latest`, trigger semantics). verify stays credential-free.

Not added: a separate actions/cache for showcase/.next/cache — build is only
~32s and isn't the bottleneck; revisit if build growth makes it one.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XXfTrdPQAHKYMRUuvhKeYr
@aaitor
aaitor enabled auto-merge (squash) September 4, 2026 10:29
@aaitor
aaitor merged commit c00290f into main Sep 4, 2026
2 checks passed
@aaitor
aaitor deleted the aaitor/tutorials-showcase-website branch September 4, 2026 10:33
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.

3 participants