- Add `@tanstack/react-pacer` and swap both hand-rolled `setTimeout`-based debounce patterns (project-name commit in `project-setup.tsx`, module search fetch in `site-search.tsx`) for `useDebouncedValue`; the search effect now runs directly against the debounced value with an abort controller rather than nesting the fetch inside the timer
- Extract `useGracePeriod` from `file-preview.tsx` into `src/hooks/use-grace-period.ts` so it can be reused independently
- Hoist `fileSelection`, `lastFileRef`, `activePath`, and `preview` above the reseed effect in `file-preview.tsx` so the effect can reference `defaultPath` and `lastFileRef` without stale-closure issues; expand ancestor directories when a hash-driven selection lands so the highlighted row is visible in the tree
- Replace per-framework template files (`eslint.config.next.mjs`, `eslint.config.tanstack.mjs`) with a single `eslint.config.mjs` that branches via Handlebars `{{#if peers.framework}}` conditionals; add `handlebars` as a direct dependency to `@stanza/registry`, `apps/cli`, and `apps/web`
- Extend the template render context with a `peers: Partial<Record<CategoryId, string>>` map so any template can inspect the active framework, auth provider, etc.; thread it through `buildRenderContext`, `synthesizeTemplates`, the CLI `codemod-runner`, and the web `getBuilderState` server function
- Extract and export `activePeerIds(manifest, appId?)` from `@stanza/registry` to derive the peer map from a manifest (app-scoped for `home: app` categories); add `selectedPeerIds(selections)` in `apps/web/src/lib/selection.ts` as the URL-params-derived equivalent
- Add integration tests verifying framework-conditional eslint-prettier output for `default`, `next`, and `tanstack-start` adapters; expand resolver tests for `activePeerIds` app-scoping and for modules that declare both a `default` adapter and a framework-specific override
- Extract `getAllModules()` into `registry-modules.server.ts` as a singleton promise that loads the full module catalog once per server process; `getBuilderState` now pays the I/O cost exactly once per deployment rather than on every request
- Wrap `renderPreviewImpl` with an LRU cache (max 500 entries, keyed by `filePath + source`) so repeated Shiki highlight calls for unchanged files are returned from memory
- Add `lru-cache` as a direct dependency
- Replace the single `index` root-loader prop with `{ registry, docs }` and split `SiteSearch` to accept both; empty state renders the docs page list and all modules from the root payload, while typed queries fan out to new `/api/search/docs` (Fumadocs Orama) and `/api/search/modules` (server-side Orama) API routes so the client bundle never carries a full search index
- Add `api.search.docs.ts` wired to the Fumadocs search server and `api.search.modules.ts` that builds an in-memory Orama index over the full registry on each request (including richer fields than the client-side summary); add `docs-index.functions.ts` to enumerate docs pages for the root loader's empty-state list
- Unify module and doc results into a `Hit` discriminated union and a `Group` abstraction; `SearchRow` replaces `SearchResult` and dispatches navigation to `/registry/$category/$id` or an arbitrary docs URL; arrow-key selection and `⌘K` hotkey are preserved
- Pre-seed the hotkey label with the macOS format (`{ platform: "mac" }`) on first render to eliminate the null→label layout shift, then upgrade to the platform-correct value in a `useEffect`; add `@orama/orama` as a direct dependency
- Add `@vitejs/plugin-rsc` and flip `components.json` to `rsc: true`; annotate all client-side UI components, builder pieces, docs sidebar/TOC, search, theme toggle, and footer with `"use client"` so the RSC boundary is explicit
- Replace the global `useRouterState({ select: s => s.isLoading })` in `Builder` with a `useMatch`-scoped `isFetching` selector so navigating away doesn't flash the file-preview overlay before the page unmounts; thread the result as an `isReloading` prop into `FilePreview` (removing the `useRouterState` import there entirely)
- Extract `apps/web/src/server/docs-meta.functions.ts` as a dedicated server functions module for the docs route; refactor `docs.$.tsx` to consume it
- Trim verbose inline comments throughout `file-preview.tsx`, `builder/index.tsx`, and `project-setup.tsx` (no logic changes)
- Extend `buildHead` / `HeadOutput` with a `scripts` field for `application/ld+json` injection and a `markdownPath` field for `<link rel="alternate" type="text/markdown">`; add `getWebSiteJsonLd`, `getTechArticleJsonLd`, and `getSoftwareSourceCodeJsonLd` helpers; wire them into the home, docs, and module-detail routes
- Add an `og.docs.$` route that generates per-page OG images for docs pages using the page title/description; rename `og.$slot.$id` → `og.m.$slot.$id` to match the `/m/` route structure; regenerate the route tree
- Replace `@vercel/og` with `@takumi-rs/image-response` in `og-card.server.tsx`; remove `@vercel/analytics` and its `<Analytics />` usage from the root layout
- Add `content/docs/agents.mdx` covering SKILL.md installation, agent guidance rules, common errors, and an end-to-end scaffolding script; link from the docs index and reorder `meta.json` to surface it before `concepts`
- Replace every bare `stanza <verb>` example in `getting-started.mdx` and `cli.mdx` with `npx stanza-cli <verb>` to match the published package name and avoid global-install assumptions
- Update `registry.mdx`, web builder components (`project-setup`, `slot-cards`), `header`, `footer`, `index` route, and `styles.css` with miscellaneous copy and UI refinements; bump `vite.config.ts` and add one new `package.json` dependency
- Add `src/components/posthog-provider.tsx` — initializes the `posthog-js` browser SDK once (guarded by `VITE_PUBLIC_POSTHOG_KEY` and `posthog.__loaded`) with history-based pageview capture, pageleave tracking, and session replay + autocapture disabled; wraps the app shell in `__root.tsx` via `<PostHogProvider>`
- Add `src/lib/analytics.ts` with a `useAnalytics` hook that returns a stable `capture` callback bound to the PostHog singleton (no-ops when the key is absent or the SDK is uninitialized) and a `selectionProperties` helper that flattens slot/addon selections into a consistent event property shape across events
- Wire `capture` into `Builder` (`builder_module_selected`, `builder_module_deselected`, `builder_addon_toggled`) and `CommandPreview` (`builder_command_copied` with `package_manager`, `command`, `name_customized`, `module_count`, and per-slot properties); add an `onCopied` callback prop to `CopyButton` to fire the capture after a successful clipboard write
- Add `src/server/posthog.server.ts` — a lazy singleton that constructs a `posthog-node` `PostHog` client from `VITE_PUBLIC_POSTHOG_KEY`/`VITE_PUBLIC_POSTHOG_HOST` and returns `null` when unconfigured; refactor `/api/events` to use this client instead of a raw `fetch` to PostHog's `/batch/` endpoint, and flush via `waitUntil` from `@vercel/functions` so the serverless function stays alive past the response without blocking it
- Remove `cmdk` and the generated `src/components/ui/command.tsx` shadcn primitive; replace `CommandDialog` / `CommandInput` / `CommandList` / `CommandItem` in `SiteSearch` with a plain `Dialog` + `<input>` + `<button>` elements so the component owns its own rendering without the cmdk abstraction layer
- Add `@tanstack/react-hotkeys` and replace the hand-rolled `keydown` listener with `useHotkey(HOTKEY, …)`; use `formatForDisplay` (deferred to mount to avoid SSR hydration mismatch) so the `<Kbd>` badge shows `⌘K` on macOS and `Ctrl+K` on Windows/Linux
- Implement client-side fuzzy filtering via a local `matches` helper (splits query on whitespace, checks every term against a joined haystack of group/id/label/description) and drive the list from derived `filtered` / `groups` / `flat` memos
- Add keyboard navigation — `ArrowDown`/`ArrowUp` cycle `activeIndex` through the flat item list, `Enter` selects; `onPointerMove` on each row syncs hover → `activeIndex`; the active row scrolls into view via a `data-index` attribute + `scrollIntoView`
- Reset `query` and `activeIndex` when the dialog opens; reset `activeIndex` whenever `query` changes
- Pin `fumadocs-ui` to `npm:@fumadocs/base-ui@^16.9.0`
- Add `react-resizable-panels` dependency and a `ResizablePanelGroup` / `ResizablePanel` / `ResizableHandle` shadcn-style UI primitive in `src/components/ui/resizable.tsx`
- Replace the static `grid-cols-[260px_1fr]` layout in `FilePreview` with a `ResizablePanelGroup` (horizontal on `sm`+, vertical on mobile) so the tree and preview panes are user-draggable; default split is 35 / 65
- Add a `useMediaQuery` hook (SSR-safe, defaults to the wide layout to avoid hydration mismatch) to switch the panel group orientation at the `sm` breakpoint
- Fix `PreviewPane` height propagation — both the empty state and the code pane now use `h-full`/`flex-1`/`min-h-0` so the scroll container fills the resizable panel instead of relying on `max-h` clamps
- Install `fumadocs-core`, `fumadocs-mdx`, and `fumadocs-ui`; add `fumadocs-mdx/vite` plugin and wire it first in `vite.config.ts` so MDX files are processed before TanStack Start
- Add `source.config.ts` (single `defineDocs` collection pointing at `content/docs`), `src/lib/source.ts` (Fumadocs loader with `/docs` base URL), and `src/lib/layout.shared.tsx` (disables Fumadocs' own nav, theme switch, and search toggle so the site shell owns those concerns)
- Add `src/components/mdx.tsx` exporting `getMDXComponents`/`useMDXComponents` built on `fumadocs-ui/mdx` defaults; declare global `MDXProvidedComponents` for type-safe component overrides
- Add `src/routes/docs.$.tsx` — splat route that runs a server function to look up the page + serialize the page tree, preloads the client MDX bundle, renders `<DocsLayout>` inside `<RootProvider theme/search disabled>`, and feeds `buildHead` for per-page SEO
- Add `content/docs/index.mdx` (Introduction), `content/docs/getting-started.mdx`, `content/docs/cli-reference.mdx`, and `content/docs/meta.json` controlling sidebar order
- Add `collections/*` path alias pointing at `.source/` in `tsconfig.json`; add `.source/` to `.gitignore`
- Import `fumadocs-ui/css/neutral.css` and `fumadocs-ui/css/preset.css` plus the Tailwind source glob in `styles.css`
- Add a "Docs" nav link in `<Header>` pointing to `/docs/$` with an empty splat
## Vercel Web Analytics Installation
Successfully installed and configured Vercel Web Analytics for the stanza-web project following the official Vercel documentation.
### Changes Made
#### 1. Package Installation
- Installed `@vercel/analytics` version 2.0.1 using pnpm
- Package added to `apps/web/package.json` dependencies
#### 2. Analytics Component Integration
Modified `apps/web/src/routes/__root.tsx`:
- Added import: `import { Analytics } from "@vercel/analytics/react";`
- Added `<Analytics />` component to the RootComponent's JSX, placed just before the `<Scripts />` component
- This ensures analytics tracking is enabled across all pages of the application
#### 3. Framework-Specific Implementation
The project uses TanStack Start (React-based framework) with Nitro as the server. According to the Vercel documentation, the correct integration approach for React applications is:
- Import `Analytics` from `@vercel/analytics/react`
- Add the `<Analytics />` component to the root layout component
- This integration automatically handles page view tracking for client-side route transitions
### Files Modified
- `apps/web/package.json` - Added @vercel/analytics dependency
- `apps/web/src/routes/__root.tsx` - Added Analytics component import and usage
- `package.json` - Added tsx as a dev dependency for build tooling
- `pnpm-lock.yaml` - Updated with new dependencies
### Verification
✅ Type checking passed (tsc --noEmit)
✅ Linting passed (oxlint) - 0 warnings, 0 errors
✅ Tests passed (vitest) - All 10 tests passing
✅ No breaking changes introduced
✅ Existing code structure preserved
### Next Steps for Activation
To enable analytics tracking in production:
1. Deploy the application to Vercel
2. Enable Web Analytics in the Vercel dashboard (Analytics section)
3. Analytics data will start appearing after user visits
### Notes
- The Analytics component is lightweight and only tracks page views by default
- Custom event tracking is available with Pro/Enterprise plans
- The component automatically detects the environment (development vs production)
- Integration follows React best practices by placing the component at the root level
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
- `registry-base.server.ts` now short-circuits in `import.meta.env.DEV` with a direct `fs/promises.readFile` from `public/registry/` — Nitro only mounts the real `assets:registry` storage in its own `nitro` environment at build time; in Vite's `ssr` environment `useStorage` resolves to an empty stub that returns null for every key, which was silently breaking all registry reads during `pnpm dev`
- Add `predev` script in `apps/web/package.json` pointing at the same `prepare-registry.sh` so the registry is populated before the dev server starts (previously only `prebuild` ran it, meaning a fresh checkout would 404 on all registry data in dev)
- Rename `copy-registry.sh` → `prepare-registry.sh` to better reflect that it may also build the registry before copying; update all references in `package.json` and CLAUDE.md
- Add `/m/$slot/$id` route (`apps/web/src/routes/m.$slot.$id.tsx`) with `module-detail.ts` server function — full adapter switcher (peer chip rows), deps/devDeps/scripts table, env-var table, templates list with click-to-expand Shiki preview, and a "Try it" copy-command bar; new detail components land under `apps/web/src/components/detail/`
- Add ⌘K search popover (`apps/web/src/components/search/site-search.tsx`) backed by `apps/web/src/lib/module-search.ts` — fuzzy-matches id/label/description/slot exactly as the CLI does; wired into the header
- Add dynamic OG image generation via `@vercel/og` + Satori: `server/routes/og/[slot]/[id].ts` renders a per-module card, `server/routes/og.ts` renders the default fallback; both served as Nitro route handlers with appropriate CDN cache headers; OG React components in `apps/web/src/server/og-card.tsx`
- Add `server/routes/sitemap.xml.ts` (enumerates `/` + all `/m/$slot/$id` URLs from registry index) and `public/robots.txt` pointing at it
- Add `apps/web/src/lib/seo.ts` — `buildHead()` helper that emits `title`, `og:*`, `twitter:*`, and `canonical` tags; consumed by `__root.tsx`, `index.tsx`, and `m.$slot.$id.tsx`
- Extract `ModuleLogo` into `apps/web/src/components/module-logo.tsx` — shared between slot-cards and the new detail page; remove the duplicate from `slot-cards.tsx`
- Add new shadcn-style UI primitives: `command.tsx` (cmdk wrapper), `dialog.tsx`, `input-group.tsx`, `kbd.tsx`, `popover.tsx`, `textarea.tsx`
- Add `apps/web/src/server/registry-base.ts` (filesystem reads to avoid SSR loopback-fetch deadlock), `registry-index.ts`, and `module-detail.ts` as the server-side data layer
- Add `@vercel/og` and `cmdk` to `apps/web` dependencies; add `vitest run` as `pnpm test` in web package
- Mark all corresponding TODO items done
- Replace Bun runtime with Node.js + `tsx` for the CLI dev path (`#!/usr/bin/env node` shebang, `tsx watch ./src/bin.ts` for `pnpm dev`) and `tsdown` for publishing — emits `dist/bin.mjs` as plain ESM; `create-stanza` gets the same treatment with its own `tsdown.config.ts`
- Add `.github/workflows/ci.yml` running lint, fmt-check, registry build, web build, typecheck, vitest, CLI bundle build, and a smoke-test (`node dist/bin.mjs --version` / `--help`) on push/PR with concurrency cancel-in-progress
- Add `apps/cli/src/commands/commands.test.ts` (196 lines, vitest) with unit tests for `add`, `init`, `list`, `remove`, and `search` command handlers; add `apps/cli/vitest.config.ts`
- Replace `apps/web/scripts/copy-registry.ts` with a pure-bash `copy-registry.sh` — no Node/tsx prerequisite on the deploy target; drop the TS version and its `predev` auto-build hook (`pnpm registry:build` must be run before the first web build)
- Add root `pnpm test` and `pnpm check-types` scripts that fan out to all workspaces via turbo; CI uses these as canonical entry points
- Update `tsconfig.json` files for `packages/registry`, `packages/codemods`, and `packages/create-stanza` to stay source-only (`noEmit: true`); add `publishConfig` pointing at `dist/` in publishable `package.json` files so workspace dev resolution uses `./src/` while published tarballs resolve `./dist/`
- Update CLAUDE.md toolchain invariants: document Node-only runtime, tsdown build pipeline, and per-workspace `dist/` paths; remove stale `bun apps/cli/src/bin.ts` dev command and all "Bun runs the CLI directly" references
- **Module logos**: `registry-build.ts` now auto-detects `logo.svg` / `logo-light.svg` + `logo-dark.svg` in each module directory and inlines them as `mod.logo` (string or `{ light, dark }`) — no `module.ts` declarations needed; first-party SVGs added for all current modules
- **Codemod bundling**: `registry-build.ts` bundles each module's `codemods/index.ts` via `Bun.build` (ESM, externals: `@stanza/codemods`, `ts-morph`, `node:*`) and embeds the output as `mod.codemodBundle`; the CLI runner materializes it to `node_modules/.cache/stanza-codemods/` so externals resolve normally, replacing the old "first-party only" approach
- **Web builder UI**: split monolithic `builder.tsx` into `builder/{index,slot-cards,command-bar,file-preview}.tsx`; add `Header`, `Footer`, `ModeToggle`, `ThemeProvider`; scaffold shadcn `Badge`, `Card`, `DropdownMenu`, `Input`, `Separator`, `Sonner`, `Tabs`, `Tooltip` components
- **Server-side Shiki**: `apps/web/src/server/highlighter.ts` keeps a warm Shiki instance; `builder-state.ts` (`createServerFn`) computes selections from URL params and pre-renders `{ light, dark }` HTML per file — Shiki is never imported client-side
- **Selection logic**: new `apps/web/src/lib/selection.ts` with full unit tests
- **copy-registry script**: `predev`/`prebuild` hook copies `dist/registry/` → `apps/web/public/registry/` so the same-domain registry path resolves without a separate CDN
- Add `shiki`, `sonner`, `@pierre/trees`, `@fontsource-variable/geist-mono` to `apps/web`; update oxfmt ignore patterns and swap `react-hooks` for `react-perf` + `promise` in oxlint web config
- configure Tailwind v4 via `@tailwindcss/vite` with a full CSS variable theme (light/dark) and Geist Variable font
- add shadcn `components.json` (base-lyra style, tabler icons) and scaffold `Button` component using `@base-ui/react`
- wire in `TanStackDevtools` + `TanStackRouterDevtoolsPanel` in the root layout
- switch path alias from `~/` to `@/` across tsconfig, routes, and components
- add `clsx`/`tailwind-merge` `cn` utility, `vitest`, `prettier-plugin-tailwindcss`, and other supporting deps