npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@frozencloud/studio

v0.2.16

Published

Runtime block editor for Nuxt — edit pages visually in a database-backed dashboard, using your existing components without rewriting them

Readme

@frozencloud/studio

Runtime, database-backed block editor for Nuxt. Edit pages visually in a dashboard using your existing Vue components without rewriting them — no field definitions, no markdown+git+rebuild cycle.

  • Your components drive the editor. Prop forms are auto-generated from your stock SFCs (typed props, defaults, JSDoc descriptions, unions, arrays of objects) via nuxt-component-meta. Prop names steer the inputs: *icon* props get an Iconify picker, html/content/body props get a WYSIWYG editor.
  • Pages live in Postgres. Trees are stored as Comark AST (JSON) via Drizzle and rendered at runtime — publishing never rebuilds the app.
  • Pruvious-style editor. Block tree with icons and slot nesting, live iframe preview of the real page, prop form; click-to-select in the preview, drag/drop, undo/redo, resizable sidebars, named page variants.
  • AI built in (optional). Compose sections from your catalog by prompt, or generate entirely new components — compiled server-side, previewed in a sandbox, and gated by approval before they reach a page.

Feature tour

| Area | What you get | | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Dashboard /studio | Page list grouped into collapsible folders by path segment; create/duplicate/delete/publish | | Editor /studio/:pageId | 3-pane layout (resizable sidebars, sizes persist): block tree · live preview · prop form | | Block tree | Per-block icons, hover actions, "Add block" modal (searchable icon grid + per-block "?" docs view), "Add inner block" for components with a default slot | | Visual editing | Click/hover selection synced both ways with overlay labels, SortableJS drag/drop (slot-aware drop targets), undo/redo, copy/paste, Delete key | | Prop forms | Generated from JSON schema: string/number/boolean/enum inputs, repeatable groups for arrays of objects, icon picker, rich text (UEditor), slot text | | Variants | Named snapshots of a page's draft — save, restore (undoable), delete; backed by studio_page_variants | | Theming /studio/theme | Site-wide runtime theme (Nuxt UI semantic colors + radius) with live sample preview; StudioTheme block re-themes just its nested blocks; preview light/dark toggle in the editor | | AI tier 1 | "Generate" in the editor: prompt → block tree using only catalog components → preview in iframe → accept/discard | | AI tier 2 | /studio/components: prompt → new SFC, compiled with the host's vue/compiler-sfc, sandboxed preview, approve/reject | | Component workbench | /studio/components/:id: monaco editor · sandbox preview · AI chat (streamed, "Apply to editor"); hand-write components without AI; edits force re-approval | | Runtime mount | Approved components mount client-side on public pages and join the editor catalog like any allowlisted block (SSR deferred) |

Setup

pnpm add @frozencloud/studio drizzle-orm@^1.0.0-rc.3
// nuxt.config.ts
export default defineNuxtConfig({
  modules: ["@frozencloud/studio"],
  studio: {
    // allowlist of editor-visible components (dirs or globs, relative to srcDir)
    components: ["components/blocks"],
  },
  components: [
    // blocks must be globally registered so the renderer can resolve them by name
    { path: "~/components/blocks", global: true, pathPrefix: false },
    "~/components",
  ],
});

The dashboard is built on Nuxt UI (installed by the module) — wrap your app in <UApp> in app.vue if you don't already.

Database

The module never owns a connection or migrations. Hand it your Drizzle instance from a Nitro plugin:

// server/plugins/studio.ts
import pg from "pg";
import { drizzle } from "drizzle-orm/node-postgres";

export default defineNitroPlugin(() => {
  const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
  setStudioDatabase(drizzle({ client: pool }));
});

Add the schema to your own migration pipeline:

// drizzle.config.ts — point drizzle-kit at the exported schema
export default defineConfig({
  dialect: "postgresql",
  schema: ["./server/database/schema.ts", "./node_modules/@frozencloud/studio/dist/db.mjs"],
  // …
});

The schema is also importable as @frozencloud/studio/db (studioPages, studioComponents, studioPageVariants, studioSettings, types).

Auth

