Skip to content

tarballs: redesign preview tarballs index page#1911

Merged
pranaygp merged 9 commits into
mainfrom
pgp/tarball-page-improvements
May 4, 2026
Merged

tarballs: redesign preview tarballs index page#1911
pranaygp merged 9 commits into
mainfrom
pgp/tarball-page-improvements

Conversation

@pranaygp
Copy link
Copy Markdown
Contributor

@pranaygp pranaygp commented May 4, 2026

Summary

Reworks the static index page generated by tarballs/scripts/pack.ts (deployed at https://workflow-tarballs.labs.vercel.dev/) to be more useful and pleasant to look at.

What's new

  • Featured workflow package at the top with a large install command, copy button, and direct tarball download — the rest of the packages are secondary.
  • Top-of-page metadata chips: short SHA (linked to commit on GitHub), branch (linked), PR number (linked), build timestamp, package count + total size.
  • Collapsible "What is this?" explainer describing how the version + dep rewriting works.
  • Package-manager tab toggle (pnpm / npm / yarn / bun) that swaps the install command shown for every row in place.
  • Live filter input over the package list, with / keyboard shortcut to focus.
  • Per-row install command, copy button, and direct download link for every package.
  • Geist-inspired theme with proper dark/light support driven by system preference.

Implementation notes

  • Pack step now records the produced tarball size via fs.stat after pnpm pack so the page can show human-readable sizes.
  • The page reads VERCEL_GIT_* env vars for commit / branch / PR links and falls back to local git rev-parse for branch when running off-Vercel.
  • All packages still get an entry, but workflow is filtered out of the bottom list and rendered separately.
  • No changes to the deployment shape — public/<escaped-name>.tgz URLs are unchanged, so existing usage keeps working.

The Vercel preview for this PR will deploy and serve the new page so you can see it live.

Test plan

  • Open the Vercel preview deployment and verify the page renders correctly in light and dark mode
  • Confirm the workflow package appears as the featured card at the top
  • Confirm SHA / branch / PR chips are present and linked
  • Toggle pnpm / npm / yarn / bun and confirm install commands update everywhere
  • Filter the package list (and try the / shortcut)
  • Click copy on a row and on the featured card
  • Click download on a row and verify the tarball downloads
  • Smoke-install the featured tarball into a fresh project and confirm it still works (pnpm i https://<deployment>/workflow.tgz)

🤖 Generated with Claude Code

Rebuild the static index page produced by `tarballs/scripts/pack.ts`:

- Featured `workflow` package up top with prominent install command,
  copy button, and direct tarball download
- Top-of-page metadata chips: short SHA (linked to commit), branch,
  PR number, build timestamp, package count + total size
- Collapsible "What is this?" explainer
- Package-manager tab toggle (pnpm / npm / yarn / bun) that swaps the
  install command for every row in place
- Live filter input over the rest of the package list (with `/` shortcut)
- Per-row install command, copy button, and direct download
- Modern dark/light theme with system preference, Geist-inspired styling

Also captures tarball size during pack and renders human-readable byte counts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 4, 2026

⚠️ No Changeset found

Latest commit: 1c48345

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Ready Ready Preview, Comment May 4, 2026 11:41am
example-nextjs-workflow-webpack Ready Ready Preview, Comment May 4, 2026 11:41am
example-workflow Ready Ready Preview, Comment May 4, 2026 11:41am
workbench-astro-workflow Ready Ready Preview, Comment May 4, 2026 11:41am
workbench-express-workflow Ready Ready Preview, Comment May 4, 2026 11:41am
workbench-fastify-workflow Ready Ready Preview, Comment May 4, 2026 11:41am
workbench-hono-workflow Ready Ready Preview, Comment May 4, 2026 11:41am
workbench-nitro-workflow Ready Ready Preview, Comment May 4, 2026 11:41am
workbench-nuxt-workflow Ready Ready Preview, Comment May 4, 2026 11:41am
workbench-sveltekit-workflow Ready Ready Preview, Comment May 4, 2026 11:41am
workbench-tanstack-start-workflow Ready Ready Preview, Comment May 4, 2026 11:41am
workbench-vite-workflow Ready Ready Preview, Comment May 4, 2026 11:41am
workflow-swc-playground Ready Ready Preview, Comment May 4, 2026 11:41am
workflow-tarballs Ready Ready Preview, Comment May 4, 2026 11:41am
workflow-web Ready Ready Preview, Comment May 4, 2026 11:41am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
workflow-docs Skipped Skipped May 4, 2026 11:41am

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

🧪 E2E Test Results

All tests passed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 859 0 219 1078
✅ 💻 Local Development 957 0 219 1176
✅ 📦 Local Production 957 0 219 1176
✅ 🐘 Local Postgres 957 0 219 1176
✅ 🪟 Windows 98 0 0 98
✅ 📋 Other 510 0 176 686
Total 4338 0 1052 5390

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 72 0 26
✅ example 72 0 26
✅ express 72 0 26
✅ fastify 72 0 26
✅ hono 72 0 26
✅ nextjs-turbopack 96 0 2
✅ nextjs-webpack 96 0 2
✅ nitro 72 0 26
✅ nuxt 72 0 26
✅ sveltekit 91 0 7
✅ vite 72 0 26
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 73 0 25
✅ express-stable 73 0 25
✅ fastify-stable 73 0 25
✅ hono-stable 73 0 25
✅ nextjs-turbopack-canary 79 0 19
✅ nextjs-turbopack-stable 98 0 0
✅ nextjs-webpack-canary 79 0 19
✅ nextjs-webpack-stable 98 0 0
✅ nitro-stable 73 0 25
✅ nuxt-stable 73 0 25
✅ sveltekit-stable 92 0 6
✅ vite-stable 73 0 25
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 73 0 25
✅ express-stable 73 0 25
✅ fastify-stable 73 0 25
✅ hono-stable 73 0 25
✅ nextjs-turbopack-canary 79 0 19
✅ nextjs-turbopack-stable 98 0 0
✅ nextjs-webpack-canary 79 0 19
✅ nextjs-webpack-stable 98 0 0
✅ nitro-stable 73 0 25
✅ nuxt-stable 73 0 25
✅ sveltekit-stable 92 0 6
✅ vite-stable 73 0 25
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 73 0 25
✅ express-stable 73 0 25
✅ fastify-stable 73 0 25
✅ hono-stable 73 0 25
✅ nextjs-turbopack-canary 79 0 19
✅ nextjs-turbopack-stable 98 0 0
✅ nextjs-webpack-canary 79 0 19
✅ nextjs-webpack-stable 98 0 0
✅ nitro-stable 73 0 25
✅ nuxt-stable 73 0 25
✅ sveltekit-stable 92 0 6
✅ vite-stable 73 0 25
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 98 0 0
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 73 0 25
✅ e2e-local-dev-tanstack-start-stable 73 0 25
✅ e2e-local-postgres-nest-stable 73 0 25
✅ e2e-local-postgres-tanstack-start-stable 73 0 25
✅ e2e-local-prod-nest-stable 73 0 25
✅ e2e-local-prod-tanstack-start-stable 73 0 25
✅ e2e-vercel-prod-tanstack-start 72 0 26

📋 View full workflow run

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 0.031s (-30.2% 🟢) 1.005s (~) 0.974s 10 1.00x
💻 Local Nitro 0.032s (-24.8% 🟢) 1.005s (~) 0.973s 10 1.05x
🐘 Postgres Express 0.043s (-26.4% 🟢) 1.010s (~) 0.967s 10 1.38x
💻 Local Next.js (Turbopack) 0.047s 1.005s 0.958s 10 1.51x
🐘 Postgres Nitro 0.052s (-45.6% 🟢) 1.012s (-3.0%) 0.960s 10 1.68x
🌐 Redis Next.js (Turbopack) 0.059s 1.005s 0.947s 10 1.89x
🐘 Postgres Next.js (Turbopack) 0.059s 1.011s 0.952s 10 1.91x
🌐 MongoDB Next.js (Turbopack) 0.100s 1.009s 0.910s 10 3.23x
workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.068s (-5.6% 🟢) 2.006s (~) 0.938s 10 1.00x
💻 Local Express 1.074s (-4.6%) 2.006s (~) 0.932s 10 1.01x
🐘 Postgres Express 1.081s (-5.7% 🟢) 2.009s (~) 0.927s 10 1.01x
🐘 Postgres Nitro 1.088s (-4.6%) 2.010s (~) 0.922s 10 1.02x
💻 Local Next.js (Turbopack) 1.108s 2.006s 0.898s 10 1.04x
🌐 Redis Next.js (Turbopack) 1.112s 2.007s 0.895s 10 1.04x
🐘 Postgres Next.js (Turbopack) 1.117s 2.009s 0.892s 10 1.05x
🌐 MongoDB Next.js (Turbopack) 1.166s 2.009s 0.843s 10 1.09x
workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 10.387s (-5.1% 🟢) 11.022s (~) 0.634s 3 1.00x
💻 Local Express 10.405s (-4.7%) 11.023s (~) 0.618s 3 1.00x
🐘 Postgres Express 10.415s (-5.0% 🟢) 11.012s (~) 0.597s 3 1.00x
🐘 Postgres Nitro 10.432s (-4.0%) 11.018s (~) 0.585s 3 1.00x
🌐 Redis Next.js (Turbopack) 10.634s 11.023s 0.389s 3 1.02x
💻 Local Next.js (Turbopack) 10.690s 11.023s 0.333s 3 1.03x
🐘 Postgres Next.js (Turbopack) 10.716s 11.017s 0.301s 3 1.03x
🌐 MongoDB Next.js (Turbopack) 10.822s 11.017s 0.195s 3 1.04x
workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 13.426s (-10.9% 🟢) 14.027s (-12.5% 🟢) 0.600s 5 1.00x
🐘 Postgres Express 13.463s (-7.7% 🟢) 14.021s (-6.7% 🟢) 0.558s 5 1.00x
💻 Local Express 13.480s (-10.0% 🟢) 14.028s (-6.7% 🟢) 0.548s 5 1.00x
🐘 Postgres Nitro 13.544s (-7.2% 🟢) 14.019s (-6.7% 🟢) 0.475s 5 1.01x
💻 Local Next.js (Turbopack) 14.031s 15.028s 0.997s 4 1.05x
🌐 Redis Next.js (Turbopack) 14.036s 14.628s 0.592s 5 1.05x
🐘 Postgres Next.js (Turbopack) 14.130s 15.022s 0.892s 4 1.05x
🌐 MongoDB Next.js (Turbopack) 14.245s 15.023s 0.778s 4 1.06x
workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 11.809s (-29.6% 🟢) 12.022s (-29.4% 🟢) 0.214s 8 1.00x
🐘 Postgres Nitro 11.928s (-14.6% 🟢) 12.017s (-16.0% 🟢) 0.089s 8 1.01x
🐘 Postgres Express 11.967s (-14.6% 🟢) 12.265s (-16.0% 🟢) 0.298s 8 1.01x
💻 Local Express 12.248s (-26.2% 🟢) 12.774s (-25.0% 🟢) 0.526s 8 1.04x
💻 Local Next.js (Turbopack) 13.061s 13.737s 0.677s 7 1.11x
🌐 Redis Next.js (Turbopack) 13.217s 13.883s 0.666s 7 1.12x
🌐 MongoDB Next.js (Turbopack) 13.255s 14.019s 0.764s 7 1.12x
🐘 Postgres Next.js (Turbopack) 13.304s 14.021s 0.718s 7 1.13x
Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.141s (-9.4% 🟢) 2.006s (~) 0.865s 15 1.00x
🐘 Postgres Nitro 1.146s (-10.1% 🟢) 2.007s (~) 0.861s 15 1.00x
💻 Local Nitro 1.171s (-28.2% 🟢) 2.006s (-3.3%) 0.835s 15 1.03x
💻 Local Express 1.174s (-21.1% 🟢) 2.006s (~) 0.832s 15 1.03x
🐘 Postgres Next.js (Turbopack) 1.217s 2.007s 0.790s 15 1.07x
🌐 Redis Next.js (Turbopack) 1.240s 2.006s 0.766s 15 1.09x
💻 Local Next.js (Turbopack) 1.289s 2.006s 0.717s 15 1.13x
🌐 MongoDB Next.js (Turbopack) 2.035s 2.826s 0.791s 11 1.78x
Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.202s (-49.1% 🟢) 2.007s (-33.3% 🟢) 0.805s 15 1.00x
🐘 Postgres Nitro 1.229s (-47.7% 🟢) 2.007s (-33.3% 🟢) 0.778s 15 1.02x
🐘 Postgres Next.js (Turbopack) 1.358s 2.008s 0.650s 15 1.13x
💻 Local Nitro 1.685s (-46.4% 🟢) 2.006s (-48.4% 🟢) 0.320s 15 1.40x
💻 Local Express 1.706s (-42.2% 🟢) 2.006s (-41.9% 🟢) 0.299s 15 1.42x
💻 Local Next.js (Turbopack) 1.858s 2.292s 0.434s 14 1.55x
🌐 Redis Next.js (Turbopack) 2.337s 3.008s 0.671s 10 1.95x
🌐 MongoDB Next.js (Turbopack) 3.568s 4.009s 0.441s 8 2.97x
Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.305s (-62.6% 🟢) 2.007s (-50.0% 🟢) 0.702s 15 1.00x
🐘 Postgres Nitro 1.383s (-60.3% 🟢) 2.007s (-49.9% 🟢) 0.624s 15 1.06x
🐘 Postgres Next.js (Turbopack) 1.629s 2.007s 0.378s 15 1.25x
🌐 Redis Next.js (Turbopack) 3.602s 4.010s 0.408s 8 2.76x
💻 Local Next.js (Turbopack) 4.383s 5.011s 0.627s 6 3.36x
💻 Local Express 4.397s (-47.3% 🟢) 5.013s (-44.5% 🟢) 0.616s 6 3.37x
💻 Local Nitro 4.571s (-45.2% 🟢) 5.011s (-44.4% 🟢) 0.440s 6 3.50x
🌐 MongoDB Next.js (Turbopack) 6.273s 7.013s 0.739s 5 4.81x
Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.139s (-9.4% 🟢) 2.009s (~) 0.869s 15 1.00x
🐘 Postgres Nitro 1.168s (-7.1% 🟢) 2.009s (~) 0.840s 15 1.03x
🌐 Redis Next.js (Turbopack) 1.233s 2.006s 0.773s 15 1.08x
🐘 Postgres Next.js (Turbopack) 1.248s 2.007s 0.760s 15 1.10x
💻 Local Next.js (Turbopack) 1.321s 2.005s 0.684s 15 1.16x
💻 Local Express 1.352s (-28.6% 🟢) 2.006s (-15.1% 🟢) 0.654s 15 1.19x
💻 Local Nitro 1.379s (-26.1% 🟢) 2.006s (-14.3% 🟢) 0.627s 15 1.21x
🌐 MongoDB Next.js (Turbopack) 2.026s 2.735s 0.709s 11 1.78x
Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.211s (-48.3% 🟢) 2.008s (-33.3% 🟢) 0.797s 15 1.00x
🐘 Postgres Nitro 1.232s (-47.3% 🟢) 2.008s (-33.3% 🟢) 0.776s 15 1.02x
🐘 Postgres Next.js (Turbopack) 1.368s 2.008s 0.640s 15 1.13x
💻 Local Express 1.838s (-41.3% 🟢) 2.075s (-44.8% 🟢) 0.238s 15 1.52x
💻 Local Nitro 1.954s (-36.3% 🟢) 2.469s (-36.5% 🟢) 0.515s 13 1.61x
💻 Local Next.js (Turbopack) 2.083s 2.826s 0.743s 11 1.72x
🌐 Redis Next.js (Turbopack) 2.350s 3.008s 0.658s 10 1.94x
🌐 MongoDB Next.js (Turbopack) 3.547s 4.008s 0.461s 8 2.93x
Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.318s (-62.3% 🟢) 2.007s (-50.0% 🟢) 0.690s 15 1.00x
🐘 Postgres Nitro 1.379s (-60.4% 🟢) 2.007s (-49.9% 🟢) 0.628s 15 1.05x
🐘 Postgres Next.js (Turbopack) 1.718s 2.075s 0.357s 15 1.30x
🌐 Redis Next.js (Turbopack) 3.595s 4.010s 0.415s 8 2.73x
💻 Local Nitro 4.877s (-46.7% 🟢) 5.516s (-45.0% 🟢) 0.639s 6 3.70x
💻 Local Express 5.136s (-41.6% 🟢) 5.515s (-40.5% 🟢) 0.379s 6 3.90x
💻 Local Next.js (Turbopack) 5.496s 6.011s 0.515s 5 4.17x
🌐 MongoDB Next.js (Turbopack) 6.286s 7.013s 0.727s 5 4.77x
workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.430s (-48.7% 🟢) 1.006s (-1.7%) 0.575s 60 1.00x
💻 Local Nitro 0.457s (-53.4% 🟢) 1.004s (-8.3% 🟢) 0.547s 60 1.06x
🐘 Postgres Nitro 0.483s (-41.1% 🟢) 1.023s (+1.7%) 0.540s 59 1.12x
💻 Local Express 0.509s (-48.3% 🟢) 1.004s (-6.7% 🟢) 0.495s 60 1.18x
🌐 Redis Next.js (Turbopack) 0.617s 1.004s 0.388s 60 1.43x
🐘 Postgres Next.js (Turbopack) 0.697s 1.007s 0.310s 60 1.62x
💻 Local Next.js (Turbopack) 0.712s 1.004s 0.292s 60 1.66x
🌐 MongoDB Next.js (Turbopack) 0.739s 1.006s 0.266s 60 1.72x
workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.027s (-48.0% 🟢) 1.586s (-29.8% 🟢) 0.559s 57 1.00x
🐘 Postgres Nitro 1.093s (-43.3% 🟢) 1.741s (-17.1% 🟢) 0.648s 52 1.06x
💻 Local Nitro 1.156s (-61.9% 🟢) 2.006s (-46.6% 🟢) 0.849s 45 1.13x
💻 Local Express 1.191s (-60.5% 🟢) 2.005s (-44.1% 🟢) 0.814s 45 1.16x
🌐 Redis Next.js (Turbopack) 1.479s 2.006s 0.527s 45 1.44x
🐘 Postgres Next.js (Turbopack) 1.638s 2.007s 0.369s 45 1.60x
💻 Local Next.js (Turbopack) 1.780s 2.027s 0.247s 45 1.73x
🌐 MongoDB Next.js (Turbopack) 1.811s 2.007s 0.196s 45 1.76x
workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 2.012s (-49.6% 🟢) 2.427s (-44.5% 🟢) 0.415s 50 1.00x
🐘 Postgres Nitro 2.035s (-50.4% 🟢) 2.457s (-46.6% 🟢) 0.422s 49 1.01x
💻 Local Nitro 2.638s (-71.6% 🟢) 3.007s (-70.0% 🟢) 0.369s 40 1.31x
💻 Local Express 2.802s (-69.6% 🟢) 3.084s (-69.2% 🟢) 0.283s 39 1.39x
🌐 Redis Next.js (Turbopack) 3.003s 3.277s 0.275s 37 1.49x
🐘 Postgres Next.js (Turbopack) 3.192s 4.010s 0.817s 30 1.59x
💻 Local Next.js (Turbopack) 3.864s 4.145s 0.282s 29 1.92x
🌐 MongoDB Next.js (Turbopack) 4.155s 5.011s 0.856s 24 2.07x
workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.172s (-39.1% 🟢) 1.006s (~) 0.834s 60 1.00x
🐘 Postgres Nitro 0.192s (-32.2% 🟢) 1.006s (~) 0.814s 60 1.12x
🐘 Postgres Next.js (Turbopack) 0.241s 1.006s 0.764s 60 1.40x
🌐 Redis Next.js (Turbopack) 0.251s 1.004s 0.753s 60 1.46x
💻 Local Nitro 0.432s (-28.6% 🟢) 1.004s (-1.7%) 0.572s 60 2.51x
💻 Local Express 0.466s (-16.9% 🟢) 1.004s (~) 0.539s 60 2.71x
💻 Local Next.js (Turbopack) 0.567s 1.004s 0.437s 60 3.30x
🌐 MongoDB Next.js (Turbopack) 1.041s 1.882s 0.842s 32 6.05x
workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.296s (-41.8% 🟢) 1.006s (~) 0.710s 90 1.00x
🐘 Postgres Nitro 0.314s (-36.8% 🟢) 1.006s (~) 0.693s 90 1.06x
🌐 Redis Next.js (Turbopack) 0.414s 1.004s 0.590s 90 1.40x
🐘 Postgres Next.js (Turbopack) 0.461s 1.006s 0.545s 90 1.55x
💻 Local Nitro 2.179s (-14.1% 🟢) 2.852s (-5.2% 🟢) 0.672s 32 7.35x
💻 Local Next.js (Turbopack) 2.182s 2.944s 0.762s 31 7.36x
💻 Local Express 2.223s (-11.6% 🟢) 2.821s (-6.3% 🟢) 0.598s 32 7.50x
🌐 MongoDB Next.js (Turbopack) 2.618s 3.007s 0.389s 30 8.83x
workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.578s (-29.3% 🟢) 1.014s (~) 0.436s 119 1.00x
🐘 Postgres Nitro 0.641s (-18.9% 🟢) 1.006s (~) 0.365s 120 1.11x
🌐 Redis Next.js (Turbopack) 0.770s 1.004s 0.234s 120 1.33x
🐘 Postgres Next.js (Turbopack) 0.957s 1.352s 0.394s 90 1.65x
🌐 MongoDB Next.js (Turbopack) 5.396s 6.011s 0.615s 20 9.33x
💻 Local Express 9.835s (-12.1% 🟢) 10.444s (-12.5% 🟢) 0.609s 12 17.00x
💻 Local Nitro 9.941s (-11.2% 🟢) 10.365s (-11.1% 🟢) 0.424s 12 17.19x
💻 Local Next.js (Turbopack) 11.011s 11.573s 0.562s 11 19.03x
Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.127s (+449.7% 🔺) 2.001s (+100.3% 🔺) 0.001s (-37.5% 🟢) 2.009s (+98.6% 🔺) 0.882s 10 1.00x
💻 Local Nitro 1.128s (+428.0% 🔺) 2.005s (+99.6% 🔺) 0.011s (-15.2% 🟢) 2.018s (+98.1% 🔺) 0.890s 10 1.00x
🐘 Postgres Nitro 1.135s (+453.5% 🔺) 2.000s (+100.1% 🔺) 0.001s (-6.7% 🟢) 2.010s (+98.7% 🔺) 0.875s 10 1.01x
💻 Local Express 1.140s (+472.4% 🔺) 2.005s (+99.6% 🔺) 0.013s (+3.3%) 2.020s (+98.4% 🔺) 0.881s 10 1.01x
💻 Local Next.js (Turbopack) 1.190s 2.003s 0.010s 2.017s 0.826s 10 1.06x
🐘 Postgres Next.js (Turbopack) 1.197s 2.001s 0.002s 2.011s 0.815s 10 1.06x
🌐 MongoDB Next.js (Turbopack) ⚠️ missing - - - - -
🌐 Redis Next.js (Turbopack) ⚠️ missing - - - - -
stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.482s (+135.3% 🔺) 2.006s (+99.3% 🔺) 0.003s (-10.4% 🟢) 2.022s (+97.7% 🔺) 0.540s 30 1.00x
🐘 Postgres Nitro 1.525s (+144.4% 🔺) 2.002s (+98.9% 🔺) 0.004s (+0.8%) 2.026s (+98.2% 🔺) 0.501s 30 1.03x
💻 Local Next.js (Turbopack) 1.670s 2.009s 0.011s 2.023s 0.352s 30 1.13x
🐘 Postgres Next.js (Turbopack) 1.682s 2.010s 0.004s 2.025s 0.343s 30 1.13x
💻 Local Express 1.705s (+125.2% 🔺) 2.011s (+95.4% 🔺) 0.010s (+8.5% 🔺) 2.202s (+111.8% 🔺) 0.498s 28 1.15x
💻 Local Nitro 1.713s (+104.2% 🔺) 2.010s (+98.6% 🔺) 0.010s (+4.8%) 2.201s (+97.2% 🔺) 0.488s 28 1.16x
🌐 MongoDB Next.js (Turbopack) ⚠️ missing - - - - -
🌐 Redis Next.js (Turbopack) ⚠️ missing - - - - -
10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.622s (-35.2% 🟢) 1.013s (-20.7% 🟢) 0.000s (+16.9% 🔺) 1.028s (-21.3% 🟢) 0.406s 59 1.00x
🐘 Postgres Nitro 0.681s (-29.7% 🟢) 1.047s (-16.1% 🟢) 0.000s (-58.6% 🟢) 1.059s (-15.8% 🟢) 0.378s 58 1.09x
🐘 Postgres Next.js (Turbopack) 0.817s 1.053s 0.000s 1.068s 0.251s 57 1.31x
💻 Local Nitro 1.331s (+8.8% 🔺) 2.015s (~) 0.000s (+66.7% 🔺) 2.017s (~) 0.686s 30 2.14x
💻 Local Express 1.394s (+13.8% 🔺) 2.015s (~) 0.000s (+10.0% 🔺) 2.017s (~) 0.623s 30 2.24x
💻 Local Next.js (Turbopack) 1.437s 2.013s 0.000s 2.016s 0.579s 30 2.31x
🌐 MongoDB Next.js (Turbopack) ⚠️ missing - - - - -
🌐 Redis Next.js (Turbopack) ⚠️ missing - - - - -
fan-out fan-in 10 streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.313s (-26.7% 🟢) 2.099s (-2.0%) 0.000s (-3.4%) 2.131s (-2.0%) 0.818s 29 1.00x
🐘 Postgres Express 1.349s (-23.9% 🟢) 2.145s (-1.5%) 0.000s (+Infinity% 🔺) 2.161s (-1.7%) 0.812s 28 1.03x
🐘 Postgres Next.js (Turbopack) 1.689s 2.225s 0.000s 2.273s 0.584s 27 1.29x
💻 Local Next.js (Turbopack) 2.563s 3.079s 0.001s 3.084s 0.521s 20 1.95x
💻 Local Nitro 3.092s (-8.7% 🟢) 3.840s (-4.8%) 0.000s (-76.6% 🟢) 3.844s (-4.8%) 0.752s 16 2.35x
💻 Local Express 3.227s (-6.9% 🟢) 4.031s (~) 0.000s (-66.7% 🟢) 4.033s (~) 0.806s 15 2.46x
🌐 MongoDB Next.js (Turbopack) ⚠️ missing - - - - -
🌐 Redis Next.js (Turbopack) ⚠️ missing - - - - -

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Nitro 14/21
🐘 Postgres Express 19/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 🐘 Postgres 18/21
Next.js (Turbopack) 🐘 Postgres 8/21
Nitro 🐘 Postgres 14/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

  • Local: success
  • Postgres: success
  • Vercel: failure

Check the workflow run for details.

Comment thread tarballs/scripts/pack.ts Outdated
Copy link
Copy Markdown
Member

@TooTallNate TooTallNate left a comment

Choose a reason for hiding this comment

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

The visual design is nice — featured workflow card, package-manager toggle, live filter, copy buttons, geist-inspired theme. Going to approve since the deployment shape doesn't change and this only affects the static index page.

That said: I'd push back on the hand-rolled HTML-in-JS approach for anything beyond a one-page diff. The new file is 857 lines, and the implementation has some smells that follow naturally from the medium:

  • The inline <script> block (~80 lines) isn't TypeScript-checked. It's inside a template literal in writeIndexHtml, so the compiler treats it as a string. Refactoring (rename a data- attribute, change a property name) won't surface type errors. The TS half references DOM via data- attributes that have to match by convention.
  • Two parallel "render the page" concerns — the pack.ts script handles both bundling-time logic (scanning packages, rewriting deps, packing tarballs) and presentation logic (HTML composition, CSS, client-side JS for PM toggle/filter/copy). Those were independent before this PR; conflating them makes both harder to evolve.
  • renderFeatured/renderRow and the inline applyPm/applyFilter script must stay in sync on attribute names, class names, and the catalog JSON shape. Right now there's a hand-rolled escapeHtml helper and the contract is "data-install-cmd must equal escapedName." Easy to break in a refactor; no tests catch the drift.
  • Hand-rolled escape helpers (escapeHtml) are correct here but would be unnecessary with JSX or a templating library.

A lightweight Vite + React (or Preact for smaller bundle) SPA would clean this up substantially. The shape I'd suggest:

tarballs/
├── package.json          # adds vite, react, @vitejs/plugin-react
├── vite.config.ts        # static build → public/
├── index.html            # entry HTML
├── src/
│   ├── main.tsx          # mount React
│   ├── App.tsx           # the page (~80 lines instead of ~600 of HTML+CSS+JS)
│   ├── catalog.ts        # types for the JSON catalog
│   └── styles.css        # the geist-inspired theme
└── scripts/
    ├── pack.ts           # data-only: scans packages, packs tarballs, writes
    │                     # public/catalog.json with the build context
    └── check-tarballs-smoke.mjs

pack.ts becomes ~140 lines (just the bundling logic), vite build produces the static page that fetches catalog.json, the React component reads it on mount and renders. State (active PM, search query) is useState. Build pipeline is pnpm pack → vite build, and Vercel's existing outputDirectory: "public" works unchanged.

DX wins: HMR while iterating on the design, TypeScript checks the JSX and event handlers, can pull in actual geist (or lucide-react) icons via npm instead of inlined SVG, hot-swap component libraries if the design evolves.

If you don't want to take that on now, this is fine as-is — the page works, the design looks good, and the deployment shape is preserved. But suggest filing a follow-up to migrate before adding the next 200 lines of HTML to this file.

Smaller things I noticed

  • getBuildContext uses process.env.VERCEL_GIT_* for SHA/branch/PR. Local builds get only the SHA/branch fallback via git rev-parse. Worth double-checking the labels on chips degrade gracefully when those env vars are missing — looks like they do (early-return paths with if (ctx.commitUrl)), but a quick local pnpm --filter tarballs build test before merge would confirm.
  • The catalog JSON is embedded via <script id="catalog" type="application/json">${escapeHtml(JSON.stringify(catalog))}</script>. JSON-inside-HTML-inside-template-literal is two layers of escape (the escapeHtml handles the outer, but the JSON itself can contain </script> if a package description has one — unlikely but possible). Safer to use JSON.stringify(catalog).replace(/</g, '\\u003c') for the embedded JSON, which escapes the dangerous bytes without affecting parseability.
  • formatBytes uses base-2 units (KB = 1024) but labels them as 'KB'/'MB' which by convention suggest base-10. Minor but KiB/MiB would be more precise (or use base-10 with 1000).
  • applyFilter runs on every keystroke against a re-querying Array.from(document.querySelectorAll('.pkg-row')). With ~25 packages this is fine; if the catalog grows substantially, debounce or pre-compute.

Test plan note

The PR description has 8 unchecked test-plan items. They're all manual visual checks (page renders, filter works, copy fires, etc.). Since the Vercel preview URL is the canonical way to validate this, they probably won't be checked off in advance — but worth at least running through them on the preview before merge.

Comment thread tarballs/scripts/pack.ts Outdated
Built ${ctx.commitUrl ? `from <a href="${escapeHtml(ctx.commitUrl)}" target="_blank" rel="noopener">${escapeHtml(ctx.shortSha)}</a>` : `from <code>${escapeHtml(ctx.shortSha)}</code>`} · ${escapeHtml(packages.length.toString())} packages totaling ${escapeHtml(formatBytes(totalSize))}
</footer>
</div>
<script id="catalog" type="application/json">${escapeHtml(JSON.stringify(catalog))}</script>
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.

Blocking: this line breaks all client-side interactivity.

Verified on the preview deployment with browser automation — every interactive feature is non-functional:

  • ❌ Package-manager toggle (clicking npm/yarn/bun does nothing)
  • ❌ Live filter (typing in search shows all rows)
  • ❌ Copy buttons (no clipboard write, no Copied state)
  • / keyboard shortcut (body keeps focus)

Root cause: escapeHtml(JSON.stringify(catalog)) HTML-encodes the JSON. The serialized catalog ends up in the page as:

<script id="catalog" type="application/json">[{&quot;name&quot;:&quot;@workflow/ai&quot;,&quot;escapedName&quot;:&quot;workflow-ai&quot;...

JSON.parse(textContent) then throws on the very first &quot; because &quot; isn't valid JSON syntax. The IIFE bails on its first line and none of the event listeners get attached. Confirmed live:

> JSON.parse(document.getElementById('catalog').textContent)
Uncaught SyntaxError: Expected property name or '}' in JSON at position 2 (line 1 column 3)

<script type="application/json"> content is treated as text by the HTML parser — it does NOT need HTML entity escaping. The only sequence that can break out of it is </script> (or </ in some legacy parsers). The narrowest correct fix is:

<script id="catalog" type="application/json">${
  JSON.stringify(catalog).replace(/</g, '\u003c')
}</script>

That escapes < bytes to \u003c in the JSON (legal per the spec — JSON allows \uXXXX escapes for any character), so </script> can't appear in the text and the JSON parses cleanly.

This was the third bullet in my "smaller things I noticed" section above — turns out it's not theoretical, it's the live bug breaking everything client-side. Should not merge until the page works in a real browser.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 3df48a7. Went with your replace(/</g, '\u003c') over the bot's </-only escape since it also covers a stray < inside, e.g., a package description. Verified locally that the embedded JSON parses cleanly out of the rendered HTML; preview deploy will validate the four interactive features end-to-end.

Copy link
Copy Markdown
Member

@TooTallNate TooTallNate left a comment

Choose a reason for hiding this comment

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

Updating my prior review to REQUEST_CHANGES. Ran the visual test plan against the preview deployment with browser automation and discovered that all client-side interactivity is broken — see the inline comment on pack.ts:696 for the root cause and fix.

The escapeHtml(JSON.stringify(catalog)) call HTML-encodes every " in the JSON to &quot;, so JSON.parse(textContent) throws on its first character. The IIFE bails immediately and none of the event listeners (PM toggle, search filter, copy buttons, / shortcut) get attached.

Test plan results from the preview at https://workflow-tarballs-git-pgp-tarball-page-improvements.labs.vercel.dev/:

  • ✅ Page renders correctly in light mode (visual layout, chips, featured card, package list)
  • workflow package appears as the featured card at the top
  • ✅ SHA / branch / timestamp / package count chips are present
  • Package-manager toggle doesn't update install commandsaria-selected stays on pnpm regardless of clicks
  • Live filter doesn't filter — typing in the search input shows all rows unchanged
  • Copy buttons don't fire — no Copied state, no clipboard write
  • / keyboard shortcut doesn't focus search — body keeps focus
  • (couldn't test direct download because earlier broken state interfered, but the <a download> is plain HTML and probably works)

So the page that ships looks correct visually but is functionally a static catalog — the toggle, filter, and copy buttons are all dead UI.

Quick fix at the call site (one line). After fix, all four interactive features should work. Worth re-testing on the next preview deployment.

The other points from my prior review (Vite + React refactor suggestion, the smaller TS-checkable concerns, KB vs KiB nit) all still stand and are non-blocking. Just this one issue blocks merging.

`escapeHtml(JSON.stringify(catalog))` was HTML-encoding every quote in
the embedded catalog JSON to `&quot;`, so `JSON.parse(textContent)` threw
on the first character and the IIFE bailed before attaching any event
listeners — package-manager toggle, search filter, copy buttons, and the
`/` shortcut were all dead UI on the deployed page.

`<script type="application/json">` content is treated as text by the HTML
parser; the only sequence that can break out is `</script>` (or `</`
in legacy parsers). Replace `<` with the JSON `<` escape, which is
legal per the JSON spec and prevents the breakout without needing entity
encoding.

Also switch `formatBytes` from `KB`/`MB` to `KiB`/`MiB` since the
divisor is 1024.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread tarballs/scripts/pack.ts Outdated
The smoke check broke in CI: `'workflow' tarball only has 0 files`.
Root cause is that `tar -tvzf` emits a different verbose layout on GNU
tar (Linux, what CI runs) vs BSD tar (macOS, where I tested locally) —
the parser only matched the BSD column ordering, so on Linux every line
was rejected and `fileCount` came out as 0.

Replace the shell-out with a small in-process tar reader using
`zlib.gunzipSync` + manual 512-byte block walk. ustar headers are
trivially structured (name at offset 0, octal size at 124, typeflag at
156, ustar prefix at 345). We emit regular files only (`typeflag` `0`
or NUL) and consume but skip pax extended headers (`x`/`g`) and GNU
long-name entries (`L`). Result is identical on every platform.

Verified locally: 206 files / 998413 bytes for `workflow.tgz` matches
`tar -tvzf` exactly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pranaygp and others added 3 commits May 4, 2026 19:31
The previous "What's inside?" view crammed nested directory groups,
proportional bars, and per-group file lists into a `<details>` inside
an already-narrow row. It was hard to read and harder to compare.

Replace it with the layout packagephobia uses on its result page:

- Two large headline metric tiles (Publish size / Unpacked size)
  with a big bold value, smaller unit, and small uppercase label.
  Modeled directly on packagephobia's `Stats` component but using
  our existing CSS variables so it tracks light/dark theme.
- A single sortable file table beneath. Default is size-descending so
  the contributors to package size are immediately visible. Click a
  header to flip direction or switch sort key. Sticky header keeps
  the columns visible inside the scrollable region.

Drop the `groupByTopLevel`, `ContentsGroup`, and bar-chart styles —
they were the source of the "hard to use" feedback and don't add
information that the flat sortable table doesn't already convey.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR replaces the old static tarballs index with a Vite/Preact single-page UI that reads a generated catalog.json, surfaces richer build/package metadata, and adds interactive install/copy/filter/download affordances for preview tarballs.

Changes:

  • Adds a Vite + Preact frontend for the tarballs index, including featured package rendering, filtering, package-manager toggles, and package contents breakdowns.
  • Extends scripts/pack.ts to emit catalog.json with build metadata, tarball sizes, unpacked sizes, and per-file listings.
  • Updates build/smoke-test plumbing so the tarballs deployment now publishes bundled SPA assets plus the generated catalog.

Reviewed changes

Copilot reviewed 12 out of 13 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
tarballs/vite.config.ts Configures Vite to build the SPA into public/ without deleting tarballs.
tarballs/turbo.json Expands build outputs to include SPA assets and catalog.json.
tarballs/tsconfig.json Adds TS config for the new frontend/build files.
tarballs/src/styles.css Adds the full styling/theme for the redesigned index UI.
tarballs/src/main.tsx Boots the SPA and loads catalog.json.
tarballs/src/icons.tsx Adds inline SVG icons used across the new UI.
tarballs/src/catalog.ts Defines shared catalog/install-command helpers for the frontend.
tarballs/src/app.tsx Implements the main tarballs UI, interactions, and package contents tables.
tarballs/scripts/pack.ts Generates tarballs plus the new metadata-rich catalog.json.
tarballs/scripts/check-tarballs-smoke.mjs Adds smoke coverage for catalog.json.
tarballs/package.json Switches build to pack.ts && vite build and adds frontend deps/scripts.
tarballs/index.html Adds the SPA entry HTML shell.
pnpm-lock.yaml Locks newly added frontend/tooling dependencies.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tarballs/package.json Outdated
"scripts": {
"build": "node scripts/pack.ts",
"build": "node scripts/pack.ts && vite build",
"dev": "vite",
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 1c48345. The new dev chains node scripts/pack.ts && vite, and I restructured the build layout to vite's conventional shape so it actually works in dev: public/ is a real vite public dir (pack writes tarballs + catalog.json there, vite serves them at root in dev), and dist/ is the production build output (set as Vercel's outputDirectory).

Comment thread tarballs/src/app.tsx
<SearchIcon />
<input
ref={searchRef}
type="search"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 1c48345. Added aria-label="Filter packages" to the input.

Comment thread tarballs/src/app.tsx Outdated
Comment on lines +178 to +185
<div class="pm-tabs" role="tablist" aria-label="Package manager">
{options.map((opt) => (
<button
key={opt}
type="button"
class="pm-tab"
role="tab"
aria-selected={value === opt}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 1c48345. Dropped role="tablist"/role="tab"/aria-selected and made these plain buttons with aria-pressed. Toggle buttons are the honest representation since we never implemented arrow-key roving focus. Each button also has an explicit aria-label (Show install commands for pnpm etc.).

Comment thread tarballs/src/app.tsx Outdated
Comment on lines +428 to +440
try {
await navigator.clipboard.writeText(text);
} catch {
const ta = document.createElement('textarea');
ta.value = text;
document.body.appendChild(ta);
ta.select();
try {
document.execCommand('copy');
} finally {
ta.remove();
}
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 1c48345. Refactored to a writeToClipboard helper that returns whether the write actually succeeded; the button only flips to Copied on success. If both navigator.clipboard.writeText and the execCommand fallback fail, it briefly shows a red Failed state instead.

Comment thread tarballs/src/app.tsx Outdated
<code class="pkg-cmd">{cmd}</code>
<div class="pkg-actions">
<CopyButton text={cmd} variant="icon" />
<a class="icon-btn" href={pkg.url} download aria-label="Download">
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 1c48345. Added an accessibleName prop to CopyButton and pass Copy install command for <pkg.name> from each row + the featured card. Same treatment on the download <a> link (Download <pkg.name> tarball).

Comment thread tarballs/src/main.tsx Outdated
if (!root) throw new Error('No #app root element');

try {
const res = await fetch('/catalog.json', { cache: 'no-store' });
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 1c48345 — dropped cache: 'no-store'. Each preview's URL is unique so the browser cache is the right thing to use.

- main.tsx: drop `cache: 'no-store'` from the catalog fetch. Each
  tarballs deployment is immutable per commit, so HTTP caching is
  appropriate; forcing no-store made every visit re-download the full
  catalog (which now includes per-package file lists).
- app.tsx (search input): add `aria-label="Filter packages"`. The
  visible label only contained an icon and placeholder, so screen
  readers had no name for the control.
- app.tsx (PmTabs): replace `role="tablist"` / `role="tab"` /
  `aria-selected` with plain buttons that use `aria-pressed`. The
  ARIA tab pattern requires arrow-key roving focus we never wired
  up; toggle buttons are the honest representation. Each button
  also gets an explicit `aria-label`.
- app.tsx (row buttons): include the package name in the accessible
  label of every per-row copy/download button (and on the featured
  card too), so the screen reader buttons/links list distinguishes
  them. Added an `accessibleName` prop to `CopyButton`.
- app.tsx (CopyButton): only flip to the "Copied" state when the
  write actually succeeded. Both the modern `navigator.clipboard`
  path and the `execCommand` fallback can fail; the new
  `writeToClipboard` helper returns success and the button shows a
  short "Failed" state if both paths fail.

# Make `pnpm dev` work from a clean checkout

The previous `dev: vite` couldn't actually serve the page because
`/catalog.json` 404s and the SPA boots into the error fallback.

Restructure the build layout to vite's conventional shape:
- `public/` is now a true vite public dir — pack writes tarballs and
  catalog.json there. In dev, vite serves these at the root.
- `dist/` is the production build output (vite copies public/ into it
  and adds index.html + assets/).
- `vercel.json#outputDirectory` switches from `public` → `dist`.
- `turbo.json` outputs updated to match.
- `dev` chains pack before vite so the catalog exists when the dev
  server starts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@socket-security
Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​preact/​preset-vite@​2.10.59910010090100
Addednpm/​preact@​10.29.110010010094100

View full report

Copy link
Copy Markdown
Member

@TooTallNate TooTallNate left a comment

Choose a reason for hiding this comment

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

Pranay took the Vite + Preact refactor recommendation from my prior review and ran with it. The new shape matches what I sketched almost exactly:

tarballs/
├── index.html              # 20-line entry
├── vite.config.ts          # 16-line config
├── tsconfig.json           # strict, jsxImportSource: preact
├── scripts/
│   ├── pack.ts             # 317 lines, data-only — packs tarballs, writes catalog.json
│   └── check-tarballs-smoke.mjs   # now also validates catalog.json shape
└── src/
    ├── main.tsx            # 26 lines — fetch + mount
    ├── app.tsx             # 550 lines — typed Preact JSX
    ├── catalog.ts          # types + small helpers
    ├── icons.tsx           # 8 inline SVG icons
    └── styles.css          # 794 lines, geist-inspired theme

Bundle ships as dist/assets/index-*.js at 26.25 kB (9.69 kB gzipped) — small for a Preact SPA with sortable tables, copy-buttons, search, and a PM toggle. CSS is 11.19 kB / 2.73 kB gzipped.

What I verified

  • Local build works: pnpm turbo build --filter=tarballs runs pack.ts then vite build, producing 26 tarballs in public/, a catalog.json matching the typed shape in src/catalog.ts, and a dist/ ready to deploy.
  • TypeScript is clean: pnpm exec tsc --noEmit from tarballs/ passes with the strict config (strict, noUnusedLocals, noUnusedParameters, isolatedModules).
  • Deployed preview serves correctly: https://workflow-tarballs-git-pgp-tarball-page-improvements.labs.vercel.dev/catalog.json returns the expected payload with build context (sha, branch, PR number, GitHub-linked commit/branch/PR URLs) and 26 packages with full file listings + sizes. Each package's url is the immutable per-deployment hash URL (e.g. https://workflow-tarballs-ch8zhxmen.labs.vercel.dev/workflow-ai.tgz), so the catalog is stable even if visited via the git-branch URL.
  • JS bundle contains real Preact: read the bundle, confirmed the App tree renders with hooks (useState, useEffect, useMemo, useRef), addEventListener('keydown', …) for the / shortcut, navigator.clipboard.writeText + execCommand('copy') fallback for CopyButton, and the data-copied / data-failed state machine.
  • Smoke check is stronger now: in addition to verifying tarballs are gzip-magic-prefixed and the index page responds with text/html, it now fetches catalog.json and asserts the workflow package has a non-trivial fileCount — a useful diagnostic for "packages weren't built before pack."

Concerns from my prior review — all addressed

  1. The hand-rolled HTML-in-JS approach — gone. JSX is TypeScript-checked end to end (event handlers, prop types, catalog shape).
  2. Two parallel render concerns merged into one file — split clean. pack.ts is now data-only (~317 lines, all bundling/tar-reading). The presentation layer is in src/.
  3. renderRow / applyPm / applyFilter had to stay in sync via string conventions — gone. State lives in React-style hooks, props are typed.
  4. Hand-rolled escapeHtml — gone. JSX handles escaping.
  5. escapeHtml(JSON.stringify(catalog)) breaking client interactivity — moot. The catalog is a separate /catalog.json static file fetched at runtime, so there's no inline-JSON encoding question.
  6. KB/MB labels with base-2 math — fixed. formatBytes now emits B/KiB/MiB.

Copilot review items — all addressed in 1c48345e6

  • a11y: aria-label="Filter packages" on search input; PM tabs use plain buttons with aria-pressed (no half-implemented role="tablist"); per-row copy buttons get accessibleName="Copy install command for ${pkg.name}" instead of a generic label.
  • Robustness: writeToClipboard returns a boolean so failed writes show "Failed" rather than a misleading "Copied" success state.
  • DX: dev script chains pack.ts && vite; dropped cache: 'no-store' since the catalog URL is per-deployment-immutable.

Smaller improvements landed along the way

  • Tar reader rewrite (6ae7736): replaced tar -tvzf shell-out with an in-process gunzip + 512-byte block walker. Handles GNU long-name (L) entries and pax headers, and works identically on macOS BSD tar and Linux GNU tar. Sorting files by descending size before emitting is a nice touch for the per-package "what's inside" view.
  • PackagePhobia-style SizeStats widget: two big tiles (publish size + unpacked size) with split value/unit rendering. Looks good and reads better than a one-liner.
  • FileTable with sortable columns: ARIA aria-sort on <th>, indicator arrows, click-to-toggle direction. Strips the package/ prefix from displayed paths.

Tiny things — non-blocking

  • pack.ts:117: updateDeps builds rewrite URLs as https://${process.env.VERCEL_URL}/${escapedName}.tgz directly, while baseUrl (defined at line 97) uses the VERCEL_URL ? https://… : '' fallback. Local builds without VERCEL_URL produce package.json deps like https://undefined/.... Pre-existing on main, not introduced by this PR. Not a real-world issue (nobody installs from a local pack output) but noting for a follow-up cleanup if you want consistency.
  • catalog.ts and pack.ts duplicate the same TS interfaces (TarballFile, PackedPackage, BuildContext, Catalog). Drift risk is low since they're both in the same package, but if you want to eliminate it, catalog.ts could be the single source of truth and pack.ts could import type from it. Fine to leave.
  • SizeStats is rendered for every package in the list, not just the featured one — but it's behind a <details> so it doesn't blow up initial render. The 26 expanded <details> plus the FileTable for each would render a lot of DOM if expanded all at once; in practice, users open one or two. Not blocking.

Verdict: approve

Approving and clearing my prior CHANGES_REQUESTED. The redesign hit every concern from my last two reviews plus the Copilot feedback, the build pipeline is cleaner than what was there before, and the deployment shape is preserved (public/<escaped-name>.tgz URLs unchanged, smoke checks pass). Nice work on the followups.

@pranaygp
Copy link
Copy Markdown
Contributor Author

pranaygp commented May 5, 2026

@v0 @vercel can you make a PR to address the remaining follow ups from @TooTallNate's review comments that we didn't get to before merge of this one

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-stable Cherry-pick this PR to the stable branch when merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants