vibe dex▮
// products + experiments built with vibe coding.
← all

changelog

Notable changes to vibe dex, newest first. Semantic versioning; each release is dated and git-stamped. Generated from the repo's CHANGELOG.md.

[Unreleased]

Removed

  • gen-seed (the seed generator) is retired. seed.json is edited directly now, fully hand-curated. The generator's monorepo-derived regenerate had drifted too far from the curated set to reconcile (86 vs 55 entries at retirement, and growing), so it was deleted rather than kept running-but-untrusted. No product-visible change; seed.json itself is unchanged.

Added

  • Builder-profile enrichment. A top-repos showcase (the highest-starred non-fork repos each builder has authored, with stars/language/description), a profile-README bio fallback for an unclaimed builder with no self-declared bio (from their github.com/<u>/<u> special repo, always attributed), and a bounded, TTL-aware cron pass that populates per-entry stars/language/last-commit for builders outside the existing profile-refresh rotation.
  • Public roadmap page. /roadmap shows what's queued, in progress, and shipped for vibe dex, in a kanban-shaped board. Sourced from a small hand-curated list (not Han's private cross-repo ops backlog, which would leak unrelated personal tasks); an empty column reads "nothing here right now" instead of a gap.
  • Builder-profile data-viz polish. The contribution heatmap now carries a less/more legend, each year's calendar shows a self-scaled monthly sparkline (blank for months that haven't happened yet in the current year), the hero is a two-column layout (identity next to the stat scoreboard), the GitHub panel is grouped into shipped / contributions / stack, the language bars show an honest "other" bucket instead of silently reporting %-of-top-5 as %-of-all, and the recent-activity feed shows a per-event glyph plus relative time instead of a bare event/repo line.

Fixed

  • A returning contributor can edit their own entries again. On approve, an entry submitted by an ALREADY-existing builder was left with a null builder_id, so the owner-scoped PATCH /api/entries/:id (ownership = builder_id matches the session) always 403'd them out of editing/pinning their own repeat entries. approveSubmission now sets builder_id on every approved entry.