Studio routes (/studio/**, /api/_studio/** except the public render endpoint, the approved-components feed and token-gated sandbox documents) are open in dev and forbidden in production until you register an authorizer. With nuxt-auth-utils:

// server/plugins/studio.ts (same plugin as the database)
setStudioAuthorize(async (event) => {
  const session = await getUserSession(event);
  return !!session.user;
});

Any callback (event: H3Event) => boolean | Promise<boolean> works.

Theming

Studio pages adopt Nuxt UI's runtime theming: colors are CSS variables, so they change at runtime — no rebuild.

  • Site theme (/studio/theme): assign Tailwind palettes to the semantic colors (primary, secondary, neutral, …) and pick a radius preset. Stored in studio_settings, applied by the renderer around every studio page (layout chrome included); the dashboard itself is unaffected. Unset keys fall back to the host's app.config.ts colors.
  • Sub-theme blocks: the built-in StudioTheme block wraps other blocks in its default slot and re-scopes the same variables for just that section. Nest them — inner themes inherit whatever they don't override. The AI assistant knows about it ("make this section emerald").
  • Preview color mode: the editor toolbar toggles the preview iframe between light and dark without touching your dashboard preference.

Custom palettes work too: any palette defined in the host's CSS with @theme static { --color-brand-50: …; } can be typed into the theme API (PUT /api/_studio/site-theme, e.g. { "primary": "brand" }) — the pickers only list Tailwind's built-ins.

AI (optional)

Generation needs one of two credentials at runtime — without one, AI endpoints return a clean 503 and the rest of studio works normally:

| Env var | Provider | | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | NUXT_STUDIO_AI_API_KEY (or ANTHROPIC_API_KEY) | Anthropic direct | | NUXT_AI_GATEWAY_API_KEY (or AI_GATEWAY_API_KEY) | Vercel AI Gateway — model ids are mapped automatically (claude-opus-4-8anthropic/claude-opus-4.8) |

Generated components are constrained to plain-JavaScript SFCs with vue-only imports and scoped CSS, compiled and validated server-side, and never served to visitors until approved in /studio/components.

Options

| Option | Default | Description | | ------------------- | ----------------- | ------------------------------------------------------------------------------------ | | studio.enabled | true | Master switch. When false the module registers nothing — no routes, pages or code. | | studio.components | [] | Allowlist of editor-visible components (dirs/globs relative to srcDir). | | studio.ai.enabled | true | Disable the AI endpoints and editor UI entirely. | | studio.ai.model | claude-opus-4-8 | Model id (also overridable via NUXT_STUDIO_AI_MODEL). |

Preview tokens are signed with runtimeConfig.studio.previewSecret (override via NUXT_STUDIO_PREVIEW_SECRET; defaults to a per-build random value).

How it works

  1. Catalog — nuxt-component-meta extracts props/slots from allowlisted SFCs; /api/_studio/components serves them as JSON Schema, which drives the auto-generated prop forms, the block picker docs, and the AI prompts. Approved runtime components merge into the same catalog.
  2. Storage — pages live in studio_pages (draft tree + published tree as jsonb, versioned); snapshots in studio_page_variants; generated components in studio_components (source + compiled + meta + approval status).
  3. Rendering — a lowest-priority catch-all route resolves unknown paths against the database and renders the published tree with @comark/vue's ComarkRenderer. Your explicit routes always win; publishing requires no rebuild. A client plugin mounts approved runtime components globally.
  4. Editing — the editor iframe loads the real page with a short-lived signed preview token (draft tree); edits stream into it over a same-origin postMessage protocol (tree:update, select, hover, highlight).
  5. AI component pipeline — prompt → SFC → server compile (host's vue/compiler-sfc, bundler-free output) → pending registry row → opaque-origin sandbox preview (<iframe sandbox="allow-scripts"> + CSP) → approval → public runtime feed.

For the full build log — per-phase decisions, verified exit criteria and gotchas — see PLAN.md.

Contribution

# Install dependencies
vp install

# Generate type stubs
npm run dev:prepare

# Start the playground database, migrate and seed it
cd playground && npm run db:up && npm run db:migrate && npm run db:seed && cd ..

# Develop with the playground
npm run dev

# Lint, type check, test, build
vp check
npm run test:types
vp test
npm run prepack

Module changes (src/module.ts routes/pages) need a dev-server restart; if generated route types stay stale, rebuild the stub and clear the jiti cache: npx nuxt-module-build build --stub && rm -rf node_modules/.cache/jiti && npx nuxt prepare playground.