137 Commits
Author SHA1 Message Date
jake 890673d334 fix: polish docs sidebar mobile disclosure, search result layout, and dropdown close behavior
- Restyle the mobile docs `<details>` disclosure with a bordered summary, animated `IconChevronRight` chevron, and a top-border separator below the open panel instead of a bottom-border above it
- Tighten `NavLink` padding (`py-1`) and add `rounded` so the active highlight matches the app's pill style; reduce separator top-padding from `pt-4` to `pt-3`
- Add `closeOnClick` to every `DropdownMenuRadioItem` in `ModeToggle` and `PackageManagerSelect` so the menu closes after a selection
- Strip the `moduleGroup/id` monospace trailing span from `SearchResult` rows to reduce visual noise
- Remove the per-item inline padding override on Shiki `.line` elements (`pl-0! pr-0!`) so code blocks no longer get double inset
- Reduce docs article top padding on mobile (`pt-2`) while keeping `md:pt-8` on wider viewports
2026-05-21 19:51:10 -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 27f90c1765 refactor: replace Fumadocs-owned docs chrome with custom DocsSidebar / DocsToc components
- Add `src/components/docs/docs-sidebar.tsx` — a bespoke sidebar that reads the Fumadocs page tree and renders a `<NavList>` of links using TanStack Router's `<Link>`; shows a `<details>` disclosure on mobile and a sticky `<aside>` rail on `md`+, active item highlighted via the site's `--accent` token
- Add `src/components/docs/docs-toc.tsx` — a bespoke table of contents that wraps Fumadocs' `AnchorProvider`/`useActiveAnchor` in a plain `<ul>` with depth-based padding; hidden below `xl`
- Replace `<DocsLayout>` / `<DocsPage>` / `<DocsTitle>` / `<DocsDescription>` in `docs.$.tsx` with the custom components and plain `<article>` markup so the docs page is fully owned by the site shell rather than Fumadocs' layout primitives; `baseOptions()` and `layout.shared.tsx` are deleted
- Simplify the Fumadocs CSS token override in `styles.css` — the `#nd-sidebar` specificity hack is no longer needed now that Fumadocs renders no sidebar of its own
- Move `bin` from the top-level field into `publishConfig` in both `apps/cli/package.json` and `packages/create-stanza/package.json` so the binary entries only apply to the published artifact, not local workspace resolution
- Add a `tslib` → `tslib/tslib.es6.mjs` alias in `vite.config.ts` to avoid Rolldown's CJS interop issue with tslib's UMD bundle in Vite 8
- Set `[--trees-border-radius-override:0px]` on the file-tree in `FilePreview` so selected rows render as flush rectangles rather than rounded pills
2026-05-21 18:58:22 -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 c96c0c9379 fix: restore file-tree selection after resetPaths and use colored branded PM logos
- `FilePreview` now captures the previously-selected path before calling `model.resetPaths` (which clears selection) and re-selects it afterward; falls back to `package.json` or the first file when the old path no longer exists — the preview pane's active file is now always highlighted in the tree
- Wire `--trees-accent-override`, `--trees-selected-bg-override`, `--trees-selected-fg-override`, and `--trees-padding-inline-override` CSS vars onto the tree so the selected row uses the app's accent token instead of a hardcoded blue, and tighten the inline inset to 8px with matching `py-2` vertical padding
- Replace the monochrome `currentColor` single-path SVG marks in `PackageManagerSelect` with colored branded logos for npm (red), pnpm (amber/grey grid), bun (illustrated bunny), and yarn (blue)
2026-05-21 18:28:41 -04:00
jake 0efdeac33a feat: extract appendEnvVar/synthesizeEnvExample/synthesizeManifest into @stanza/registry and surface .env.example + stanza.json in web preview
- Add `packages/registry/src/synthesize.ts` with three pure (fs-free) exports: `ENV_EXAMPLE_HEADER` (the managed comment stanza writes at the top of `.env.example`), `appendEnvVar` (idempotent env-var upsert — same logic the CLI used inline, now shared), and `synthesizeEnvExample` / `synthesizeManifest` which walk resolved slots + add-ons in apply order to reproduce the files stanza writes at install time
- Refactor `packages/codemods/src/env.ts` — `addEnvVar` now delegates to `appendEnvVar` from `@stanza/registry` and just wraps the fs read/write; the inline split/splice logic is deleted; `ENV_EXAMPLE_HEADER` replaces the hardcoded string in `init.ts`
- Wire `synthesizeManifest` and `synthesizeEnvExample` into `builder-state.functions.ts` so the web builder preview tree now includes a synthesized `stanza.json` and `.env.example` alongside the already-synthesized `package.json` files — the preview matches what `stanza init` actually writes byte-for-byte
- `regions` is intentionally left empty in the synthesized manifest (it's internal per-file codemod bookkeeping that would require duplicating codemod-internal path logic to reproduce faithfully)
2026-05-21 18:11:02 -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
jake 5e966f7143 feat: resolve ^/~ dep ranges to latest satisfying npm version at install time
- Add `apps/cli/src/lib/npm-version.ts` with `resolveRange(name, range)` and `resolveRanges(deps)` — fetches the abbreviated packument from the npm registry (or `STANZA_NPM_REGISTRY`), picks the `semver.maxSatisfying` version, and re-attaches the original modifier (e.g. `^1.6.11` → `^1.8.3`); non-`^`/`~` ranges, workspace specs, and tags are left verbatim without hitting the network; results are cached per package name for the process lifetime so a multi-module `init` fetches each package at most once
- Wire `resolveRanges` into `applyModule` in `codemod-runner.ts` — both `dependencies` and `devDependencies` are re-pinned before being written to `package.json`; dry-run skips the network entirely since nothing is written
- Add `STANZA_NO_NPM_LOOKUP=1` env var to bypass lookups and fall back to verbatim ranges (network errors also fall back per-entry); set it in `commands.test.ts` `beforeEach` to keep integration tests hermetic
- Add `npm-version.test.ts` covering caret/tilde bumping, pass-through of other range shapes, `STANZA_NO_NPM_LOOKUP` bypass, network error fallback, non-ok response fallback, nothing-satisfies fallback, per-package caching, and scoped name URL encoding
- Document the new behavior and env vars in `skills/stanza-cli/SKILL.md` under a new "Dependency Versions" section
2026-05-21 17:47:54 -04:00
jake d1498a9ff6 docs: add stanza-cli installable agent skill and reference it from AGENTS.md
- Add `skills/stanza-cli/SKILL.md` — a self-contained agent skill covering the published CLI surface: runtime contract, non-interactive `init`/`add`/`remove`/`list`/`search` usage, slot vs. add-on semantics, safety flags (`--dry-run`, `--dangerously-allow-dirty`, `STANZA_REGISTRY`), error handling, and agent rules; intended for agents that don't have access to this source repo
- Add `skills/stanza-cli/agents/openai.yaml` with display name, short description, and default prompt for OpenAI agent tooling
- Update `AGENTS.md` with a "Distributed Skill" section noting that `skills/stanza-cli/SKILL.md` must be kept in sync whenever CLI flags, command behaviour, slot/add-on names, or published package names change
2026-05-21 17:39:52 -04:00
jake 71b55d8bc8 docs: rename CLAUDE.md to AGENTS.md for broader agent compatibility 2026-05-21 17:27:07 -04:00
jake c044a56487 feat: guard mutating commands against a dirty git working tree
- Add `apps/cli/src/lib/git.ts` with `worktreeStatus()` (runs `git status --porcelain` in the target dir; treats missing git / non-repo as clean) and `ensureCleanWorktree()` which prints a formatted error listing the dirty paths and returns false so callers can bail before writing anything
- Wire the guard into `cmdInit`, `cmdAdd`, and `cmdRemove` — all three check the worktree before the interactive wizard or any file mutations; `--dry-run` skips the check since nothing is written
- Add `--dangerously-allow-dirty` boolean flag (registered in `mri` and documented in the `--help` output) to override the refusal with a warning when callers explicitly opt in
2026-05-21 17:24:27 -04:00
jake 74abfecc53 feat: package-manager selector, CommandPreview component, and package.json default in file tree
- Add `apps/web/src/lib/package-manager.ts` — a `usePackageManager` hook backed by `localStorage` (key `stanza:pm`) that persists the user's preferred package manager across sessions; defaults to `npm`
- Add `<PackageManagerSelect>` (Base UI `Select`) rendering npm / pnpm / bun / yarn options with their respective icons, sized to match the `h-8` command bar
- Extract `<CommandPreview name selections addons>` — owns the PM picker, `<pre>` command text, and `<CopyButton>`; `buildCommand` gains an optional `pm` parameter so the emitted command is prefixed with the chosen manager (e.g. `pnpm create stanza …`); `project-setup.tsx` and `try-it.tsx` both drop their inline `<pre>` + `CopyButton` in favour of `<CommandPreview>`
- `ProjectSetup` now receives `selections` + `addons` and delegates command construction to `CommandPreview` — the parent (`builder/index.tsx`) no longer calls `buildCommand` itself and no longer passes a pre-built string down the tree
- `TryIt` on the module detail page (`m.$slot.$id.tsx`) updated the same way: accepts `name / selections / addons` and renders `<CommandPreview>`
- `FilePreview` defaults the active path to `"package.json"` when that file exists in the tree (the synthesized root package.json is the most useful at-a-glance summary), falling back to `filePaths[0]` otherwise
- Remove the `slot/id · vX.Y.Z` monospace footer from non-disabled `ModuleCard` buttons — it added noise without much value
- Add `selection.test.ts` coverage for the `pm`-parameterised `buildCommand` variants
2026-05-21 17:19:31 -04:00
jake da9f8a8b69 refactor: extract package-json builders into @stanza/registry and surface synthesized files in web preview
- Move `mergeInstallFields`, `rootPackageJson`, `appPackageJsonBase`, and `slotPackageJsonBase` out of `codemod-runner.ts` / `init.ts` and into a new `packages/registry/src/package-json.ts` module; all consumers now import from `@stanza/registry` — the CLI no longer hand-rolls inline object literals for workspace roots or app packages, and the bun/npm "read-parse-mutate-write workspaces" dance collapses into the single `rootPackageJson()` call that emits `workspaces` for non-pnpm managers
- Add `synthesizePackageJsons(resolved, resolvedAddons, { name })` which produces a `path → PackageJson` map for every `package.json` the codemod runner would write (root, app, each slot package); `builder-state.functions.ts` calls it and appends the results to `previewFiles` so the web builder tree shows merged deps/scripts as they'll actually appear on disk — previously package.json files were entirely absent from the preview
- Command bar `<pre>` in `project-setup.tsx` and `try-it.tsx` gets `h-8 flex items-center no-scrollbar` so the command text aligns with the copy button and hides the scrollbar gutter; `CopyButton` drops the `className="h-auto!"` override that was fighting the button's own size token
- Builder grid columns gain `min-w-0` to prevent flex children from overflowing their track on narrow viewports
2026-05-21 17:00:28 -04:00
jake 19b5e51075 feat: add-on module kind, testing-vitest + testing-playwright, and multi-choice CLI/web support
- Introduce a `kind: "slot" | "addon"` discriminated union on `Module` — slot modules carry `slot`, add-on modules carry `category` (one of `KNOWN_ADDONS`). `isAddon()` narrows the union; `moduleGroup()` returns the slot-or-category key. Add-on categories (`testing`, `tooling`, `deploy`, `email`, `monorepo`) live in `KNOWN_ADDONS`/`ADDON_CATEGORIES`, deliberately disjoint from `KNOWN_SLOTS` so the resolver never lets add-ons participate in peer dispatch (they can still declare a one-way `peers` + per-framework adapters)
- Ship the first two add-ons: `testing-vitest` (jsdom + RTL, `test`/`test:watch` scripts, per-framework adapters) and `testing-playwright` (per-framework `webServer`, `test:e2e`/`test:e2e:ui` scripts — disjoint from vitest's so region claims don't collide); both under `registry/modules/testing-*/`
- Manifest gains `addons: Partial<Record<AddonCategoryId, StanzaAddonRecord[]>>` (`.default({})` keeps existing `stanza.json` valid); resolver gains `addonOrder` for deterministic codemod sequencing
- CLI: `stanza add <slot|category> <module>` accepts both slots and add-on categories; duplicate-id guard for add-ons instead of the single-fill slot guard; `stanza remove <slot|category> [id]` removes a specific add-on by id (or the whole slot); `stanza list` shows add-ons alongside slots; `stanza init --yes` gains `--testing vitest,playwright` flag; wizard prompts multi-select for add-on categories
- Web builder: add-on slots render as checkbox multi-select cards (`SlotSection` detects `isAddon` and switches to `<CheckboxCard>`), `?testing=vitest,playwright` comma-joined URL param, `builder-state.functions.ts` parses/serializes per-category add-on lists, OG and sitemap routes updated for add-on module paths
- Codemod runner branches on `isAddon` to resolve add-on records from `manifest.addons[category]` rather than `manifest.modules[slot]`; region ownership keys on `module.id` so co-existing add-ons writing the same file at disjoint dot-paths never conflict
- Add `commands.test.ts`, `selection.test.ts`, `manifest.test.ts`, and `resolver.test.ts` coverage for add-on add/remove/list flows and schema validation
2026-05-21 16:34:09 -04:00
vercel[bot]GitHubVercel <vercel[bot]@users.noreply.github.com>
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 743c356b17 refactor: extract CopyButton, memoize callbacks, and add oxc/vitest oxlint plugins
- Extract a shared `CopyButton` component (`apps/web/src/components/copy-button.tsx`) — the clipboard-copy + toast + tooltip pattern was duplicated verbatim in `command-bar.tsx` and `try-it.tsx`; both now import `<CopyButton value={command} />`
- Wrap `setName` and `setSelection` in `useCallback` in `builder/index.tsx`, `onDraftChange` in `command-bar.tsx`, and `ModuleCard`'s `onClick` in `slot-cards.tsx` — these were recreated on every render, causing unnecessary child re-renders
- Move `summaries.filter(m => m.slot === slot)` into `SlotSection` and pass `summaries` + `slot` instead of a pre-filtered `modules` array; return `null` from `SlotSection` when the filtered list is empty — removes an extra allocation in the parent and colocates the null-check with the data
- Memoize `dangerouslySetInnerHTML` objects in `file-preview.tsx` and `templates-list.tsx` as `useMemo(() => ({ __html: ... }))` instead of deriving a plain string and constructing a fresh object inline on every render
- Hoist the GitHub `<a>` element in `header.tsx` to a module-level constant so the Base UI `Button render=` prop receives a stable reference across renders
- Add `oxc`, `eslint`, and `vitest` plugins to `.oxlintrc.json` for both the root config and the `apps/web/**/*.tsx` override
2026-05-21 15:46:56 -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 cadef10226 fix: debounce project-name input, preserve tree expansion on reset, add loading overlay, and make file-preview fill available height on desktop
- `CommandBar` now holds a local `draft` state and pushes `onNameChange` only after a 300 ms debounce — each call navigates, reruns the loader, and rebuilds the file tree, so firing per keystroke was wasteful; external `name` changes (history nav, reset) flow back into `draft` via a sync effect
- `FilePreview.useEffect` snapshots which directory nodes were expanded before calling `model.resetPaths` and replays them via `initialExpandedPaths` — previously `resetPaths` collapsed the entire tree on every module selection change
- A semi-transparent spinner overlay (`useRouterState({ select: s => s.isLoading })`) renders over the file-preview grid while the route loader reruns, replacing the jarring "flash of stale content" with a subtle refresh indicator
- Right-column section in `builder/index.tsx` gains `lg:flex lg:h-[calc(100vh-6rem)] lg:flex-col` so it fills the viewport and `FilePreview` can grow to consume remaining space; card, inner grid, and `PreviewPane` scroll container all get `lg:flex-1 lg:min-h-0 lg:max-h-none` to lift the fixed `sm:max-h-[480px]` cap on large screens; `EmptyState` gets the same flex treatment
- Both `navigate` calls pass `resetScroll: false` so typing a project name or toggling a module doesn't jump the viewport back to the top
- `--trees-bg-override:transparent` is set on `<FileTree>` so the shadow-DOM tree background inherits the card surface rather than painting its own opaque fill
2026-05-21 14:45:21 -04:00
jake 70a41f67e8 feat: mobile/responsive pass, reactive resolvedTheme, Nitro serverAssets registry, and inline disabled-card reasons
- Builder layout: `CommandBar` is rendered twice in `builder/index.tsx` — a `lg:hidden` instance above the slot cards so phones see the copy-able command first, and the original inside the `hidden lg:block` right column; it's stateless so duplication is cheap
- File-preview height: tree pane is a fixed `h-[180px]` below `sm` (the virtualized `@pierre/trees` tree collapses to 0 without a definite height) and `sm:h-full` on wider screens; code pane is capped `max-h-[360px]` / `sm:max-h-[480px]`
- Command overflow: `command-bar.tsx` and detail `try-it.tsx` both use `whitespace-pre-wrap break-words` + `text-[11px] sm:text-xs` so the full `pnpm create stanza …` command wraps cleanly on narrow viewports
- Header density: search trigger in `site-search.tsx` collapses to an icon-only button below `sm` (label + `⌘K` hint `hidden sm:inline`, `min-w` / wider padding gated to `sm:`)
- `ThemeProvider` now exposes a reactive `resolvedTheme` computed post-mount — the old per-component `useResolvedTheme` returned `"light"` during SSR and never re-rendered, so for `system`/dark users the file tree and Shiki blocks rendered `github-light` on a dark page; `FileTree` is also re-keyed on `resolvedTheme` so its shadow-DOM palette resets on theme change; same fix applied to `templates-list.tsx`
- Disabled slot cards render an inline why-disabled reason via `describeError` in `slot-cards.tsx`: `missing-peer` → "Pick a {Slot} module first.", `incompatible-peer` → "Doesn't pair with {peer} (your {Slot} pick)." — inline (not just a `title` tooltip) so it works on touch
- Registry SSR: `public/registry/` is now registered as a Nitro `serverAssets` dir in `vite.config.ts`; `registry-base.server.ts` reads via `useStorage("assets:registry")` instead of direct `fs` calls — JSON is embedded in the server bundle at build time so SSR works on Vercel where `public/` is CDN-only, and the loopback-fetch deadlock is still avoided
- Mark all "UI polish / responsiveness" TODO items done; update CLAUDE.md to document the serverAssets pattern; clean up REGISTRY.md `ui` slot section
2026-05-21 14:20:56 -04:00
jake f0a65906fa fix: auto-build registry in copy-registry.sh when dist/registry/ is missing and update docs
- `copy-registry.sh` previously exited with an error if `dist/registry/` wasn't present (e.g. Vercel's clean checkout); now it builds the registry on-demand — preferring `bun` locally and falling back to `pnpm exec tsx` on node-only deploy targets
- Update CLAUDE.md dev-command description to reflect the new self-healing behavior and drop the "run `pnpm registry:build` first" prerequisite
- Add a "UI polish / responsiveness" section to TODO.md covering mobile layout gaps in the builder, file-preview, command bar, header, slot cards, devtools overlay, and module detail page
2026-05-21 14:00:09 -04:00
jake 0faa301737 refactor: migrate OG/sitemap to TanStack Start server routes, adopt *.functions.ts/*.server.ts file conventions, and fix palette crash + stale file-tree + theme mismatch
- Delete `apps/web/server/routes/` (standalone Nitro dir) and replace with TanStack Start server routes colocated in `src/routes/`: `og.index.ts` and `og.$slot.$id.ts` use `createFileRoute(...).server.handlers.GET` to serve `@vercel/og` image responses; `sitemap[.]xml.ts` does the same for XML — no separate `serverDir` config or Nitro required
- OG image URLs are extensionless (`/og`, `/og/$slot/$id`) so they aren't swallowed by Vite static-asset handling; crawlers read the `image/png` content-type header
- Adopt `apps/web/src/server/` file-organization convention: `*.functions.ts` for `createServerFn` wrappers safe to import anywhere, `*.server.ts(x)` for server-only modules (Shiki, `@vercel/og` card components, fs registry reads), plain `*.ts` for client-safe types — rename `builder-state.ts` → `builder-state.functions.ts`, `module-detail.ts` → `module-detail.functions.ts`, `registry-index.ts` → `registry-index.functions.ts`, `og-card.tsx` → `og-card.server.tsx`, `registry-base.ts` → `registry-base.server.ts`; extract pure Shiki logic into `highlighter.server.ts`; leave `highlighter.ts` as the client-safe re-export
- Fix CMD+K palette crash: `DialogContent` was rendering children outside any `<Command>` context; move `<DialogHeader>` inside `DialogContent` and wrap children in `<Command>` in `command.tsx`
- Fix stale file-tree on selection change: `useFileTree` builds its model once (lazy `useState` init) and ignores later `paths` prop changes; call `model.resetPaths(filePaths)` in a `useEffect` keyed on `filePaths` so the tree reflects new selections
- Fix file-tree theme mismatch: tree's shadow-DOM palette was auto-detecting via `prefers-color-scheme` and diverging when the user picked a non-OS theme; pass `themeToTreeStyles({ type: resolvedTheme })` as `style` to `<FileTree>` so it tracks the app's ThemeProvider
- Fix Base UI native-`<button>` warning in `header.tsx`: add `nativeButton={false}` to the GitHub link `<Button>` that renders an `<a>` tag
2026-05-21 13:56:22 -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 ad622c8579 feat: wire Changesets publish pipeline, mark internal packages private, and fix add-vite-plugin multiline indent
- Initialize Changesets: `.changeset/config.json` with `@changesets/changelog-github` for PR-linked changelog entries, `access: "public"`, `baseBranch: "main"`, `updateInternalDependencies: "patch"`
- Add `.github/workflows/release.yml` — on push to main, `changesets/action@v1` opens a "Version Packages" PR when changesets are queued; merging that PR re-runs the workflow and calls `pnpm release` (build CLI + create-stanza, then `changeset publish`); provenance attestations enabled via `id-token: write` + `NPM_CONFIG_PROVENANCE=true`; requires `NPM_TOKEN` in repo secrets
- Add root `pnpm changeset` and `pnpm release` scripts; add `@changesets/cli` + `@changesets/changelog-github` to root devDependencies
- `@stanza/codemods` and `@stanza/registry` marked `private: true` — they are inlined into the CLI bundle by tsdown and never published independently; drop the now-redundant `files` field from codemods `package.json`
- `apps/cli/package.json` and `packages/create-stanza/package.json` gain `keywords`, `homepage`, `bugs`, `author`, `repository`, and `publishConfig.access: "public"` — npm metadata required for a clean first publish
- Fix `add-vite-plugin` multiline-array indent regression — when inserting into a `plugins: [...]` array that already spans multiple lines, ts-morph was reformatting the inserted and following elements to 8-space indent while leaving the first element at 4; fix preserves the existing element indent; add a dedicated test case covering the regression
- Drop stale bun shebang comment from CI workflow; remove `packages/internal` from README; mark npm publish TODO done; update CLAUDE.md with Changesets workflow, `private: true` invariant, and `pnpm release` entry
2026-05-20 18:25:50 -04:00
jake d9ccde00e9 fix: wire tanstack-start src/globals.css ownership and drop premature deps
- `framework-tanstack-start` now ships `src/globals.css` as a first-class template (base styles: reset, system font stack, centred `<main>`) and `__root.tsx` imports it directly — CSS was previously written by `styling-tailwind` but never consumed, causing it to be orphaned on disk
- `styling-tailwind`'s tanstack-start adapter drops its `templates` entry and switches to the same `append-to-file { position: "start" }` pattern as the next adapter — prepends `@import "tailwindcss";` before existing base styles, revert is clean, `@import` order is guaranteed; both framework adapters are now structurally identical
- Delete `scripts/module-new.ts` — scaffolding helper no longer used or maintained
- Remove `posthog-node` from `apps/cli` dependencies — telemetry is unimplemented; drop it until the feature is actually wired
- Remove `@clack/prompts`, `kleur`, `mri` from root `package.json` devDependencies — these belong to `apps/cli` and were redundant at the root level
- Mark two TODO items done: tanstack-start globals.css orphan and `auth-better-auth` VITE_BETTER_AUTH_URL (stale — auth-client defaults to current origin, no template references it)
2026-05-20 18:09:37 -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 eb1b9ddba0 feat: add position: "start" prepend mode to append-to-file and split auth-better-auth drizzle templates per-db
- `append-to-file` gains `position: "start"` to prepend the wrapped block before existing file content — required for CSS `@import "tailwindcss"` which must precede all other rules; `leadingBlank` becomes a trailing-blank control in prepend mode; revert strips the injected block cleanly in both directions; three new test cases (prepend order, revert, idempotency)
- `styling-tailwind` next adapter switches from writing `app/globals.css` as a template to prepending `@import "tailwindcss";` via `append-to-file { position: "start" }` — framework retains ownership of base styles, merge order is guaranteed, revert restores cleanly
- `auth-better-auth` drizzle templates split from a single `auth.drizzle.ts` into `auth.drizzle.postgres.ts` + `auth.drizzle.sqlite.ts` so each passes the correct `provider: "pg"` or `provider: "sqlite"` to `drizzleAdapter`; module adapter updated to select the right template based on the active orm peer
- Inline `defaultPmVersion` (init.ts), `summarize` (registry-loader.ts), `cancel` (wizard.ts), and `openViteConfig`/`relPath` (add-vite-plugin.ts) helpers — each was called exactly once; removing the indirection makes the call sites self-contained
- Mark two TODO items as done: per-db drizzle template split and styling-tailwind globals.css merge order
2026-05-20 17:14:43 -04:00
jake 3a30c30d62 refactor: consolidate slot metadata into canonical SLOTS array, hoist module-level install fields, and rename peerPackagesconsumesPackages
- Replace parallel `KNOWN_SLOTS` tuple, `slotOrder` array, and `SLOT_PACKAGE_DIR` map with a single `SLOTS: Slot[]` constant — each entry carries `{ id, label, description, packageDir }`; `KNOWN_SLOTS` is re-derived as `SLOTS.map(s => s.id) as const` for Zod literal inference; `slotLabel` and the package-dir lookup both become simple lookups over the same array; adding a slot is now a two-line edit
- Hoist `dependencies`, `devDependencies`, `env`, and `scripts` to the module level on `ModuleAdapter`'s parent `Module` type — adapter-level values still override per-key; avoids re-declaring the same dep/env block in every adapter (e.g. Better Auth's `better-auth` dep and two env vars are now declared once at module level while per-framework templates and codemods stay in their adapter blocks)
- Rename `peerPackages` → `consumesPackages` and lift it from adapter scope to module scope — cross-package imports are shared infrastructure that doesn't vary per adapter; runner wires `@<project>/<dir>: workspace:*` once per module install rather than once per adapter
- Update all first-party modules (`auth-better-auth`, `orm-drizzle`, `orm-prisma`, `framework-next`, `framework-tanstack-start`, `styling-tailwind`) to the new schema
- Update CLI (`codemod-runner`, `registry-loader`, `wizard`, `add`, `init`) and `scripts/registry-build.ts` to consume the new `SLOTS` array and hoisted module fields
- Update `apps/web/scripts/copy-registry.ts` and docs (`CLAUDE.md`, `REGISTRY.md`, `README.md`, `TODO.md`) to reflect the consolidated model and clarify the slots-vs-add-ons taxonomy
2026-05-20 17:00:56 -04:00
jake 2651daf573 refactor: dissolve packages/internal into scripts/, reorganize CLI into src/lib/, and wire stanza remove codemod revert
- Delete `packages/internal` workspace package — `registry-build.ts` and `module-new.ts` move to top-level `scripts/` (no `package.json`, not shipped); `pnpm registry:build` and `pnpm module:new` scripts updated in root `package.json` and `turbo.json`; CLAUDE.md / TODO.md references updated
- Relocate CLI internals from `apps/cli/src/` → `apps/cli/src/lib/` — `codemod-runner.ts`, `manifest.ts`, `region-tracker.ts`, `registry-loader.ts`, `wizard.ts`; all intra-CLI imports updated to `@/lib/*` path alias via tsconfig `paths`; add `@types/bun` to CLI devDependencies
- `stanza remove` now dispatches `revertCodemods()` as step 1 (before declarative cleanup) — re-loads the module adapter from the registry, calls imperative reverts while framework files are still intact, then sweeps remaining region claims; fixes `wrap-root-layout` / `re-export` regions previously left as "needs manual cleanup" despite shipping working reverts
2026-05-20 16:10:17 -04:00
jake f83e84eb85 feat: slot-package extraction — auth/db/orm install into their own workspace packages
- Add `SLOT_PACKAGE_DIR` map to `packages/registry/src/module.ts`: `auth → "auth"`, `db`+`orm → "db"`, `framework`+`styling → null` (app-scoped); drives where package-scoped templates and deps land
- Runner gains `ensureSlotPackage` — bootstraps `packages/<dir>/package.json` + `tsconfig.json` on first install and merges adapter `dependencies`/`devDeps`/`scripts` there; wires the app to the slot package via a `workspace:*` dep
- Add `scope: "package"` to `TemplateRef` — resolves dest against `packages/<SLOT_PACKAGE_DIR[slot]>/` instead of `appDir` or repo root; runner writes package-scoped files to the right home
- Add `peerPackages` to `ModuleAdapter` — declares cross-package deps (e.g. `auth` needing `db`); runner injects `@<project>/<dir>: workspace:*` into the current package's `package.json`
- Add `{{<dir>PackageName}}` mustache substitution (e.g. `{{dbPackageName}}` → `@my-app/db`) — runs over template bodies (`template: true`) and codemod `args` string values; enables auth templates to import from the db package without hardcoding names
- Add `base: "package:<dir>"` support to `re-export` and `append-to-file` builtins — codemods can target files inside another slot's package (e.g. extending the orm schema barrel from the auth module)
- Update `auth-better-auth`, `auth-clerk`, `orm-drizzle`, `orm-prisma` modules to use `scope: "package"` templates and `peerPackages` cross-wiring; rename `layout-wrapper.tsx` → `provider.tsx` in auth-clerk
- `stanza remove` updated to sweep `packages/<dir>/` when no region claims remain under it
- Update `selection.ts` in apps/web to account for package-scoped file paths
- Document extraction model, `SLOT_PACKAGE_DIR`, authoring rules, and `base:` codemod targeting in `CLAUDE.md`, `README.md`, and `REGISTRY.md`
2026-05-20 15:05:11 -04:00
jake 227b625ea1 feat: add append-to-file and re-export builtin codemods + wire up auth-better-auth
- `append-to-file`: text-level append primitive that wraps inserted blocks with `stanza:<marker>:start/end` marker comments — idempotent on re-apply, fully revertible, comment style auto-inferred from file extension (line/hash/block); designed for Prisma schemas, CSS, YAML, and other non-TS files where AST codemods are overkill
- `re-export`: AST builtin for adding named or wildcard re-exports to an existing barrel/index file
- export both from `builtins/index.ts` and the top-level `src/index.ts`
- wire `auth-better-auth/module.ts` to use the new builtins via catalog invocations
2026-05-20 14:43:30 -04:00
jake 57f43fd846 refactor: replace bundled module codemods with a static CLI-side catalog
- Modules no longer ship codemod code; adapters now declare `codemods: [{ id, args }]` referencing catalog entries by id — the registry stays pure data
- Add `apps/cli/src/codemods/` catalog: `wrap-root-layout` codemod (parameterized by provider import, component name, etc.) + `CODEMOD_CATALOG` map statically imported into the CLI binary so distribution shape (single binary, npx, bun compile) doesn't matter
- Remove `loadCodemods`, `getBundleCacheDir`, and all `codemodBundle` write/import machinery from `codemod-runner.ts`; runner now dispatches via `CODEMOD_CATALOG[invocation.id]` and forwards `invocation.args`
- Drop `registry-build.ts` codemod bundling step and `mod.codemodBundle` field from the registry package; remove per-module `codemods/index.ts` (auth-clerk replaced by catalog invocation with args)
- Change `ModuleAdapter.codemods` type from `string[]` to `CodemodInvocation[]` (`{ id: string; args?: Record<string, unknown> }`)
- Update `CLAUDE.md` to document "Registry is data; CLI is the runtime" principle and the process for adding/invoking generic codemods
2026-05-20 14:23:34 -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 f03bdd0f49 fix: inline template contents into registry build output for self-contained HTTP manifests
- add `content?: string` to `TemplateRef` and export `TemplateRef`/`EnvVar` from the registry package
- `registry-build.ts` now reads each template file and bakes it into the per-module JSON so CDN-fetched manifests need no follow-up requests
- `codemod-runner.ts` prefers `tpl.content` when present and falls back to reading from disk (local dev); replaces `writeTemplateFile` with `renderTemplate` + manual `writeFileSync` for finer control
- document the template distribution model in `CLAUDE.md`
2026-05-20 12:20:04 -04:00
jake 0fcc225d85 feat: consolidate maintenance scripts into packages/internal and add module:new scaffolder
- rename `packages/registry-build` → `packages/internal` to serve as a single home for internal tooling
- add `module-new.ts`: interactive CLI (powered by `@clack/prompts`) that scaffolds a new first-party module under `registry/modules/<slot>-<id>/` with `module.ts`, `package.json`, `tsconfig.json`, `templates/.gitkeep`, and an optional `codemods/index.ts` stub
- expose `pnpm module:new [slot] [id]` at the repo root and update `registry:build` turbo filter to `@stanza/internal`
2026-05-20 12:12:59 -04:00
jake a50ababb49 feat: add sqlite drizzle adapter for better-auth and clerk codemods
- split better-auth drizzle adapters into separate postgres/sqlite variants with a dedicated sqlite schema template
- simplify auth-client templates to use `createAuthClient()` without an explicit baseURL
- export `Node` type and `SyntaxKind` enum from the codemods package
- add codemod for the auth-clerk module
2026-05-20 12:06:48 -04:00
jake b74ffb9d1a feat: initial commit 2026-05-20 11:52:53 -04:00