Commit Graph
20 Commits
Author SHA1 Message Date
jake 6767362641 refactor: replace hand-rolled debounce with @tanstack/react-pacer and extract useGracePeriod
- 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
2026-05-25 14:23:33 -04:00
jake e28eba2702 feat: add Handlebars template engine with peer-aware render context and consolidate eslint-prettier into a single adaptive template
- 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
2026-05-25 13:35:27 -04:00
jake 9953570814 perf: add process-level module catalog cache and LRU preview cache
- 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
2026-05-23 23:08:35 -04:00
jake 9b2546ed23 feat: add unified docs + module search with server-side Orama and Fumadocs
- 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
2026-05-23 20:58:22 -04:00
jake 5364c8a4f0 feat: add RSC support and scope builder loading overlay to current route match
- 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)
2026-05-23 19:59:01 -04:00
jake 9d1b79f6ae feat: add JSON-LD structured data, per-page OG images for docs, and swap OG image renderer
- 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
2026-05-23 16:33:23 -04:00
jake 7d1fa8e657 docs: add agents guide, update CLI examples to npx stanza-cli, and polish web UI
- 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
2026-05-23 15:09:40 -04:00
jake 78f8e44721 chore: migrate to vite+ 2026-05-22 18:37:24 -04:00
jake 0db48d5dbc chore: drop .npmrc and bump nitro, posthog-js, and posthog-node 2026-05-22 16:21:53 -04:00
jake ab5ef7ec4a feat: add client-side PostHog analytics and refactor /api/events to use posthog-node
- 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
2026-05-21 21:32:20 -04:00
jake 1eb0b9c5dd feat: replace cmdk-backed CommandDialog with a bespoke search dialog and @tanstack/react-hotkeys
- 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`
2026-05-21 19:26:48 -04:00
jake 7b4e99c19d feat: replace fixed-size file-preview grid with draggable react-resizable-panels layout
- 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
2026-05-21 18:35:54 -04:00
jake cca42071ee feat: add Fumadocs-powered /docs section with Introduction, Getting Started, and CLI reference pages
- 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
2026-05-21 18:08:39 -04:00
vercel[bot]andVercel bdf2f1f028 Install and configure Vercel Web Analytics (#1)
## 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>
2026-05-21 15:54:27 -04:00
jake 7464bf3346 fix: read registry files from disk in dev mode, add predev hook, and rename prepare script
- `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
2026-05-21 14:55:24 -04:00
jake 5ca679cf32 feat: add module detail pages, ⌘K search, dynamic OG images, sitemap, and SEO groundwork for apps/web
- 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
2026-05-20 19:36:34 -04:00
jake 9460bceffc feat: migrate CLI to Node/tsx/tsdown, add CI pipeline, and wire root-level test + typecheck turbo tasks
- 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
2026-05-20 18:04:11 -04:00
jake 5e83f07b83 feat: add module logos, bundle codemods into registry, and build out the web UI
- **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
2026-05-20 13:20:01 -04:00
jake 6e88a1f5e9 feat: add Tailwind CSS v4, shadcn/ui, and TanStack devtools to apps/web
- 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
2026-05-20 12:33:49 -04:00
jake b74ffb9d1a feat: initial commit 2026-05-20 11:52:53 -04:00