Generative UI builder. Users describe the interface they want; the AI composes a JSON spec from a fixed component catalog and binds it to live data from NASA, SpaceX, and other registered sources. Specs are persisted in Supabase for later edits.
Built on:
- @json-render/core + @json-render/react — catalog-constrained schema-driven rendering.
- Vercel AI SDK + AI Gateway — streaming text + tool calling. One
AI_GATEWAY_API_KEYroutes to Anthropic, OpenAI, xAI, Google, Groq, and anyone else behind the gateway; swap models by string id (anthropic/claude-opus-4-7→openai/gpt-5.1) without touching code. - Next.js 15 (App Router, Node runtime for the streaming route).
- Supabase — Postgres + Auth + RLS for canvas storage.
- Upstash Redis — optional fetch-through cache for public API responses.
M0–M5 of the roadmap in design.md are scaffolded:
- ✅ Catalog (
lib/catalog.ts) + React registry (lib/registry.tsx). - ✅ Data sources: SpaceX (upcoming, past), NASA (APOD, Mars rover, NeoWs).
- ✅ Streaming route (
app/api/canvas/run) with tool-call hydration over custom SSE. - ✅ Client Canvas with chat, live Renderer, and hydration store.
- ✅ Supabase schema + save/load/list API.
- ⏭ Inspector (click-to-edit), auth pages, version history UI, public share page — to come.
# 1. Install
cd stellar-canvas
npm install
# 2. Configure
cp .env.example .env.local
# - AI_GATEWAY_API_KEY is the only credential you need for the model (one key, every provider).
# Get one at https://vercel.com/ai-gateway. When deployed to Vercel it's auto-provided.
# - STELLAR_DEFAULT_MODEL picks the default model (e.g. "anthropic/claude-opus-4-7").
# - NASA_API_KEY=DEMO_KEY works for light use; free key at https://api.nasa.gov.
# - Supabase vars are optional until you wire auth — the canvas generator runs without them.
# - Want to bypass the gateway? Set STELLAR_USE_DIRECT_PROVIDERS=true and provide
# ANTHROPIC_API_KEY / OPENAI_API_KEY.
# 3. (Optional) Supabase
# Create a project at supabase.com, run the migration:
# supabase db push
# or paste supabase/migrations/0001_init.sql into the SQL editor.
# 4. Dev
npm run dev
# → http://localhost:3000prompt ──► /api/canvas/run (streamText)
│
├─ tool calls ──► data sources ──► cache ──► hydration SSE events
│
└─ text stream ──► createSpecStreamCompiler ──► spec SSE events
│
client ◄────┤
▼
<Renderer spec registry state/>
The model never inlines API data into props; it returns a spec with $state references (e.g. {"$state": "/sources/spacex_upcoming_3/0/name"}) that the Renderer resolves against the hydrated state object. This keeps specs tiny, portable, and safely re-hydratable.
Create lib/sources/mysource.ts:
import { z } from "zod";
import type { DataSource } from "./types";
export const myThing: DataSource = {
id: "my.thing",
label: "My Thing",
description: "Plain-English sentence the model will see as a tool description.",
ttlSeconds: 300,
input: z.object({ limit: z.number().int().default(5) }),
output: z.array(z.object({ name: z.string(), count: z.number() })),
statePath: ({ limit }) => `/sources/my_thing_${limit}`,
fetch: async ({ limit }) => { /* … */ },
};Then register it in lib/sources/registry.ts. The tool, proxy route, cache entry, and picker chip are all derived automatically.
- Add a Zod entry in
lib/catalog.tsundercomponents:— props and a shortdescription(the description ships to the model). - Add a React impl in
lib/registry.tsxunderdefineRegistry(catalog, { components: { … } }).
Never pass user/AI strings to dangerouslySetInnerHTML or as src without URL validation. The Zod schema on Image.src + Next.js remotePatterns in next.config.ts are the two guards.
app/
api/canvas/run/ streaming generation (SSE)
api/canvas/save/ create or version a canvas
api/canvas/[id]/ load / delete
api/canvas/list/ list user canvases
api/sources/[id]/ read-through proxy for a single source
page.tsx Canvas editor
components/canvas/ ChatPanel, SourcePanel, Canvas
lib/
catalog.ts json-render catalog (Zod-typed components)
registry.tsx React impls for catalog components
state.ts hydration store (zustand)
useCanvasStream.ts SSE consumer + spec stream compiler
ai/ model resolver + system prompt
sources/ DataSource type + plugins + cache + registry
supabase/ browser/server/admin clients
supabase/migrations/ SQL schema + RLS
- Catalog is the only allow-list. The model cannot render arbitrary HTML.
$statepath resolution is sealed: unresolved paths render as empty strings, not errors.- Supabase RLS gates every read/write on
owner_id = auth.uid()(oris_public). - Service-role key lives in
SUPABASE_SERVICE_ROLE_KEY, server-only. - Rate limiting belongs on
/api/canvas/runonce Upstash is wired (hook is inlib/sources/cache.ts).