RMHVibe — Implementation Spec¶
Overview¶
RMHVibe transforms the RMH Studios homepage into an AI-powered “vibe” platform. Users enter a prompt and get a fully generated, shareable, collaboratively-editable webpage — instantly. Think of it as a public creative scratchpad where any prompt becomes a living page.
1. Revised Homepage (/_site/index.tsx)¶
Replace the ComingSoonGate with a clean, minimal landing page.
Layout¶
Full-viewport centered column, no sidebar chrome needed (the
_sitelayout wraps it so the sidebar is still present on desktop)Title:
RMH Studios— large, bold, sparseTagline:
The everything platform.Prompt input: a single wide text input with placeholder
"Where do you want to go?"and a Submit button (or Enter key)On submit → navigate to
/v/new?prompt=<encoded>which triggers generation
Behavior¶
Input is uncontrolled until submit; no debounce/streaming needed here
If the user is on mobile, auto-focus the input after mount
No auth required to generate a vibe page
2. Database Model¶
Add to prisma/schema.prisma:
model VibePage {
id String @id @default(cuid())
slug String @unique // short URL-safe identifier, e.g. "neon-city-3k"
prompt String // original user prompt
html String // full generated HTML (self-contained, no external deps required)
conversationHistory Json // array of { role, content } turns for "customize" follow-ups
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([slug])
@@map("vibe_page")
}
Run migration:
npx prisma migrate dev --name add_vibe_page
3. Route Structure¶
Path |
File |
Purpose |
|---|---|---|
|
|
New minimalist homepage |
|
|
Renders a saved vibe page |
|
handled inside |
Triggers generation, then redirects to permanent slug |
Recommended approach: v.new.tsx + v.$slug.tsx¶
v.new.tsx— server-side loader reads?prompt=, calls DeepSeek, saves to DB, redirects to/v/<slug>v.$slug.tsx— loads page from DB by slug, renders the HTML + customize toolbar
4. Slug Generation¶
Keep slugs short (2–4 words) and human-readable.
Strategy: Ask DeepSeek to suggest a slug as part of the same generation call (no extra latency). Fall back to nanoid(8) if the suggestion is unusable.
Slug rules:
Lowercase kebab-case
Max 32 chars
No special chars except
-Must be unique — retry with appended random suffix if collision
5. DeepSeek API Integration¶
RMH Studios already has the openai package (v6) — DeepSeek’s API is OpenAI-compatible.
Client setup (app/lib/deepseek.ts)¶
import OpenAI from 'openai';
export const deepseek = new OpenAI({
baseURL: 'https://api.deepseek.com',
apiKey: process.env.DEEPSEEK_API_KEY,
});
Generation server function (app/lib/vibe.server.ts)¶
export async function generateVibePage(prompt: string, history: Message[] = []) {
const messages = [
{
role: 'system',
content: VIBE_SYSTEM_PROMPT,
},
...history,
{ role: 'user', content: prompt },
];
const res = await deepseek.chat.completions.create({
model: 'deepseek-chat', // fast, cheap
messages,
temperature: 1.0,
max_tokens: 4096,
response_format: { type: 'json_object' },
});
return JSON.parse(res.choices[0].message.content) as {
slug: string;
html: string;
};
}
System prompt¶
You are a creative web designer. Given a user's prompt, generate a complete, self-contained single-page HTML document that visually captures the vibe of their request.
Rules:
- Use only inline styles and a <style> block — no external stylesheets or scripts
- Make it visually striking: bold typography, colors, layout that fits the theme
- Include real readable content that matches the prompt (text, sections, fake data if helpful)
- Add subtle CSS animations where they enhance the vibe (no JS needed)
- The page should look finished, not like a template
- Respond ONLY with a JSON object: { "slug": "<2-4 word kebab slug>", "html": "<full HTML string>" }
- Keep the slug under 32 chars and relevant to the prompt
- Speed over perfection — it can be customized later
6. /v/new Route — Generation Flow¶
File: app/routes/v.new.tsx
loader:
1. Read ?prompt from search params
2. If missing → redirect to /
3. Call generateVibePage(prompt)
4. slugify + uniqueness check against DB (retry up to 3x with nanoid suffix)
5. INSERT VibePage { slug, prompt, html, conversationHistory: [{ role: 'user', content: prompt }, { role: 'assistant', content: html }] }
6. throw redirect(`/v/${slug}`)
While loading (step 1–5), show a loading screen: “Creating your vibe…” with a subtle animation.
Since TanStack Router loaders are async, the loading UI is the route’s pendingComponent.
7. /v/$slug Route — Vibe Page Viewer¶
File: app/routes/v.$slug.tsx
Loader¶
Fetch
VibePageby slug from DB404 if not found
Render¶
The route renders outside the
_sitelayout (no sidebar) — it should be a full-screen experienceRender the stored
htmlin a sandboxed<iframe srcdoc={html}>— this isolates the generated content from the app’s JS/CSSOn top of the iframe, overlay a minimal toolbar (top-right corner, semi-transparent):
✏️ Customizebutton — opens a slide-up/slide-over panel with a text input🔗 Sharebutton — copies the URL to clipboard← Backlink — goes to homepage
Customize flow¶
User types a customization prompt and submits
Client calls a server function
customizeVibePage(slug, newPrompt)Server function:
Loads existing
conversationHistoryfrom DBAppends new user message
Calls
generateVibePagewith full history (so DeepSeek understands the context)Updates
VibePage.htmlandconversationHistoryin DBReturns new
html
Client updates the iframe’s
srcdocwith the new HTMLThe URL doesn’t change — same slug, updated content
Anyone can customize — no auth required. Last write wins (optimistic, no locking needed at this scale).
8. File Map¶
app/
lib/
deepseek.ts # DeepSeek OpenAI-compat client
vibe.server.ts # generateVibePage(), customizeVibePage() server fns
routes/
_site/
index.tsx # NEW: minimalist homepage with prompt input
v.new.tsx # loader: generate + save + redirect
v.$slug.tsx # viewer: iframe render + customize toolbar
prisma/
schema.prisma # add VibePage model
migrations/
..._add_vibe_page/ # generated migration
9. Implementation Order¶
Prisma — add
VibePagemodel, run migrationDeepSeek client —
app/lib/deepseek.tsServer logic —
app/lib/vibe.server.tswithgenerateVibePageandcustomizeVibePage/v/newroute — generation loader + pending/loading UI/v/$slugroute — iframe viewer + customize panelHomepage — replace
ComingSoonGatewith prompt input UI
10. Open Questions / Later¶
Rate limiting: for now none — DeepSeek is cheap. Add per-IP limiting if abused.
Page history: current design overwrites on customize. Could add a
versions Json[]field later.Auth-gated “my pages” dashboard — not in scope for v1.
The
/v/subdirectory intentionally uses a flat slug (/v/neon-city) not nested paths.