vibes
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePlan mode: If you are planning work, this entire skill is ONE plan step: "Invoke /vibes:vibes". Do not decompose the steps below into separate plan tasks.
Display this ASCII art immediately when starting:
░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓███████▓▒░░▒▓████████▓▒░░▒▓███████▓▒░
░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░
░▒▓█▓▒▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░
░▒▓█▓▒▒▓█▓▒░░▒▓█▓▒░▒▓███████▓▒░░▒▓██████▓▒░ ░▒▓██████▓▒░
░▒▓█▓▓█▓▒░ ░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░
░▒▓█▓▓█▓▒░ ░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░
░▒▓██▓▒░ ░▒▓█▓▒░▒▓███████▓▒░░▒▓████████▓▒░▒▓███████▓▒░规划模式:如果您正在规划工作,整个技能是一个规划步骤:"Invoke /vibes:vibes"。请勿将以下步骤拆分为单独的规划任务。
启动时立即显示此ASCII艺术字:
░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓███████▓▒░░▒▓████████▓▒░░▒▓███████▓▒░
░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░
░▒▓█▓▒▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░
░▒▓█▓▒▒▓█▓▒░░▒▓█▓▒░▒▓███████▓▒░░▒▓██████▓▒░ ░▒▓██████▓▒░
░▒▓█▓▓█▓▒░ ░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░
░▒▓█▓▓█▓▒░ ░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░ ░▒▓█▓▒░
░▒▓██▓▒░ ░▒▓█▓▒░▒▓███████▓▒░░▒▓████████▓▒░▒▓███████▓▒░Quick Navigation
快速导航
- Pre-Flight Check - Validate Connect setup before coding
- Core Rules - Essential guidelines for app generation
- Generation Process - Design reasoning and code output
- Assembly Workflow - Build the final app
- UI Style & Theming - OKLCH colors and design patterns
- Fireproof API - Database operations and hooks
- AI Features - Optional AI integration
- Common Mistakes - Avoid these pitfalls
- Deployment Options - Where to deploy
Vibes DIY App Generator
Vibes DIY应用生成器
Generate React web applications using Fireproof for local-first data persistence.
使用Fireproof生成支持本地优先数据持久化的React Web应用。
Pre-Flight Check: Connect Status
飞行前检查:Connect状态
MANDATORY: Complete these steps BEFORE generating any app code.
Step 0: Check Connect Status
Run this command first to validate all required credentials:
bash
if test -f "./.env" && \
grep -qE "^VITE_CLERK_PUBLISHABLE_KEY=pk_(test|live)_" ./.env 2>/dev/null && \
grep -qE "^VITE_API_URL=" ./.env 2>/dev/null && \
grep -qE "^VITE_CLOUD_URL=" ./.env 2>/dev/null; then
echo "CONNECT_READY"
else
echo "CONNECT_NOT_READY"
fiIf output is "CONNECT_NOT_READY", Connect setup is required:
Connect with Clerk authentication is required for Vibes apps.
Invoke to deploy Connect, then return here when complete.
/vibes:connectIf Connect IS set up (CONNECT_READY), proceed directly to app generation. The assemble script will populate Connect config from .env.
Platform Name vs User Intent: "Vibes" is the name of this app platform (Vibes DIY). When users say "vibe" or "vibes" in their prompt, interpret it as:
- Their project/brand name ("my vibes tracker")
- A positive descriptor ("good vibes app")
- NOT as "mood/atmosphere" literally
Do not default to ambient mood generators, floating orbs, or meditation apps unless explicitly requested.
Import Map Note: The import map points to , a bridge module that wraps the raw Fireproof bundle with sync status forwarding and an onTock kick effect. Your code uses and the browser resolves this through the bridge → . This is intentional—the bridge ensures subscribers see synced data and that gets live sync status via a window global.
use-fireproof/fireproof-vibes-bridge.jsimport { useFireproofClerk } from "use-fireproof"./fireproof-clerk-bundle.jsuseLiveQuerySyncStatusDot必填:在生成任何应用代码前完成以下步骤。
步骤0:检查Connect状态
首先运行此命令以验证所有必需的凭据:
bash
if test -f "./.env" && \
grep -qE "^VITE_CLERK_PUBLISHABLE_KEY=pk_(test|live)_" ./.env 2>/dev/null && \
grep -qE "^VITE_API_URL=" ./.env 2>/dev/null && \
grep -qE "^VITE_CLOUD_URL=" ./.env 2>/dev/null; then
echo "CONNECT_READY"
else
echo "CONNECT_NOT_READY"
fi如果输出为"CONNECT_NOT_READY",则需要设置Connect:
Vibes应用需要搭配Clerk身份验证的Connect。
调用部署Connect,完成后返回此处。
/vibes:connect如果Connect已设置完成(CONNECT_READY),直接进入应用生成。组装脚本会从.env文件中填充Connect配置。
平台名称与用户意图:"Vibes"是此应用平台的名称(Vibes DIY)。当用户在提示中提到"vibe"或"vibes"时,将其解读为:
- 他们的项目/品牌名称(如"my vibes tracker")
- 积极的描述词(如"good vibes app")
- 不要字面理解为"情绪/氛围"
除非用户明确要求,否则不要默认生成氛围情绪生成器、悬浮球体或冥想类应用。
导入映射说明:导入映射将指向,这是一个桥接模块,它包装了原始Fireproof包,实现同步状态转发和onTock触发效果。您的代码使用,浏览器会通过桥接模块解析到。这是有意设计的——桥接模块确保订阅者能看到同步后的数据,并且能通过全局window对象获取实时同步状态。
use-fireproof/fireproof-vibes-bridge.jsimport { useFireproofClerk } from "use-fireproof"./fireproof-clerk-bundle.jsuseLiveQuerySyncStatusDotCore Rules
核心规则
- Use JSX - Standard React syntax with Babel transpilation
- Single HTML file - App code assembled into template
- Fireproof for data - Use for database + sync
useFireproofClerk - Auto-detect Connect - Template handles Clerk auth when Connect is configured
- Tailwind for styling - Mobile-first, responsive design
- 使用JSX - 标准React语法,支持Babel转译
- 单HTML文件 - 应用代码组装到模板中
- Fireproof处理数据 - 使用实现数据库+同步
useFireproofClerk - 自动检测Connect - 模板在配置Connect后自动处理Clerk身份验证
- Tailwind进行样式设计 - 移动端优先的响应式设计
Generation Process
生成流程
Step 1: Design Reasoning
步骤1:设计推理
Before writing code, reason about the design in tags:
<design><design>
- What is the core functionality and user flow?
- What OKLCH colors fit this theme? (dark/light, warm/cool, vibrant/muted)
- What layout best serves the content? (cards, list, dashboard, single-focus)
- What micro-interactions would feel satisfying? (hover states, transitions)
- What visual style matches the purpose? (minimal, bold, playful, professional)
</design>Assembly: generate (preserve) —injects your code as-is. Import and export statements work because the import map intercepts bare specifiers at runtime. Code examples below include imports.assemble.jsIf you're a launch/builder agent: Sell transforms vibes artifacts by stripping imports. When generating app.jsx for the launch pipeline, omit all imports — the sell template provides everything. Follow builder.md rules; use only the patterns from examples below, not the import lines.
编写代码前,在标签中进行设计推理:
<design><design>
- 核心功能和用户流程是什么?
- 哪些OKLCH颜色适合此主题?(深色/浅色、暖色/冷色、鲜艳/柔和)
- 哪种布局最适合内容展示?(卡片、列表、仪表板、单焦点)
- 哪些微交互会带来愉悦感?(悬停状态、过渡动画)
- 哪种视觉风格符合应用目的?(极简、大胆、趣味、专业)
</design>组装:生成(保留) —会按原样注入您的代码。导入和导出语句可以正常工作,因为导入映射会在运行时拦截裸模块说明符。下面的代码示例包含导入语句。assemble.js如果您是启动/构建代理:通过移除导入语句来优化Vibes产物。为启动流水线生成app.jsx时,省略所有导入——售卖模板会提供所有依赖。遵循builder.md规则;仅使用以下示例中的模式,不要使用导入行。
Step 2: Output Code
步骤2:输出代码
After reasoning, output the complete JSX in tags:
<code><code>
import React, { useState } from "react";
import { useFireproofClerk } from "use-fireproof";
export default function App() {
const { database, useLiveQuery, useDocument, syncStatus } = useFireproofClerk("app-name-db");
// ... component logic
return (
<div className="min-h-screen bg-[#f1f5f9] p-4">
{/* Sync status indicator (optional) */}
<div className="text-xs text-gray-500 mb-2">Sync: {syncStatus}</div>
{/* Your app UI */}
</div>
);
}
</code>⚠️ CRITICAL: Fireproof Hook Pattern
The package exports ONLY . Always use this pattern:
@necrodome/fireproof-clerkuseFireproofClerkjsx
// ✅ CORRECT - This is the ONLY pattern that works
import { useFireproofClerk } from "use-fireproof";
const { database, useDocument, useLiveQuery, syncStatus } = useFireproofClerk("my-db");
const { doc, merge } = useDocument({ _id: "doc1" });
// ❌ WRONG - DO NOT USE (old use-vibes API)
import { toCloud, useFireproof } from "use-fireproof"; // WRONG - old API
import { useDocument } from "use-fireproof"; // WRONG - standalone import
const { attach } = useFireproof("db", { attach: toCloud() }); // WRONG - old patternSync Status: provides the current sync state. Values: , , , , . Display it for user feedback.
syncStatus"idle""connecting""synced""reconnecting""error"Connect Configuration: Generated apps require Clerk authentication and cloud sync.
The script populates from your file.
Apps will show a configuration error if credentials are missing.
assemble.jswindow.__VIBES_CONFIG__.env推理完成后,在标签中输出完整的JSX代码:
<code><code>
import React, { useState } from "react";
import { useFireproofClerk } from "use-fireproof";
export default function App() {
const { database, useLiveQuery, useDocument, syncStatus } = useFireproofClerk("app-name-db");
// ... 组件逻辑
return (
<div className="min-h-screen bg-[#f1f5f9] p-4">
{/* 同步状态指示器(可选) */}
<div className="text-xs text-gray-500 mb-2">Sync: {syncStatus}</div>
{/* 您的应用UI */}
</div>
);
}
</code>⚠️ 重要:Fireproof钩子模式
@necrodome/fireproof-clerkuseFireproofClerkjsx
// ✅ 正确 - 这是唯一有效的模式
import { useFireproofClerk } from "use-fireproof";
const { database, useDocument, useLiveQuery, syncStatus } = useFireproofClerk("my-db");
const { doc, merge } = useDocument({ _id: "doc1" });
// ❌ 错误 - 请勿使用(旧版use-vibes API)
import { toCloud, useFireproof } from "use-fireproof"; // 错误 - 旧版API
import { useDocument } from "use-fireproof"; // 错误 - 独立导入
const { attach } = useFireproof("db", { attach: toCloud() }); // 错误 - 旧模式同步状态:提供当前的同步状态,取值包括:、、、、。建议显示此状态以提供用户反馈。
syncStatus"idle""connecting""synced""reconnecting""error"Connect配置:生成的应用需要Clerk身份验证和云同步。脚本会从您的.env文件中填充。如果缺少凭据,应用会显示配置错误。
assemble.jswindow.__VIBES_CONFIG__Assembly Workflow
组装工作流
- Extract the code from tags and write to
<code>app.jsx - Optionally save content to
<design>for documentationdesign.md - Run assembly:
bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/assemble.js" app.jsx index.html - Deploy the app so the user can see it. Clerk auth requires a public URL — the app cannot be viewed locally. Auto-invoke /vibes:cloudflare to deploy, then present the live URL.
⚠️ DEPRECATED API: Never use the oldwithuseFireproofpattern. See references/DEPRECATED.md for migration details if you encounter legacy code.toCloud()
- 从标签中提取代码并写入
<code>app.jsx - (可选)将内容保存到
<design>作为文档design.md - 运行组装命令:
bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/assemble.js" app.jsx index.html - 部署应用以便用户查看。Clerk身份验证需要公共URL——应用无法在本地查看。自动调用进行部署,然后提供实时URL。
/vibes:cloudflare
⚠️ 已废弃API: 请勿使用旧版搭配useFireproof的模式。如果遇到遗留代码,请参考references/DEPRECATED.md了解迁移细节。toCloud()
UI Style & Theming
UI样式与主题
OKLCH Colors (Recommended)
推荐使用OKLCH颜色
Use OKLCH for predictable, vibrant colors. Unlike RGB/HSL, OKLCH has perceptual lightness - changing L by 10% looks the same across all hues.
css
oklch(L C H)
/* L = Lightness (0-1): 0 black, 1 white */
/* C = Chroma (0-0.4): 0 gray, higher = more saturated */
/* H = Hue (0-360): color wheel degrees */Theme-appropriate palettes:
jsx
{/* Dark/moody theme */}
className="bg-[oklch(0.15_0.02_250)]" /* Deep blue-black */
{/* Warm/cozy theme */}
className="bg-[oklch(0.25_0.08_30)]" /* Warm brown */
{/* Fresh/bright theme */}
className="bg-[oklch(0.95_0.03_150)]" /* Mint white */
{/* Vibrant accent */}
className="bg-[oklch(0.7_0.2_145)]" /* Vivid green */使用OKLCH实现可预测的鲜艳颜色。与RGB/HSL不同,OKLCH具有感知亮度——将L值改变10%在所有色调中视觉效果一致。
css
oklch(L C H)
/* L = 亮度 (0-1): 0为黑色,1为白色 */
/* C = 色度 (0-0.4): 0为灰色,值越高饱和度越高 */
/* H = 色相 (0-360): 色轮度数 */主题适配调色板:
jsx
{/* 深色/氛围感主题 */}
className="bg-[oklch(0.15_0.02_250)]" /* 深蓝黑色 */
{/* 温暖/舒适主题 */}
className="bg-[oklch(0.25_0.08_30)]" /* 暖棕色 */
{/* 清新/明亮主题 */}
className="bg-[oklch(0.95_0.03_150)]" /* 薄荷白 */
{/* 鲜艳强调色 */}
className="bg-[oklch(0.7_0.2_145)]" /* 鲜绿色 */Better Gradients with OKLCH
使用OKLCH实现更平滑的渐变
Use for smooth gradients without muddy middle zones:
in oklchjsx
{/* Smooth gradient - no gray middle */}
className="bg-[linear-gradient(in_oklch,oklch(0.6_0.2_250),oklch(0.6_0.2_150))]"
{/* Sunset gradient */}
className="bg-[linear-gradient(135deg_in_oklch,oklch(0.7_0.25_30),oklch(0.5_0.2_330))]"
{/* Dark glass effect */}
className="bg-[linear-gradient(180deg_in_oklch,oklch(0.2_0.05_270),oklch(0.1_0.02_250))]"使用实现无浑浊中间色的平滑渐变:
in oklchjsx
{/* 平滑渐变 - 无灰色中间色 */}
className="bg-[linear-gradient(in_oklch,oklch(0.6_0.2_250),oklch(0.6_0.2_150))]"
{/* 日落渐变 */}
className="bg-[linear-gradient(135deg_in_oklch,oklch(0.7_0.25_30),oklch(0.5_0.2_330))]"
{/* 深色毛玻璃效果 */}
className="bg-[linear-gradient(180deg_in_oklch,oklch(0.2_0.05_270),oklch(0.1_0.02_250))]"Neobrute Style (Optional)
Neobrute风格(可选)
For bold, graphic UI:
- Borders: thick 4px, dark
border-[#0f172a] - Shadows: hard offset
shadow-[6px_6px_0px_#0f172a] - Corners: square (0px) OR pill (rounded-full) - no in-between
jsx
<button className="px-6 py-3 bg-[oklch(0.95_0.02_90)] border-4 border-[#0f172a] shadow-[6px_6px_0px_#0f172a] hover:shadow-[4px_4px_0px_#0f172a] font-bold">
Click Me
</button>用于大胆的图形化UI:
- 边框:4px粗边框,深色
border-[#0f172a] - 阴影:硬偏移
shadow-[6px_6px_0px_#0f172a] - 圆角:方形(0px)或药丸形(rounded-full)- 不使用中间值
jsx
<button className="px-6 py-3 bg-[oklch(0.95_0.02_90)] border-4 border-[#0f172a] shadow-[6px_6px_0px_#0f172a] hover:shadow-[4px_4px_0px_#0f172a] font-bold">
Click Me
</button>Glass Morphism (Dark themes)
毛玻璃效果(深色主题)
jsx
<div className="bg-white/5 backdrop-blur-lg border border-white/10 rounded-2xl">
{/* content */}
</div>jsx
<div className="bg-white/5 backdrop-blur-lg border border-white/10 rounded-2xl">
{/* 内容 */}
</div>Color Modifications
颜色调整
Lighten/darken using L value:
- Hover: increase L by 0.05-0.1
- Active/pressed: decrease L by 0.05
- Disabled: reduce C to near 0
通过L值调整明暗:
- 悬停状态:L值增加0.05-0.1
- 激活/按下状态:L值减少0.05
- 禁用状态:C值降至接近0
Fireproof API
Fireproof API
Fireproof is a local-first database - no loading or error states required, just empty data states. Data persists across sessions and syncs in real-time when Connect is configured.
Fireproof是本地优先数据库——无需加载或错误状态,只需处理空数据状态。数据在会话间持久化,配置Connect后可实现实时同步。
Setup
设置
jsx
import { useFireproofClerk } from "use-fireproof";
const { database, useLiveQuery, useDocument, syncStatus } = useFireproofClerk("my-app-db");Note: When Connect is configured (via .env), the template wraps your App in , enabling authenticated cloud sync automatically. Your code just uses .
ClerkFireproofProvideruseFireproofClerkjsx
import { useFireproofClerk } from "use-fireproof";
const { database, useLiveQuery, useDocument, syncStatus } = useFireproofClerk("my-app-db");注意:当配置Connect后(通过.env),模板会将您的App包裹在中,自动启用带身份验证的云同步。您的代码只需使用即可。
ClerkFireproofProvideruseFireproofClerkChoosing Your Pattern
选择合适的模式
useDocument = Form-like editing. Accumulate changes with , then save with or . Best for: text inputs, multi-field forms, editing workflows.
merge()submit()save()database.put() + useLiveQuery = Immediate state changes. Each action writes directly. Best for: counters, toggles, buttons, any single-action updates.
jsx
// FORM PATTERN: User types, then submits
const { doc, merge, submit } = useDocument({ title: "", body: "", type: "post" });
// merge({ title: "..." }) on each keystroke, submit() when done
// IMMEDIATE PATTERN: Each click is a complete action
const { docs } = useLiveQuery("_id", { key: "counter" });
const count = docs[0]?.value || 0;
const increment = () => database.put({ _id: "counter", value: count + 1 });WARNING — merge() + submit() timing trap: Never a computed value (like or ) and call in the same event handler. React batches state updates, so reads stale state and the merged field may be missing from the saved document. Use with explicit fields instead:
merge()Date.now()crypto.randomUUID()submit()submit()database.put()jsx
// BAD — ts may not be saved due to React batching
merge({ ts: Date.now() });
submit();
// GOOD — all fields written atomically
await database.put({ text: doc.text, ts: Date.now(), type: "item" });
reset();useDocument = 类表单编辑。使用累积更改,然后使用或保存。最适合:文本输入、多字段表单、编辑工作流。
merge()submit()save()database.put() + useLiveQuery = 即时状态更改。每个操作直接写入数据库。最适合:计数器、开关、按钮、任何单操作更新。
jsx
// 表单模式:用户输入,然后提交
const { doc, merge, submit } = useDocument({ title: "", body: "", type: "post" });
// 每次按键时调用merge({ title: "..." }),完成后调用submit()
// 即时模式:每次点击都是完整操作
const { docs } = useLiveQuery("_id", { key: "counter" });
const count = docs[0]?.value || 0;
const increment = () => database.put({ _id: "counter", value: count + 1 });警告 — merge() + submit()时序陷阱: 切勿在同一事件处理程序中计算值(如或)并调用。React会批量处理状态更新,因此会读取旧状态,导致合并的字段可能不会被保存到文档中。请改用并传入显式字段:
merge()Date.now()crypto.randomUUID()submit()submit()database.put()jsx
// 错误 — 由于React批处理,ts可能不会被保存
merge({ ts: Date.now() });
submit();
// 正确 — 所有字段原子性写入
await database.put({ text: doc.text, ts: Date.now(), type: "item" });
reset();useDocument - Form State (NOT useState)
useDocument - 表单状态(请勿使用useState)
IMPORTANT: Don't use for form data. Use and from . Only use for ephemeral UI state (active tabs, open/closed panels).
useState()merge()submit()useDocumentuseStatejsx
// Create new documents (auto-generated _id recommended)
const { doc, merge, submit, reset } = useDocument({ text: "", type: "item" });
// Edit existing document by known _id
const { doc, merge, save } = useDocument({ _id: "user-profile:abc@example.com" });
// Methods:
// - merge(updates) - update fields: merge({ text: "new value" })
// - submit(e) - save + reset (for forms creating new items)
// - save() - save without reset (for editing existing items)
// - reset() - discard changes重要:请勿使用处理表单数据。请使用提供的和。仅将用于临时UI状态(如活动标签页、面板展开/关闭)。
useState()useDocumentmerge()submit()useStatejsx
// 创建新文档(推荐自动生成_id)
const { doc, merge, submit, reset } = useDocument({ text: "", type: "item" });
// 通过已知_id编辑现有文档
const { doc, merge, save } = useDocument({ _id: "user-profile:abc@example.com" });
// 方法:
// - merge(updates) - 更新字段:merge({ text: "new value" })
// - submit(e) - 保存+重置(适用于创建新项的表单)
// - save() - 保存但不重置(适用于编辑现有项)
// - reset() - 丢弃更改useLiveQuery - Real-time Lists
useLiveQuery - 实时列表
jsx
// Simple: query by field value
const { docs } = useLiveQuery("type", { key: "item" });
// Recent items (_id is roughly temporal - great for simple sorting)
const { docs } = useLiveQuery("_id", { descending: true, limit: 100 });
// Range query
const { docs } = useLiveQuery("rating", { range: [3, 5] });CRITICAL: Custom index functions are SANDBOXED and CANNOT access external variables. Query all, filter in render:
jsx
// GOOD: Query all, filter in render
const { docs: allItems } = useLiveQuery("type", { key: "item" });
const filtered = allItems.filter(d => d.category === selectedCategory);jsx
// 简单查询:按字段值查询
const { docs } = useLiveQuery("type", { key: "item" });
// 最新项(_id大致按时间排序 - 非常适合简单排序)
const { docs } = useLiveQuery("_id", { descending: true, limit: 100 });
// 范围查询
const { docs } = useLiveQuery("rating", { range: [3, 5] });关键:自定义索引函数运行在沙箱中,无法访问外部变量。请查询所有数据,在渲染时进行过滤:
jsx
// 正确:查询所有数据,渲染时过滤
const { docs: allItems } = useLiveQuery("type", { key: "item" });
const filtered = allItems.filter(d => d.category === selectedCategory);Direct Database Operations
直接数据库操作
jsx
// Create/update
const { id } = await database.put({ text: "hello", type: "item" });
// Delete
await database.del(item._id);jsx
// 创建/更新
const { id } = await database.put({ text: "hello", type: "item" });
// 删除
await database.del(item._id);Common Pattern - Form + List
常见模式 - 表单+列表
jsx
import React from "react";
import { useFireproofClerk } from "use-fireproof";
export default function App() {
const { database, useLiveQuery, useDocument, syncStatus } = useFireproofClerk("my-db");
// Form for new items (submit resets for next entry)
const { doc, merge, submit } = useDocument({ text: "", type: "item" });
// Live list of all items of type "item"
const { docs } = useLiveQuery("type", { key: "item" });
return (
<div className="min-h-screen bg-[#f1f5f9] p-4">
{/* Optional sync status indicator */}
<div className="text-xs text-gray-500 mb-2">Sync: {syncStatus}</div>
<form onSubmit={submit} className="mb-4">
<input
value={doc.text}
onChange={(e) => merge({ text: e.target.value })}
className="w-full px-4 py-3 border-4 border-[#0f172a]"
/>
<button type="submit" className="mt-2 px-4 py-2 bg-[#0f172a] text-[#f1f5f9]">
Add
</button>
</form>
{docs.map(item => (
<div key={item._id} className="p-2 mb-2 bg-white border-4 border-[#0f172a]">
{item.text}
<button onClick={() => database.del(item._id)} className="ml-2 text-red-500">
Delete
</button>
</div>
))}
</div>
);
}jsx
import React from "react";
import { useFireproofClerk } from "use-fireproof";
export default function App() {
const { database, useLiveQuery, useDocument, syncStatus } = useFireproofClerk("my-db");
// 用于创建新项的表单(提交后重置以便输入下一项)
const { doc, merge, submit } = useDocument({ text: "", type: "item" });
// 所有类型为"item"的实时列表
const { docs } = useLiveQuery("type", { key: "item" });
return (
<div className="min-h-screen bg-[#f1f5f9] p-4">
{/* 可选同步状态指示器 */}
<div className="text-xs text-gray-500 mb-2">Sync: {syncStatus}</div>
<form onSubmit={submit} className="mb-4">
<input
value={doc.text}
onChange={(e) => merge({ text: e.target.value })}
className="w-full px-4 py-3 border-4 border-[#0f172a]"
/>
<button type="submit" className="mt-2 px-4 py-2 bg-[#0f172a] text-[#f1f5f9]">
Add
</button>
</form>
{docs.map(item => (
<div key={item._id} className="p-2 mb-2 bg-white border-4 border-[#0f172a]">
{item.text}
<button onClick={() => database.del(item._id)} className="ml-2 text-red-500">
Delete
</button>
</div>
))}
</div>
);
}AI Features (Optional)
AI功能(可选)
If the user's prompt suggests AI-powered features (chatbot, summarization, content generation, etc.), the app needs AI capabilities via the hook.
useAI如果用户提示中包含AI相关功能(聊天机器人、摘要、内容生成等),应用需要通过钩子添加AI能力。
useAIDetecting AI Requirements
检测AI需求
Look for these patterns in the user's prompt:
- "chatbot", "chat with AI", "ask AI"
- "summarize", "generate", "write", "create content"
- "analyze", "classify", "recommend"
- "AI-powered", "intelligent", "smart" (in context of features)
在用户提示中寻找以下模式:
- "chatbot"、"chat with AI"、"ask AI"
- "summarize"、"generate"、"write"、"create content"
- "analyze"、"classify"、"recommend"
- "AI-powered"、"intelligent"、"smart"(在功能语境下)
Collecting OpenRouter Key
收集OpenRouter密钥
When AI is needed, ask the user:
This app needs AI capabilities. Please provide your OpenRouter API key. Get one at: https://openrouter.ai/keys
Store the key for use with the flag during deployment.
--ai-key当需要AI功能时,请向用户询问:
此应用需要AI能力。请提供您的OpenRouter API密钥。 获取地址:https://openrouter.ai/keys
存储密钥以便在部署时使用标志。
--ai-keyUsing the useAI Hook
使用useAI钩子
The hook is automatically included in the template when AI features are detected:
useAIjsx
import React from "react";
import { useFireproofClerk } from "use-fireproof";
export default function App() {
const { database, useLiveQuery, syncStatus } = useFireproofClerk("ai-chat-db");
const { callAI, loading, error } = useAI();
const handleSend = async (message) => {
// Save user message
await database.put({ role: "user", content: message, type: "message" });
// Call AI
const response = await callAI({
model: "anthropic/claude-sonnet-4",
messages: [{ role: "user", content: message }]
});
// Save AI response
const aiMessage = response.choices[0].message.content;
await database.put({ role: "assistant", content: aiMessage, type: "message" });
};
// Handle limit exceeded
if (error?.code === 'LIMIT_EXCEEDED') {
return (
<div className="p-4 bg-amber-100 text-amber-800 rounded">
AI usage limit reached. Please wait for monthly reset or upgrade your plan.
</div>
);
}
// ... rest of UI
}检测到AI功能需求时,模板会自动包含钩子:
useAIjsx
import React from "react";
import { useFireproofClerk } from "use-fireproof";
export default function App() {
const { database, useLiveQuery, syncStatus } = useFireproofClerk("ai-chat-db");
const { callAI, loading, error } = useAI();
const handleSend = async (message) => {
// 保存用户消息
await database.put({ role: "user", content: message, type: "message" });
// 调用AI
const response = await callAI({
model: "anthropic/claude-sonnet-4",
messages: [{ role: "user", content: message }]
});
// 保存AI响应
const aiMessage = response.choices[0].message.content;
await database.put({ role: "assistant", content: aiMessage, type: "message" });
};
// 处理超出限制的情况
if (error?.code === 'LIMIT_EXCEEDED') {
return (
<div className="p-4 bg-amber-100 text-amber-800 rounded">
AI使用额度已达上限。请等待每月重置或升级您的计划。
</div>
);
}
// ... 其余UI代码
}useAI API
useAI API
jsx
const { callAI, loading, error, clearError } = useAI();
// callAI options
await callAI({
model: "anthropic/claude-sonnet-4", // or other OpenRouter models
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Hello!" }
],
temperature: 0.7, // optional
max_tokens: 1000 // optional
});
// error structure
error = {
code: "LIMIT_EXCEEDED" | "API_ERROR" | "NETWORK_ERROR",
message: "Human-readable error message"
}jsx
const { callAI, loading, error, clearError } = useAI();
// callAI选项
await callAI({
model: "anthropic/claude-sonnet-4", // 或其他OpenRouter模型
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Hello!" }
],
temperature: 0.7, // 可选
max_tokens: 1000 // 可选
});
// 错误结构
error = {
code: "LIMIT_EXCEEDED" | "API_ERROR" | "NETWORK_ERROR",
message: "人类可读的错误消息"
}Deployment with AI
部署AI应用
When deploying AI-enabled apps, include the OpenRouter key:
bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-cloudflare.js" \
--name myapp \
--file index.html \
--ai-key "sk-or-v1-your-key"部署支持AI的应用时,请包含OpenRouter密钥:
bash
node "${CLAUDE_PLUGIN_ROOT}/scripts/deploy-cloudflare.js" \
--name myapp \
--file index.html \
--ai-key "sk-or-v1-your-key"Sharing / Inviting Users
分享/邀请用户
The template includes a built-in invite UI in VibesPanel (the slide-out menu). For custom sharing in user app code, use the hook:
useSharingjavascript
const { inviteUser, listInvites, deleteInvite, findUser, ready } = window.useSharing();
// Invite by email
async function handleInvite(email) {
if (!ready) return;
const result = await inviteUser(email, 'read'); // 'read' or 'write'
console.log('Invited:', result);
}The hook is available on after Clerk loads. Check before calling methods.
window.useSharingready模板在VibesPanel(侧边滑出菜单)中内置了邀请UI。如需在用户应用代码中实现自定义分享功能,请使用钩子:
useSharingjavascript
const { inviteUser, listInvites, deleteInvite, findUser, ready } = window.useSharing();
// 通过邮箱邀请用户
async function handleInvite(email) {
if (!ready) return;
const result = await inviteUser(email, 'read'); // 'read' 或 'write'
console.log('Invited:', result);
}Clerk加载完成后,钩子会在上可用。调用方法前请检查状态。
useSharingwindow.useSharingreadyCommon Mistakes to Avoid
常见错误避免
- DON'T use for form fields - use
useStateuseDocument - DON'T use - use
Fireproof.fireproof()hookuseFireproofClerk() - DON'T use the old with
useFireproof- usetoCloud()insteaduseFireproofClerk - DON'T use white text on light backgrounds
- DON'T use directly - use
call-aihook instead (it handles proxying and limits)useAI - DON'T use Fireproof's API for images — it has a sync bug where blobs arrive after metadata, causing 404s on other devices. Store image data as Uint8Array directly on documents:
_filesjsx// Convert file to Uint8Array (with resize) async function fileToImageData(file, maxDim = 1200) { const bitmap = await createImageBitmap(file); const scale = Math.min(1, maxDim / Math.max(bitmap.width, bitmap.height)); const canvas = new OffscreenCanvas(bitmap.width * scale, bitmap.height * scale); canvas.getContext('2d').drawImage(bitmap, 0, 0, canvas.width, canvas.height); const blob = await canvas.convertToBlob({ type: 'image/jpeg', quality: 0.8 }); return new Uint8Array(await blob.arrayBuffer()); } // Display from Uint8Array function StoredImage({ data, type = 'image/jpeg', alt, className }) { const [url, setUrl] = useState(null); useEffect(() => { if (!data) return; // Fireproof CBOR round-trips Uint8Array as plain objects with numeric keys const bytes = data instanceof Uint8Array ? data : new Uint8Array(Object.values(data)); const objectUrl = URL.createObjectURL(new Blob([bytes], { type })); setUrl(objectUrl); return () => URL.revokeObjectURL(objectUrl); }, [data, type]); return url ? <img src={url} alt={alt} className={className} /> : null; } // Usage: <StoredImage data={doc.imageData} type={doc.imageType} alt="Photo" /> - DON'T call and
merge()in the same handler when adding computed fields (timestamps, UUIDs, derived values). React batches the state update fromsubmit(), somerge()writes the old state. Usesubmit()with explicit fields +database.put()instead.reset() - DON'T spread doc into
useDocument— internal CRDT metadata contaminates the write and can corrupt the database (database.put()errors). Build documents with explicit fields instead:missing blockjsx// BAD — spreads internal metadata await database.put({ ...doc, completed: true }); // GOOD — explicit fields only await database.put({ _id: doc._id, type: doc.type, todo: doc.todo, completed: true }); - DON'T wrap your app in - that's a vibes.diy platform-only component. Standalone apps use
VibeContextProviderdirectly.useFireproofClerk() - DON'T panic if you see "Cannot read properties of null (reading 'useContext')" - the template already handles the React singleton via in the import map. Check that the import map wasn't accidentally modified.
?external=react,react-dom - NOTE: Apps use — this bridge module wraps the local Fireproof bundle with sync status forwarding + onTock kick. The bundle itself (
/fireproof-vibes-bridge.js) is a temporary workaround that fixes a CID bug and includes sync improvements. Apps work correctly with it./fireproof-clerk-bundle.js - DON'T hand-write and assemble it manually — always generate through
app.jsx, even for test or diagnostic apps. The skill generates code that's compatible with the template by construction. Hand-written code may include imports or patterns that conflict with the template's runtime setup./vibes:vibes
- 请勿使用处理表单字段 - 请使用
useStateuseDocument - 请勿使用- 请使用
Fireproof.fireproof()钩子useFireproofClerk() - 请勿使用旧版搭配
useFireproof- 请改用toCloud()useFireproofClerk - 请勿在浅色背景上使用白色文本
- 请勿直接使用- 请改用
call-ai钩子(它会处理代理和限制)useAI - 请勿使用Fireproof的API处理图片——它存在同步bug,blob会在元数据之后到达,导致其他设备上出现404错误。 请将图片数据作为Uint8Array直接存储在文档中:
_filesjsx// 将文件转换为Uint8Array(支持缩放) async function fileToImageData(file, maxDim = 1200) { const bitmap = await createImageBitmap(file); const scale = Math.min(1, maxDim / Math.max(bitmap.width, bitmap.height)); const canvas = new OffscreenCanvas(bitmap.width * scale, bitmap.height * scale); canvas.getContext('2d').drawImage(bitmap, 0, 0, canvas.width, canvas.height); const blob = await canvas.convertToBlob({ type: 'image/jpeg', quality: 0.8 }); return new Uint8Array(await blob.arrayBuffer()); } // 从Uint8Array显示图片 function StoredImage({ data, type = 'image/jpeg', alt, className }) { const [url, setUrl] = useState(null); useEffect(() => { if (!data) return; // Fireproof CBOR会将Uint8Array转换为带数字键的普通对象 const bytes = data instanceof Uint8Array ? data : new Uint8Array(Object.values(data)); const objectUrl = URL.createObjectURL(new Blob([bytes], { type })); setUrl(objectUrl); return () => URL.revokeObjectURL(objectUrl); }, [data, type]); return url ? <img src={url} alt={alt} className={className} /> : null; } // 使用方式:<StoredImage data={doc.imageData} type={doc.imageType} alt="Photo" /> - 请勿在同一处理程序中调用和
merge()来添加计算字段 (时间戳、UUID、派生值)。React会批量处理submit()的状态更新,因此merge()会写入旧状态。请改用submit()传入显式字段 +database.put()。reset() - 请勿将的doc对象展开到
useDocument中——内部CRDT元数据 会污染写入操作,可能导致数据库损坏(出现database.put()错误)。 请改用显式字段构建文档:missing blockjsx// 错误 — 展开了内部元数据 await database.put({ ...doc, completed: true }); // 正确 — 仅使用显式字段 await database.put({ _id: doc._id, type: doc.type, todo: doc.todo, completed: true }); - 请勿将您的应用包裹在中——这是vibes.diy平台专用组件。独立应用直接使用
VibeContextProvider即可。useFireproofClerk() - 如果看到"Cannot read properties of null (reading 'useContext')"错误请不要惊慌——模板已通过导入映射中的处理React单例。请检查导入映射是否被意外修改。
?external=react,react-dom - 注意:应用使用——此桥接模块包装了本地Fireproof包,实现同步状态转发+onTock触发效果。包本身(
/fireproof-vibes-bridge.js)是临时解决方案,修复了CID bug并包含同步改进。应用配合它可以正常工作。/fireproof-clerk-bundle.js - 请勿手动编写并手动组装——始终通过
app.jsx生成,即使是测试或诊断应用。该技能生成的代码 天生与模板兼容。手动编写的代码可能包含与模板运行时设置冲突的导入 或模式。/vibes:vibes
When to Read Extended Docs
何时阅读扩展文档
The shipped cache files contain detailed reference material. Read them when the user's prompt matches these signals:
| Need | Signal in Prompt | Read This |
|---|---|---|
| File uploads | "upload", "images", "photos", "attachments" | |
| Auth / sync config | "Clerk", "Connect", "cloud sync", "login" | |
| Sync status display | "online/offline", "connection status" | |
| Full Neobrute design details | detailed design system, spacing, typography | |
随附的缓存文件包含详细的参考资料。当用户提示中出现以下信号时,请阅读这些资料:
| 需求 | 提示中的信号 | 阅读内容 |
|---|---|---|
| 文件上传 | "upload", "images", "photos", "attachments" | |
| 身份验证/同步配置 | "Clerk", "Connect", "cloud sync", "login" | |
| 同步状态显示 | "online/offline", "connection status" | |
| 完整Neobrute设计细节 | 详细设计系统、间距、排版 | |
Deployment Options
部署选项
After generating your app, deploy it:
- Cloudflare - Edge deployment with Workers. Use to deploy.
/vibes:cloudflare
生成应用后,进行部署:
- Cloudflare - 基于边缘Workers的部署。使用进行部署。
/vibes:cloudflare
What's Next?
下一步操作
After generating and assembling the app, present these options using AskUserQuestion:
Question: "Your app is live! Want to turn it into a product? The /sell skill adds multi-tenant SaaS with auth and billing. Or pick another direction:"
Header: "Next"
Options:
- Label: "Keep improving this app"
Description: "Continue iterating on what you've built. Add new features, refine the styling, or adjust functionality. Great when you have a clear vision and want to polish it further."
- Label: "Apply a design reference (/design-reference)"
Description: "Have a design.html or mockup file? This skill mechanically transforms your app to match it exactly - pixel-perfect fidelity with your Fireproof data binding preserved."
- Label: "Explore variations (/riff)"
Description: "Not sure if this is the best approach? Riff generates 3-10 completely different interpretations of your idea in parallel. You'll get ranked variations with business model analysis to help you pick the winner."
- Label: "Make it a SaaS (/sell)"
Description: "Ready to monetize? Sell transforms your app into a multi-tenant SaaS with Clerk authentication, subscription billing, and isolated databases per customer. Each user gets their own subdomain."
- Label: "Deploy to Cloudflare (/cloudflare)"
Description: "Go live on the edge. Deploy to Cloudflare Workers with a subdomain registry, KV storage, and global CDN. Fast, scalable, and always on."
- Label: "I'm done for now"
Description: "Wrap up this session. Your files are saved locally - come back anytime to continue."After user responds:
- "Keep improving" → Acknowledge and stay ready for iteration prompts. After each round of changes to app.jsx, re-run assembly and re-deploy.
- "Apply a design reference" → Auto-invoke /vibes:design-reference skill
- "Explore variations" → Auto-invoke /vibes:riff skill
- "Make it a SaaS" → Auto-invoke /vibes:sell skill
- "Deploy" → Auto-invoke /vibes:cloudflare skill
- "I'm done" → Confirm files saved, wish them well
Do NOT proceed to code generation until:
Connect setup is complete with valid Clerk credentials in .env (pre-flight check returns CONNECT_READY).
生成并组装应用后,使用AskUserQuestion呈现以下选项:
Question: "Your app is live! Want to turn it into a product? The /sell skill adds multi-tenant SaaS with auth and billing. Or pick another direction:"
Header: "Next"
Options:
- Label: "Keep improving this app"
Description: "Continue iterating on what you've built. Add new features, refine the styling, or adjust functionality. Great when you have a clear vision and want to polish it further."
- Label: "Apply a design reference (/design-reference)"
Description: "Have a design.html or mockup file? This skill mechanically transforms your app to match it exactly - pixel-perfect fidelity with your Fireproof data binding preserved."
- Label: "Explore variations (/riff)"
Description: "Not sure if this is the best approach? Riff generates 3-10 completely different interpretations of your idea in parallel. You'll get ranked variations with business model analysis to help you pick the winner."
- Label: "Make it a SaaS (/sell)"
Description: "Ready to monetize? Sell transforms your app into a multi-tenant SaaS with Clerk authentication, subscription billing, and isolated databases per customer. Each user gets their own subdomain."
- Label: "Deploy to Cloudflare (/cloudflare)"
Description: "Go live on the edge. Deploy to Cloudflare Workers with a subdomain registry, KV storage, and global CDN. Fast, scalable, and always on."
- Label: "I'm done for now"
Description: "Wrap up this session. Your files are saved locally - come back anytime to continue."用户回应后:
- "Keep improving" → 确认并准备好接收迭代提示。每次修改app.jsx后,重新运行组装并重新部署。
- "Apply a design reference" → 自动调用技能
/vibes:design-reference - "Explore variations" → 自动调用技能
/vibes:riff - "Make it a SaaS" → 自动调用技能
/vibes:sell - "Deploy" → 自动调用技能
/vibes:cloudflare - "I'm done" → 确认文件已保存,祝用户愉快
在以下情况完成前请勿进行代码生成:
Connect设置完成,.env文件中包含有效的Clerk凭据(飞行前检查返回CONNECT_READY)。