Commit Graph
80 Commits
Author SHA1 Message Date
jake 678b5bc1af feat: add ui-shadcn-base, ui-shadcn-radix, and ui-tailwind modules; rename styling category to ui
- Rename `styling` → `ui` across the entire codebase (CATEGORIES array, docs, AGENTS.md, CLI tests, resolver, template tests, codemod runner); `ui` is `home: package` so shadcn installs into `packages/ui/` as a workspace dep rather than wiring directly into the app shell
- Add `ui-shadcn-base` (Tailwind v4 / CSS-first, no Radix peer) and `ui-shadcn-radix` (Tailwind v4 + Radix primitives) with full per-framework template sets for Next.js and TanStack Start: `components.json`, `globals.css`, `postcss.config.mjs`, `theme-provider.tsx`, shared `Button` + `utils.ts`; light/dark logos inlined from svgl
- Rename `styling-tailwind` → `ui-tailwind` to match the new category id; update its module definition to `category: "ui"` and `home: package`
- Add four new codemod builtins with tests: `add-array-entry-in-call` (inserts an element into a named array argument of any `CallExpression`), `replace-import` (rewrites a specifier in an existing import declaration), `set-html-attributes` (sets attributes on an HTML/JSX element by tag name or selector), `set-tsconfig-paths` (merges `compilerOptions` entries into a `tsconfig.json` AST node); register all four in the builtins index and export from `@stanza/codemods`
- Add `wrap-root-layout` builtin tests
- Update registry.mdx, concepts.mdx, cli.mdx, getting-started.mdx, agents.mdx, index.mdx, AGENTS.md, README.md, TODO.md, and SKILL.md to reflect the category rename and new modules
2026-05-25 15:26:08 -04:00
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 16295952a8 feat: add payments-stripe module and sync file-preview selection to URL hash
- Add `payments-stripe`: `stripe` SDK + framework-native Checkout Session / webhook route handlers for Next.js and TanStack Start; Better Auth bridge variant ships `@better-auth/stripe` and wires the plugin into `auth.ts` when `auth: better-auth` is selected; light logo inlined from svgl
- Sync the web builder's open file to the URL hash so refreshes and shared links restore the same selection; latch the last file path so folder-expand clicks don't wipe the preview; back/forward navigation reflects hash changes back into the tree; default-file selections don't pollute the hash (keeps shareable URLs clean)
- Trim over-explained JSDoc comments in `add-package-dep.ts` and `add-plugin-to-call.ts` to the terse one-liner style; condense the `init.ts` `fullPending` comment to match
- Update `registry.mdx` stripe row from 🟨 Planned to 🟢 Available with a brief feature summary
2026-05-25 14:06:07 -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 3304abbe98 feat: add payments-polar module, consolidate registry build into packages/registry, and generalize plugin codemods
- Add `payments-polar`: `@polar-sh/sdk` + framework-native checkout/webhook route handlers for Next.js and TanStack Start; Better Auth bridge variant ships `@polar-sh/better-auth` and wires the plugin into `auth.ts` when `auth: better-auth` is selected; light/dark logos inlined from svgl
- Consolidate the registry build: delete `scripts/registry-build.ts` and move the emit logic into `packages/registry/src/build.ts`, which now accepts an optional positional output-base arg (defaults to `dist/`) and writes `{base}/registry/{index,modules/*}.json` + `{base}/schema.json` directly; `prepare-registry.sh` is reduced to a single `jiti packages/registry/src/build.ts apps/web/public` invocation; CI/release workflows drop the Bun setup + `registry:build` steps since the build now runs inline at web-app prebuild time
- Replace `add-vite-plugin` codemod with the more general `add-plugin-to-call` (targets any `CallExpression` by callee + arg position, not just `vite.config` `plugins` arrays); add `add-package-dep` builtin for imperatively injecting deps into a `package.json` AST node; both are fully tested and registered in the builtins index
- Fix `init` adapter resolution order: precompute a `fullPending` map from all peer-category picks before the apply loop so `resolveAdapter` sees the complete peer set regardless of `categoryOrder` traversal sequence; export `PEER_CATEGORIES` from `@stanza/registry` to support this
2026-05-25 13:19:06 -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 9e1f0076a2 fix: set infinite stale time for web builder data loading 2026-05-23 22:45:00 -04:00
jake be001b0037 feat: add multi-app support with per-app module targeting and app-aware manifest schema
- Introduce `AppSpec` (`{ id, dir, kind }`) and `AppKind = "web" | "native"` types; `stanza.json` now declares `apps: AppSpec[]` alongside modules, and `stanza init` scaffolds a default web app via `defaultWebApp()`
- Add optional `apps?: string[]` to `StanzaModuleRecord` to encode which apps a record targets; `home: app` modules require it, `home: package` modules use it to scope workspace deps (omitting means all apps), `home: repo` modules forbid it
- Make cardinality enforcement app-scoped for `home: app` categories: `selectedOne(m, cat, appId?)` and `selectedAll(m, cat, appId?)` now filter by app, and `add`/`remove` thread a `targetApp` through the runner so per-app single-choice rejection works correctly
- Extend the template render context with an `app: { id, dir, kind }` binding (plus the legacy `project.appDir` alias) re-bound per target app; the codemod runner fans out template/codemod/dep application once per consuming app in a loop
- Update `synthesizePackageJsons`, `synthesizeTemplates`, `buildRenderContext`, and related web-preview helpers to accept an `apps` array and produce per-app output
- Update `package-json.ts` to strip workspace deps from all apps' `package.json` on remove and to inject them into every consuming app on install
- Expand `manifest.test.ts` and `template.test.ts` with multi-app fixture coverage; add `"use client"` directive to codemod test files to satisfy the RSC boundary
- Refresh `AGENTS.md`, `SKILL.md`, and all affected docs (`concepts.mdx`, `registry.mdx`, `cli.mdx`, `getting-started.mdx`, `agents.mdx`) to document the new apps schema and routing semantics
2026-05-23 22:26:09 -04:00
jake 3c338a6b64 refactor: remove global pending boundary component
- Removed `RoutePendingBoundary` from `route-boundaries.tsx` and its usage in `router.tsx`.
- Simplified router configuration by eliminating default pending component and related properties.
2026-05-23 21:47:29 -04:00
jake e6709b24f7 fix: capitalize "Stanza" consistently across docs, code, and templates 2026-05-23 21:40:15 -04:00
jake fff2a4ca55 feat: add navigation progress bar, route error/pending boundaries, and detail section primitives
- Add `NavigationProgress` component that subscribes to `onBeforeLoad` / `onResolved` router events; defers 120 ms so instant cached-route navigations don't flash a bar, trickles toward 90% with an easing function, snaps to 100% on resolve, then fades out with a 200 ms hold; includes a 12 s safety-net reset in case `onResolved` never fires
- Extract `route-boundaries.tsx` with reusable error and pending boundary wrappers and wire them into `router.tsx` and `__root.tsx` (replacing the inline implementations there)
- Add `section.tsx` exporting `Section` (heading + children wrapper) and `SectionList` (bordered divided `<ul>`); refactor `deps-table.tsx`, `env-table.tsx`, `templates-list.tsx`, and `try-it.tsx` to consume them, removing the duplicated heading markup from each; rename `try-it.tsx` → `install.tsx` and its export `TryIt` → `Install` to match the section heading copy
- Add `posthog.server.ts` to centralise the PostHog server-side client initialisation
- Downgrade section and card headings from `font-semibold` to `font-medium` throughout `module-cards.tsx` and `section.tsx` for a lighter visual weight; miscellaneous footer/header spacing tweaks
2026-05-23 21:29:38 -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 4f456cc485 fix: replace useMatch overlay guard with pathname-scoped useRouterState and add useGracePeriod debounce to FilePreview
- Revert the `useMatch` / `m.isFetching` approach in `Builder` to a `useRouterState` selector that only sets `isReloading` when the router is loading *and* the resolved pathname hasn't changed; preserves the "don't flash on navigate-away" invariant without relying on internal match shape
- Add a `useGracePeriod(active, delayMs, minMs)` hook in `FilePreview` that mirrors TanStack Router's `defaultPendingMs` / `defaultPendingMinMs` semantics: the overlay is suppressed for the first 300 ms (skips the flash on fast loads) and, once shown, stays visible for at least 250 ms (avoids a flicker on near-misses)
2026-05-23 20:18:42 -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 ba504bba34 feat: add prerender enumeration & docs markdown routes 2026-05-23 17:54:51 -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 25fcbd6b12 perf: stabilize builder callbacks with useRef snapshot and memoize derived slot-card data
- Replace the scattered `[navigate, name, pm, optimistic, ...]` dependency arrays in `setName`, `setPm`, and `toggle` with a `latest` ref that always holds the current values; callbacks are now stable across optimistic flips so downstream `memo`/`useMemo` in `SlotCards` and `ModuleCard` actually prevent re-renders
- Memoize `parsed`, `selections`, `pending`, `byCategory`, and `categories` in `Builder` and `SlotCards`; hoist the `resolveAdapter` manifest stub as a module-level constant (`RESOLVER_MANIFEST`) to stop it from allocating on every card render; thread `selections` directly into `ModuleSection` so `isSelected` no longer allocates a closure per card
- Swap the `onNameChangeRef` manual-ref pattern in `ProjectSetup` for `useEffectEvent`; extract a shared `defaultPathFor` helper in `FilePreview` to deduplicate the `package.json`-fallback logic
- Rename `mode-toggle.tsx` → `theme-toggle.tsx`; move LLM txt routes under the `docs.` prefix (`docs.llms[.]txt.ts`, `docs.llms-full[.]txt.ts`) and regenerate the route tree; add `vercel.json`
2026-05-23 16:08:29 -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 f45031dec5 fix: prevent draft clobber on debounce echo and polish ProjectSetup error UI
- Track the last value pushed upstream with `lastSyncedRef` so incoming `name` prop changes that are echoes of our own debounced push are ignored instead of overwriting mid-flight user input
- Gate the debounce effect on `lastSyncedRef.current` rather than the stale `name` prop so the timer fires correctly after a history-nav reset
- Add an `<IconAlertCircle>` prefix to `FieldError` and inline the capitalize logic to avoid the CSS-only `capitalize` class clobbering multi-word messages
2026-05-22 22:09:36 -04:00
jake 7d9d78b444 feat: add SVG logo mark and replace wordmark text with icon across header and OG cards
- Add `apps/web/public/icon.svg` and wire it up as the `<link rel="icon">` favicon in the root route
- Extract a reusable `<Logo>` component (`components/logo.tsx`) using the MingCute icon SVG with `currentColor` fill; swap the plain "stanza" text link in `Header` for the icon with an `sr-only` label and `aria-label`
- Replace the text wordmark in `OgCard` and `OgDefault` with an inline data-URI `<img>` of the SVG using an explicit `#fafafa` fill (Satori doesn't propagate `currentColor` to images); remove now-unused `WORDMARK` and `BIG_WORDMARK` style constants
2026-05-22 21:49:01 -04:00
jake 007733d320 fix: wrap docs Suspense in Hydrate to prevent content flash after page load 2026-05-22 21:21:56 -04:00
jake 017b8620fc feat: centralize project-name validation in @stanza/registry and wire it into the CLI and web builder
- Add `packages/registry/src/project-name.ts` with a `validateProjectName` helper backed by `validate-npm-package-name`; rejects scoped names (the user supplies only the bare name, stanza synthesizes `@<name>/db` etc.) and returns a typed `ProjectNameValidation` discriminated union; export from `@stanza/registry`
- Replace the inline regex in `apps/cli/src/lib/wizard.ts` (both interactive and `--yes` paths) with `validateProjectName` so error messages are consistent
- Update `apps/web/src/components/builder/project-setup.tsx` to call `validateProjectName` via `useMemo`, gate the debounced `onNameChange` callback to only fire on valid input, and display inline `FieldError` feedback (suppressed while the field is empty so the placeholder fallback isn't alarming); add `maxLength`, `aria-invalid`, and `aria-describedby` for accessibility
2026-05-22 20:35:58 -04:00
jake 702a22232b fix: remove explicit prerender config from tanstackStart() in apps/web 2026-05-22 20:05:35 -04:00
jake 8c5433aa3c feat: move template substitution into @stanza/registry and switch to dotted Mustache paths
- Promote `renderTemplate` and `buildRenderContext` from `@stanza/codemods` into `@stanza/registry` alongside `synthesizeManifest`/`synthesizeEnvExample`/`synthesizePackageJsons`; add a new `synthesizeTemplates` export that returns the fully-substituted file list for a resolved selection so the web preview and the CLI apply path share identical logic
- Change the template DSL to dotted paths: `{{project.name}}` (was `{{projectName}}`), `{{project.appDir}}` (was `{{appDir}}`), `{{package.name}}` (was `{{packageName}}`), and `{{packages.<dir>.name}}` (was `{{<dir>PackageName}}`, e.g. `{{packages.db.name}}`); add a `TemplateContext` type exported from `@stanza/registry`
- Migrate all first-party modules (`auth-better-auth`, `auth-clerk`) to the new dotted syntax; fixes the web builder preview regression where tokens like `{{dbPackageName}}` appeared unsubstituted
- Update AGENTS.md docs and CLI `codemod-runner.ts` to reflect the new context shape and import locations
2026-05-22 19:58:09 -04:00
jake bdc69dc483 feat: add TanStack Start adapter to auth-clerk module
- Add a `tanstack-start` adapter to `auth-clerk` with `@clerk/tanstack-react-start` dependency, env vars, and templates (`provider.tsx`, `index.ts`, `start.ts`)
- Reorganize existing Next.js templates into a `next/` subdirectory and update `module.ts` src paths accordingly
- Update peers from `["next"]` to `["next", "tanstack-start"]` and reflect the completed TanStack Start support in the registry docs
2026-05-22 19:45:06 -04:00
jake 101990843e fix: add docs pages to sitemap and enable prerendering in TanStack Start
- Include Fumadocs `source.getPages()` entries in `/sitemap.xml` alongside the home and module detail URLs; drop `changefreq` from all entries and make `priority` optional
- Enable `prerender` in `tanstackStart()` with `crawlLinks: false` so static pages are pre-built without a full crawl
2026-05-22 19:34:39 -04:00
jake e019c28faa fix: pass source.config.ts to mdx() and re-add apps/web to the workspace test runner
- Make the `lazyPlugins` callback `async` and forward the Fumadocs source config to `mdx()` so the MDX plugin picks up the correct collection/source settings
- Remove the `!apps/web` exclusion from the root `vite.config.ts` test projects list now that the upstream vite-plus bug (`runner.config` undefined during suite collection) is resolved
2026-05-22 19:15:29 -04:00
jake d733efa7d4 feat: add /llms.txt, /llms-full.txt, and Accept-header content negotiation for LLM-friendly docs
- Add `/llms.txt` route using Fumadocs' `llms().index()` helper (follows the https://llmstxt.org convention) and `/llms-full.txt` that concatenates the processed Markdown of every docs page into a single response
- Enable `includeProcessedMarkdown` on the docs collection and add `getLLMText` + `markdownPathToSlugs` helpers in `lib/source.ts`
- Add `src/start.ts` with a `llmMiddleware` that intercepts `/docs/**` requests: serves raw Markdown when the path ends in `.md` or when the `Accept` header prefers Markdown (via `fumadocs-core/negotiation`), short-circuiting TanStack Start's normal rendering
2026-05-22 19:09:53 -04:00
jake e730541bd6 fix: a11y, transition scoping, and UX polish across the web app
- Add skip-to-content link, `theme-color` meta tags for light/dark, `role="status"` + `aria-live` on the file-preview loading spinner, and `aria-hidden` on decorative icons throughout
- Scope `transition-*` on `Button` and `ModeToggle` to explicit properties instead of `transition-all`; add `overscroll-contain` to `DialogContent` and the `SiteSearch` results list; add `focus-within:ring` to the search input wrapper and `focus-visible:ring` to `CopyableField`
- Suppress `autoFocus` in `SiteSearch` on touch devices, add `translate="no"` to mono/code content, set `type="search"` / `autoComplete="off"` / `spellCheck={false}` on search and name inputs, and shorten doc page titles ("CLI reference" → "CLI", "Module registry" → "Registry")
2026-05-22 18:53:09 -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 9bf80ce2e8 docs: expand README and documentation site with concepts, getting-started, and CLI reference pages
- Add `concepts.mdx` covering categories, cardinality, home, vendoring, peers/adapters, manifest, regions, and the open registry model
- Flesh out `getting-started.mdx` with prerequisites, install/run steps, package-manager variants, inspect/remove commands, and a non-interactive `--yes` example
- Rewrite `cli-reference.mdx` with per-verb sections, global flags, environment variables, and dependency-versioning semantics
- Expand `README.md` with a "Why Stanza?" section, quick-start snippet, docs link, and updated module list; swap `pnpm create` example for `npm init`
- Add `concepts` to `meta.json` nav order
2026-05-22 16:03:15 -04:00
jake 54de4cf892 fix: declare PostHog env vars as Turborepo build inputs in apps/web/turbo.json 2026-05-22 15:43:14 -04:00
jake c7e99685f6 refactor: move CopyButton to ui/, add CopyableField and usePointerCapability, and polish UI components
- Delete `components/copy-button.tsx` and replace it with `components/ui/copy-button.tsx`; the new version is a proper shadcn-style primitive that uses the enhanced `Tooltip` for its label and fires an `onCopy` callback after a successful write
- Add `components/ui/copyable-field.tsx` — a composable field that pairs a monospace text display with the new `CopyButton` in a single bordered row; `CommandPreview` now delegates to `CopyableField` instead of managing its own `<pre>` + icon button
- Add `hooks/use-pointer-capability.ts` — a `matchMedia(pointer: fine)` hook that distinguishes touch-only devices from mouse/stylus inputs; used to conditionally suppress hover-only affordances
- Expand `components/ui/tooltip.tsx` with a `nativeButton` prop on `TooltipTrigger` and better composability; use it in `slot-cards.tsx` to show disabled-card reasons as a tooltip instead of appending inline muted text below the card
- Expand `components/ui/scroll-area.tsx` with scrollbar-corner and horizontal scrollbar sub-components so the primitive is fully composable
- Add icons (sun / moon / laptop) to `ModeToggle` dropdown items; make the GitHub header button `size="icon"` and inline the anchor JSX; rename the detail-page "Try it" heading to "Install"
- Replace the `active && "bg-muted"` conditional class in `SiteSearch` results with a plain `hover:bg-muted` rule; remove `no-scrollbar` from the results list so native scrollbars reappear on pointer devices
2026-05-22 15:25:56 -04:00
jake 66d6e19661 refactor: lift package-manager into the URL and move CommandPreview into the file-preview header
- Replace `usePackageManager` (a localStorage-backed hook) with a `pm` field in `BuilderSearch` / `toSearchParams`; the package manager is now a first-class URL param that survives share links, back/forward, and optimistic navigation — `DEFAULT_PACKAGE_MANAGER` (`"npm"`) is used as the fallback
- Make `CommandPreview` fully controlled: drop the internal `usePackageManager` call and accept `pm` + `onPmChange` as props; wire a `setPm` callback in `Builder` that navigates like `setName`; keep local `useState` in `TryIt` (detail page) since it has no URL to write into
- Move `CommandPreview` out of `ProjectSetup` and into `FilePreview`'s header bar so the install command is always visible beside the file tree; `ProjectSetup` loses its `selections` prop and the command section, shrinking to the project-name field only
- Replace `FilePreview`'s `moduleCount: number` prop with `header: ReactNode` so the pane accepts arbitrary header content rather than computing its own file/module-count summary string
- Drop `yarn` from `PACKAGE_MANAGERS` and remove `YarnLogo` from `PackageManagerSelect`; the three remaining options (`npm`, `pnpm`, `bun`) cover all supported scaffolders
2026-05-21 23:21:43 -04:00
jake 71f4c9d89b refactor: unify module taxonomy into a single Category concept with cardinality and home
- Replace the `slot` / `add-on` discriminated-union split with one `Category` shape carrying two orthogonal fields: `cardinality` (`"one"` for single-choice, `"many"` for coexisting) and `home` (`"app"` / `"repo"` / `"package"`) — the old `packageDir` + `repoScoped` pair is dropped
- `Module` is no longer a discriminated union; it carries a single `category` field referencing a `Category` id; `KNOWN_SLOTS`, `KNOWN_ADDONS`, `ADDON_CATEGORIES`, and all related helpers are replaced by a single `CATEGORIES` array and derived `categoryHome` / `categoryLabel` / `PACKAGE_DIRS` lookups
- Manifest schema bumped to version `0.2` — `modules` is now one record keyed by category holding arrays; `cardinality: "one"` categories are enforced to ≤ 1 entry at install time; no migration (stanza is pre-1.0 and unpublished)
- Peer/constraint logic is now emergent from cardinality: the resolver treats only `cardinality: "one"` categories as peers, so `testing` (many) can never accidentally become a peer; install routing is a single `categoryHome` lookup shared by the CLI runner, codemod runner, and web preview
- Update all CLI commands (`add`, `remove`, `init`, `list`, `search`), the wizard, `codemod-runner`, `registry-loader`, all web builder/detail/search components, server functions, OG route, sitemap, analytics, and selection logic to use the new category API
- Update all first-party `module.ts` files, `registry-build.ts`, tests, and docs (`AGENTS.md`, `REGISTRY.md`, `SKILL.md`) to reflect the unified taxonomy
2026-05-21 23:03:02 -04:00
jake c454f30798 feat: add optimistic slot/addon selection, warm Shiki on load, and fix file-tree expand replay
- Wrap `setSelection` and `toggleAddon` in `startTransition` + `useOptimistic` so card borders and checkmarks flip instantly on click; the preview pane continues to wait on committed loader data (server-rendered Shiki HTML) and shows its own spinner for that round-trip
- Fire-and-forget `getHighlighter()` during the initial `getBuilderState` call so the Shiki singleton is warm before the first slot toggle, eliminating cold-start latency on the first preview render
- Fix `initialExpandedPaths` replay in `FilePreview`: filter the expanded-dir set to only directories whose entire ancestor chain is also expanded, preventing a collapsed parent from being re-opened when its still-flagged descendant is replayed
- Hoist the loading overlay outside the empty/non-empty branch so it also covers the initial `filePaths === []` → content transition, where `filePaths` is still stale while the loader is in flight
2026-05-21 22:22:03 -04:00
jake 65c02d4a46 feat: add tooling slot with eslint-prettier, biome, and oxlint-oxfmt modules
- Promote `tooling` from an add-on category to a single-choice **slot** in `KNOWN_SLOTS`/`SLOTS` and remove it from `KNOWN_ADDONS`/`ADDON_CATEGORIES`; the three toolchains are mutually exclusive substitutes so single-choice semantics fit, and nothing peers on `tooling` so it bears no outbound dispatch constraints
- Add `registry/modules/tooling-eslint-prettier` — ESLint flat config + Prettier with per-framework adapters (`next` → `eslint.config.next.mjs`; `tanstack-start` → `eslint.config.tanstack.mjs`) and a shared `prettier.config.mjs`; scripts: `lint: "eslint ."`, `format: "prettier --write ."`
- Add `registry/modules/tooling-biome` — Biome 2.x with a single `default` adapter (framework-agnostic `biome.json`); scripts: `lint`, `format`, `check`
- Add `registry/modules/tooling-oxlint-oxfmt` — Oxlint + oxfmt with a single `default` adapter (`dot_oxlintrc.json`, `dot_oxfmtrc.json`); scripts: `lint`, `format`
- Drop `lint: "next lint"` from `framework-next` — `next lint` was removed in Next 16, so the tooling module now owns `scripts.lint` cleanly
- Add CLI tests covering `init --yes --tooling eslint-prettier`, framework-next shipping no lint script without a tooling pick, `add tooling biome` on an existing project, and slot-already-filled rejection
2026-05-21 21:50:33 -04:00
jake b6e9b55c56 feat: generate and publish stanza.json JSON Schema at https://stanza.tools/schema.json
- Add `MANIFEST_SCHEMA_URL` constant and `manifestJsonSchema()` function to `packages/registry/src/manifest.ts`; the schema is derived from `StanzaManifestSchema` via `z.toJSONSchema` and decorated with `$id`, `title`, and `description`
- Add `$schema` field to the `StanzaManifest` type and `StanzaManifestSchema`; `emptyManifest` populates it automatically and `writeManifest` in the CLI spreads `$schema` first so pre-existing manifests gain the pointer on their next write
- Extend `scripts/registry-build.ts` to write `dist/schema.json` alongside the registry output; update `prepare-registry.sh` to copy `dist/schema.json` to `apps/web/public/schema.json` so it is served at the web root
- Gitignore `apps/web/public/schema.json` as a generated artifact
2026-05-21 21:32:58 -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 69e02522be refactor: replace mri with citty for CLI argument parsing
- Swap `mri` for `citty` and wrap every command handler with `defineCommand` so citty owns parsing, validation, and `--help` output; remove the hand-rolled `mri` parse block from `bin.ts` and simplify `run()` to accept no arguments
- Extract shared global flags (`--dry-run`, `--dangerously-allow-dirty`, `--no-telemetry`) into a new `commands/_args.ts` as `commonArgs` that is spread into each subcommand's `args` definition; introduce a `CliArgs` open-record type for handler signatures
- Migrate `add`/`remove`/`init`/`list`/`search` command signatures from `({ positional, argv: Argv }) => …` to a flat `(args: CliArgs) => …` shape; declare `slot`, `moduleId`, and `name` as `type: "positional"` in their respective `defineCommand` blocks
- Update the `commands.test.ts` helper from `argv(flags)` to `args(flags)` to match the flat citty-parsed shape (named positionals promoted to top-level keys alongside booleans)
2026-05-21 21:16:10 -04:00
jake 59d131140d fix: override @pierre/trees middle-truncate layout with plain single-line ellipsis in FilePreview
- `@pierre/trees`' built-in middle-truncate renders both halves flush together with no visible ellipsis when the content cell is narrow; inject `unsafeCSS` to collapse the truncate group container to a standard `overflow: hidden` / `text-overflow: ellipsis` block and hide the overflow half and marker cell entirely
2026-05-21 20:33:16 -04:00
jake f6384e3ce7 feat: add opt-out CLI telemetry via a fire-and-forget /api/events PostHog proxy
- Add `apps/cli/src/lib/telemetry.ts` — a dependency-free telemetry module that accumulates events in memory and flushes them as a single `fetch` POST at the end of each run; identity is an ephemeral per-process UUID (no on-disk state); suppressed by `--no-telemetry`, `STANZA_TELEMETRY=0`, `DO_NOT_TRACK=1`, or any recognized CI environment variable
- Wire `telemetry.configure` / `telemetry.capture` / `telemetry.flush` into `run.ts` so every command emits a `cli_command` event (with `status` and `duration_ms`) and individual `init`/`add`/`remove` calls emit `cli_module` events (with `action`, `group`, and `module`)
- Rename the `mri` boolean from `no-telemetry` to `telemetry` in `bin.ts` so mri's `--no-` prefix convention populates `argv.telemetry === false` as `isTelemetryDisabled` expects
- Add `apps/web/src/routes/api.events.ts` — a TanStack Start API route that receives the CLI's batched event payload and forwards each event to PostHog via the REST `capture` endpoint using `POSTHOG_API_KEY`; non-`POST` requests, missing keys, and upstream errors all return appropriate status codes without leaking details
- Document the `--no-telemetry` flag, `STANZA_TELEMETRY`, and `DO_NOT_TRACK` env vars in `skills/stanza-cli/SKILL.md`
2026-05-21 20:17:25 -04:00
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