Security

  • Session/PII responses are never cached. GET /api/settings/export (contains the owner's email), /auth/me (CSRF token), and /settings (CSRF token + privacy flags) now set Cache-Control: private, no-store, so a shared proxy / browser disk cache / over-broad Cache Rule can't serve one builder's PII to another.
  • The legacy submit path no longer double-lists a repo. repo.submit now canonicalizes repo_url and dedups against pending submissions (not just published entries), matching the flat path , so github.com/o/r vs GitHub.com/O/R/ (or two identical pending submissions) can't create duplicate listings for one repository.
  • Takedown/archive works for every submitted handle. The legacy submit path stored builder_github verbatim (mixed-case), but archive / takedown / self-delete match the normalized handle, so an entry submitted as NewPerson became a permanent, un-removable orphan in the public feed. repo.submit now normalizes the handle at the boundary (like the flat path), closing the consent bypass.
  • Submitted URLs, tags, and handles are length-bounded. HttpUrlSchema caps at 2048 chars, tags get a per-tag length cap + an array-count cap, and builder_github is capped at 64 on every submit path, so a submission can't store a multi-MB URL / tag / handle that would be re-served on every public read (normalizeGithub strips bad characters but not length).
  • Free-text fields are length-bounded on every write path. The owner-edit routes (PATCH /api/builders/:handle, PATCH /api/entries/:id) AND the submit paths (SubmissionInputSchema, the legacy NewEntrySchema) now cap headline / ai_blurb / bio / what_it_does / problem_solved, so neither an authenticated owner nor a submission-then-approve can store an unbounded blob that would be served in full on every public /api/entries read.
  • Account export no longer leaks a third party's email. GET /api/settings/export stripped the submitter-supplied contact_email from the exported submissions rows: because anyone can file a pending entry for another builder's handle with their own email, that field was not necessarily the exporting builder's own PII. The builder's own contact remains in their identity.email.
  • URL scheme allowlist on submissions. repo_url / site_url / image_url now reject javascript: / data: / vbscript: schemes at the write boundary (http(s) only), closing a stored-XSS vector where a submitted site_url would execute when rendered into a profile link.
  • Pinning is operator-only on every path. The legacy POST /api/entries / /submit path no longer accepts a pinned flag from anonymous submitters (it was silently honored into the pending row and onto the entry at approve); it is dropped at the schema and hard-coalesced to false, matching the flat /api/v1/submissions path. Operators still pin via the authenticated edit path.

Fixed

  • Federation listing fetch enforces its size cap before buffering. fetchListing now checks Content-Length and stream-reads with a running byte count that aborts past the cap, instead of downloading the whole body and then checking its length, so a malicious builder's oversized vibedex.json can't be materialized into memory during the operator sync.
  • Migration completeness (restore-path drift). Seven columns that predate the additive-migration runner (entries.builder_id/pinned, builders.headline/ai_blurb, submissions.pinned/headline/ ai_blurb) were added to LIFECYCLE_COLUMNS, so a live D1 restored to a pre-2026-06-15 point (Time Travel) and brought forward with migrate:remote gets them too, instead of 500ing the approve-new -builder path on a missing builder_id. A reverse-direction contract test now fails if any future fresh-schema column is added without its migration entry.
  • /health now canaries every table, not just builders. The liveness probe selects from identities and listing_cache too, so a live D1 missing a migrated column/table trips 503 instead of staying green and masking the outage (the failure mode that hid this cycle's schema drift). CI now also runs migrate:remote before wrangler deploy, so the live schema can no longer lag the deployed code.
  • Live schema drift that 500'd the site. CI deploys code but never migrated the live D1, so columns and a whole table (listing_cache) that shipped in code were missing on production, and every data + profile read 500'd (while /health stayed green and hid it). Added bun run migrate:remote, a one-command, idempotent, non-destructive runner that brings the live D1 up to the deployed schema (creates missing tables, adds missing columns) and applied it. A bin/smoke script now walks the full production surface so this class of drift is caught immediately.

Added

  • First-login onboarding + designed empty states. After claiming a profile, the owner sees a one-time checklist (add a headline, say how you use AI, add a first entry) that tracks completion from their own public fields; a "skip for now" dismisses it permanently (owner-only + CSRF). The empty directory, empty profile, and no-search-results views now carry designed copy with a submit call to action instead of the old dev seed hints.
  • Account settings (/settings). A signed-in builder owns their account: edit profile fields, control visibility (hide from the directory/feed/profile/leaderboard, or opt out of the leaderboard only, flags respected by every read surface), sign out everywhere (stateless-session revoke), export all their data as JSON, and delete their account self-serve (a typed confirm, then the lifecycle purge, real deletion, not a soft flag). Anonymous and non-owner visitors get none of it.
  • Launch pack: legal, SEO, analytics. Plain-language /terms + /privacy (what data is held, how deletion works, no cookies/email/ads), a /sitemap.xml + /robots.txt (private paths excluded), complete baseline meta/OG + canonical on every page, and cookie-free Cloudflare Web Analytics (no consent banner). A docs/launch-checklist.md enumerates the launch-day truths.
  • Admin moderation queue (/admin). A browser moderation surface gated by Cloudflare Access at the edge AND re-verified in the Worker (RS256 over the team JWKS: audience + issuer + expiry checks, fail closed). It approves / rejects submissions, processes takedowns, and archives / unarchives builders through the SAME core paths as the operator CLI (one moderation brain), never exposing contact_email. No app-level admin auth: zero admin passwords or sessions in the app.
  • Production hardening (abuse + failure hygiene). Every mutating endpoint is rate-limited (429 with a Retry-After); the anonymous submission path sits behind Cloudflare Turnstile (server-verified, with a dev bypass when no key is set); every response carries security headers (CSP, HSTS, nosniff, X-Frame-Options: DENY); a global error boundary returns a clean page/JSON with no stack or internals (and the legacy submit route no longer leaks raw validation issues); a /health endpoint backs a synthetic uptime probe; and D1 is exported to R2 daily with a matched, rehearsed restore path.
  • Builder-owned listings (GitHub as your data node). A builder can own a vibedex.json on their GitHub profile repo; the dex resolves, validates, and caches it (via the operator-run bun run sync-listings), then renders their profile from it with builder-owned precedence (falling back to the dex's own data when absent). An invalid or deleted listing never clobbers the last good one. atproto-shape: the data lives on your node, the dex is the AppView , no account to run, no server to host. (Ingest is operator-invoked for now; auto-scheduling it on the cron is deferred pending an egress review of fetching every builder's external URL each tick.)
  • Build-signal leaderboard. A transparent, outcome-first BUILD SIGNAL ranks builders on the web (/leaderboard), the API (GET /api/leaderboard), and MCP. It scores shipped / used / sustained / reach from public data (token burn is excluded), shown as a band + a per-signal breakdown with the formula visible, never a bare number and never a personal ranking. The criteria are public in docs/ranking-standard.md; the weights are config-driven (a re-weight is a config edit, not a rewrite).
  • Sign in with GitHub to claim + edit your page. A builder can "Sign in with GitHub" (read:user scope only), which claims their profile, and a signed-in builder edits their own headline, blurb, and entries directly, no operator approval for their own content. The anonymous submission + operator- approve path still works, and an anonymous visitor sees no change. Sessions are httpOnly + Secure + SameSite cookies; CSRF is enforced on the OAuth state and every edit; all secrets are op://-managed. (Sign-in is inert until the OAuth app secrets are provisioned; the read + submit paths are unaffected.)
  • Exact contribution calendar + streak with a GitHub token. When the optional GITHUB_BOT_TOKEN is set, a builder's contribution calendar, yearly totals, and streak come from GitHub's exact GraphQL API, and every GitHub read is authenticated (5000/hr instead of 60/hr). Without a token (or with an invalid one) the dex silently falls back to the public HTML scrape, exactly as before. The token is fine-grained, read-only, minimal-scope, public-data only, and never committed (op:// reference).
  • Share cards now unfurl as a PNG. A profile's og:image points at a real 1200x630 PNG rendered at the edge (/b/:handle/og.png), so a shared /b/:handle link shows a designed card on X / Slack / Discord / iMessage (those platforms do not unfurl an SVG og:image). The PNG is rasterized deterministically from the same mono card SVG via resvg-wasm with an embedded JetBrains Mono (SIL OFL) face; the /og.svg route stays as the fallback and the on-page card.
  • Builder-data lifecycle (onboard / refresh / archive) + a public submission API. The directory is now live data, not a static seed. A scheduled hourly cron re-fetches each builder's public GitHub signal (deep profile + per-repo stars/language/last-commit) into D1, keyed by the builder's immutable GitHub id, with two freshness clocks, a last-good-wins flap guard, and a dead-handle retirement (a 404'd account is dropped from the rotation after 3 strikes). Anyone can propose a builder + project via POST /api/v1/submissions (flat, validated, consent-required, idempotent on Idempotency-Key, opaque sub_<ulid> id) or the MCP submit_entry tool; contact_email is stored server-side only and never read back. Removal is one unauthenticated call: POST /api/v1/builders/:handle/takedown, which an operator approves to archive the builder (hidden from every read) and hard-purge their PII. Operators run approve / reject / archive / takedown as a CLI over the live D1 (no public admin surface). Repo stars/languages now span all of a builder's repos (paginated), and the repo count is the true GitHub count, not a 100-cap. /openapi.json documents the submission path and carries a real version. Migrated non-destructively over the existing D1 (no drop-and-reseed).
  • Multi-builder seed. The directory seeds three builders now, not one: tamnd (Tam Nguyen Duc) and monotykamary (Tom) join tieubao, each with a few of their top public repos as entries (ascii-gif, ccrawl-cli, brain; localterm, inline-yaml, folio). Curated external builders live in a new packages/core/seed.external.json, merged into the seed by gen-seed and kept separate from the auto-generated Han set for privacy review. Their data is static seed for now; a live refresh flow is backlogged.
  • Builder profile v2. A hierarchy + data pass on /b/:handle. Orgs render as a linked greyscale logo grid (using the already-cached org avatar, full-color on hover, with a generated placeholder fallback for avatar-less orgs). The stat band becomes a boxed "scoreboard" (~2rem numbers, hairline cell rules). The profile entry list splits into a Featured (pinned) block + a secondary "All work (N)" list with a N entries · P products · E experiments count header, each row carrying its own repo/site links + tags + a detail link. New splitFeatured + entryKindCounts spine helpers; the render stays a dumb projector. CSS + inline SVG + already-cached data only (edge-safe). The home / feed keeps its flat list unchanged.

Changed

  • Contribution-number label. The stat-band total is now labelled contributions · last 12mo so it no longer silently conflicts with the calendar's per-year totals (N contributions in YYYY). Two numbers, two honest labels.

Fixed

  • Streak-0. When the current streak is 0, the 0 days current chip (sitting next to a strong longest) is dropped; only longest renders. The page no longer shows a self-sabotaging zero.

0.4.0 · 2026-06-15 · acbb351

Added

  • Public /changelog page. A nav-linked changelog page rendered edge-safe from a committed, generated TS const (parsed from this file at build time, no markdown parser on the edge).
  • Release automation: changelog drift gate + auto-deploy CI. bun run changelog:draft reads the conventional commits since the last released section and drafts/updates a top ## [Unreleased] block (grouped Added / Changed / Fixed) for a human to polish; bun run changelog:check FAILS when a diff touches packages/** code without a CHANGELOG.md note, so a code change can no longer ship without a changelog entry. A repo-root GitHub Actions workflow runs the full gate (test, typecheck, biome, changelog:check, build:worker) on push to main and deploys the Cloudflare Worker, tolerating the benign zone-routes auth error. The workflow is inert until the two Cloudflare repo secrets are set.

Fixed

  • Builder handle corrected to tieubao.
  • Three flagship entries pinned: tide, bot-fleet, vps-mon.
  • The builder name in the entry list now links to the builder's profile.

0.3.0 · 2026-06-15 · 9fdba71

Added

  • Stable opaque builder id under the GitHub handle. Each builder gets a stable opaque id that is the durable key, with the GitHub handle as the human-facing alias; the model survives a handle rename without a re-key.
  • Profile hero + headline stat band. A profile-top hero with a headline stat band (GitHub signal summarized as numbers), GitHub social / followers, a markdown bio (edge-safe inline render), and a claim badge. The legacy token-burn stat was removed.
  • Activity-depth showcase. A merged-PR reel, a multi-year contribution calendar with a year toggle, a current streak, and language % bars, all from public GitHub signal, cached.
  • Pinned / featured projects + self-declared headline & AI blurb. A builder can pin/feature projects and self-declare a headline and an AI blurb, supplied via submit and applied on operator approve (no login).
  • Builder directory + sort/search + vanity /@handle. A mono builder directory at /builders with sort (activity / recency / tech) and a handle/tech search, plus a vanity /@handle URL that 302-redirects to the canonical profile.
  • Per-profile OG share-card + embeddable badge. A deterministic, edge-safe SVG OG share-card per profile (wired into og:image) and an embeddable "Featured on vibe dex" badge with a copyable snippet.

0.2.0 · 2026-06-13 · d972bc6

Added

  • Identity model. A GitHub-canonical identities table (one per builder), the auth anchor distinct from the public profile. Carries email (server-side only) + claim_status (unclaimed default). /api/builders/:github now resolves claim_status through identity. OAuth/claiming is deferred; the model leaves room for it without a re-model. normalizeGithub canonicalizes handles (lowercase). gen-sql emits identity rows into the D1 seed.
  • Repo-sourced rich detail. The detail page assembles itself from the entry repo's README into themed sections. A cached entry_docs table holds sanitized section HTML (the markdown parser runs with html:false, so raw HTML / javascript: links are inert). Extraction is a cached extract-docs step (token-aware split, immune to # in code fences), kept out of the Workers bundle. Degrades gracefully when a repo has no README or is private. bun run extract-docs populates the cache.
  • Deep GitHub profile. Builder profiles show public GitHub signal: a contribution heatmap (mono grey ramp), repo stats, top languages, orgs, and recent activity. All public + unauthenticated (no token), cached in profile_cache; the HTML scrape is isolated to one non-barrel adapter and kept out of the Workers bundle. Each source degrades independently. bun run fetch-profiles populates the cache.
  • Generative placeholders. Imageless entries and avatar-less builders render a deterministic near-monochrome SVG pattern (generatePattern, a pure id-seeded function, no deps) instead of a blank box: wired into the entry detail slot, list thumbnails, and builder avatars, each falling back to a real image when present.
  • Info / onboarding pages. About, How it works, Access the data, and a Submit guide, each with an in-style ASCII diagram, linked from a nav. The access page documents the real HTTP API / MCP tools / CLI commands, with a route test that fails if the docs drift from the code.

0.1.0 · 2026-06-13 · de09400

Added

  • v1 read slice. An agent-native catalog readable over web, HTTP API, MCP, and CLI from one data core.
  • Entry model v2 + real-data seed. The richer entry model (builder, what-it-does, problem, links, kind, tags) and a seed of real entries.
  • Builder profiles. Per-builder profile pages.
  • Design guideline applied to the web surface. The mono / light visual language.
  • Discovery filters + agent export protocols. Filter by kind / tag / query; /openapi.json and /schema.json derived from the one Zod source.
  • GitHub repo enrichment. Cached, graceful repo metadata enrichment.
  • Submit + operator curation. A submit flow (web form, API, MCP, CLI) where an entry lands pending and an operator approves it before it goes live.
  • Deploy. Live on Cloudflare Workers + D1 at dex.han.ws, recreatable from the repo + 1Password alone.