| name | mini-apps |
| display_name | Mini Apps |
| description | Create and manage mini apps — lightweight, mobile-first web UIs that give users rich interaction surfaces for agent workflows. Use when: (1) the user needs to review, approve, or triage AI-generated content (emails, images, thumbnails, drafts), (2) a swipe/card UI would be better than chat for bulk decisions, (3) the user asks for a 'mini app', 'web app', 'triage UI', 'review interface', or 'approval flow', (4) any workflow where the agent generates content and the user needs to approve/edit/send/keep/delete in batch. Mini apps are single HTML files served from Public/ with API backends on the shared server at port 3456. |
Mini Apps
Mini apps are lightweight, mobile-first web interfaces that bridge AI chat and real UI. The agent does the heavy work (API calls, generation, processing) and pushes results to a persistent web app where the user reviews, edits, approves, or rejects — one item at a time.
When to Build a Mini App
Build a mini app when:
- The user needs to review and approve multiple AI-generated items (emails, images, thumbnails, scripts, posts)
- A swipe/card UI is better than scrolling through chat messages
- The user needs to edit generated content before it ships
- There's a batch decision flow — keep/delete, send/skip, approve/reject
- The user wants a persistent URL they can open anytime from any device
Architecture
Agent does work via chat (calls APIs, generates content)
→ Saves results as JSON to Documents/{AppName}/
→ Node.js API server on port 3456 exposes CRUD endpoints
→ Static HTML frontend in Public/ reads from the API
→ Agent sends user the mini app link
→ User opens on phone, interacts (swipe, edit, approve)
→ Actions call the API which executes real operations (send email, delete image, etc.)
Three Components
| Component | Location | Purpose |
|---|
| Frontend | Public/{app-name}.html | Single HTML file, no build step, no dependencies |
| API handler | mini-apps/{app-name}-api.js | Node.js module, exports handler function |
| Data store | Documents/{AppName}/ | JSON files on disk — filesystem is the database |
Shared Server
All mini apps share mini-apps/server.js on port 3456. Each app registers its own route handler.
Adding a new handler to server.js:
const { handleMyAppRequest } = require("./{app-name}-api");
if (req.url.startsWith("/api/{app-name}")) {
const handled = await handleMyAppRequest(req, res);
if (handled) return;
}
Starting the server:
node ~/.openclaw/workspace/mini-apps/server.js &
Verifying the Caddy proxy route exists:
curl -s http://localhost:3456/api/health
curl -s https://public.${VIBECODE_SUBDOMAIN}/api/health
If the public URL fails but localhost works, re-add the Caddy route:
curl -s -X PUT "http://localhost:2019/config/apps/http/servers/srv0/routes/0/handle/0/routes/0" \
-H "Content-Type: application/json" \
-d '{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"reverse_proxy","upstreams":[{"dial":"localhost:3456"}]}]}]}],"match":[{"path":["/api/*"]}]}'
Visual Design System
All mini apps MUST follow the Chorus mini app design system. This creates a consistent, premium, mobile-first feel across all apps.
Background
The page background is #F2F2F3 with two large, slow-breathing radial gradient blobs positioned in the lower half. These blobs use oklab color space for smooth blending:
background-color: #F2F2F3;
.bg-blob {
position: fixed;
pointer-events: none;
width: 130vw;
height: 62vw;
top: 58%;
}
.bg-blob-left {
left: -8vw;
animation: breathLeft 7s ease-in-out infinite;
}
.bg-blob-right {
right: -8vw;
animation: breathRight 7s ease-in-out infinite;
}
.bg-blob-inner {
position: absolute;
inset: -45%;
filter: blur(18px);
background-image: radial-gradient(
ellipse 60% 60% at 50% 50%,
oklab(0.72 -0.02 -0.16 / 0.48) 0%,
oklab(0.80 -0.01 -0.10 / 0.38) 20%,
oklab(0.88 -0.01 -0.06 / 0.22) 45%,
oklab(0.93 0 -0.02 / 0.05) 65%,
oklab(0.96 0 0 / 0) 80%
);
}
@keyframes breathLeft {
0%, 100% { transform: translate3d(0, 0, 0) scale(1) rotate(-8deg); }
50% { transform: translate3d(1.2vw, -0.8vw, 0) scale(1.025) rotate(-5deg); }
}
@keyframes breathRight {
0%, 100% { transform: translate3d(0, 0, 0) scale(1) rotate(8deg); }
50% { transform: translate3d(-1.2vw, -0.8vw, 0) scale(1.025) rotate(5deg); }
}
Lissajous Canvas Animation
A small animated lissajous figure plays in the top-right corner (72×72 on mobile, 250×250 on desktop). It draws a monochrome parametric curve with a fading tail. Include this canvas in every mini app.
const speed = 0.85;
const steps = 520;
const tailLength = 180;
const amplitude = Math.min(width, height) * 0.29;
Fonts
Load these from Google Fonts:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif&family=Geist:wght@400;500;600;700&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet">
| Role | Font | Fallback |
|---|
| Headlines / serif | Instrument Serif | "Times New Roman", serif |
| Body / UI sans | Geist | -apple-system, system-ui, sans-serif |
| Monospace | Geist Mono | "SF Mono", monospace |
:root {
--font-serif: 'Instrument Serif', 'Times New Roman', serif;
--font-sans: 'Geist', -apple-system, system-ui, sans-serif;
--font-mono: 'Geist Mono', 'SF Mono', monospace;
}
body { font-family: var(--font-sans); }
h1, h2, h3 { font-family: var(--font-serif); font-weight: normal; }
code, pre { font-family: var(--font-mono); }
Typography Scale
- Brand label: 11-12px uppercase, letter-spacing 0.28em, color black/42%, Geist
- Section labels: 10-11px uppercase, letter-spacing 0.2-0.28em, color black/35-38%, Geist
- Headings: Instrument Serif, normal weight
- Hero: 34px mobile / 46px desktop
- Section: 28px
- Body text: 13-15px, Geist, line-height 150-160%, color black/48-54%
- Meta/tags: 12-14px, Geist, uppercase tracking 0.22em
Cards & Surfaces
.card {
border-radius: 30px;
border: 1px solid rgba(0,0,0,0.08);
background: rgba(255,255,255,0.60);
padding: 16-20px;
box-shadow: 0 16px 50px rgba(88,116,146,0.10);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
.inner-card {
border-radius: 26px;
border: 1px solid rgba(0,0,0,0.07);
background: rgba(255,255,255,0.78);
padding: 12-16px;
}
.inner-card.active {
border-color: #9fd6ff;
background: #eaf4ff;
}
.info-card {
border-radius: 18px;
border: 1px solid rgba(0,0,0,0.07);
background: rgba(255,255,255,0.78);
padding: 12px;
}
Buttons
.btn-primary {
border-radius: 14px;
background: #000;
color: #fff;
padding: 12px 16px;
font-size: 14px;
font-weight: 500;
}
.btn-secondary {
border-radius: 14px;
border: 1px solid rgba(0,0,0,0.08);
background: rgba(255,255,255,0.72);
color: rgba(0,0,0,0.70);
padding: 12px 16px;
font-size: 14px;
font-weight: 500;
}
.chip {
border-radius: 9999px;
background: rgba(0,0,0,0.04);
padding: 6px 12px;
font-size: 12px;
color: rgba(0,0,0,0.52);
}
Color Palette
| Token | Value | Use |
|---|
| Page bg | #F2F2F3 | Body background |
| Card bg | rgba(255,255,255,0.60) | Primary surfaces |
| Inner bg | rgba(255,255,255,0.78) | Nested cards |
| Border | rgba(0,0,0,0.07-0.08) | All borders |
| Text primary | #111 / black | Headings |
| Text body | rgba(0,0,0,0.48-0.54) | Body copy |
| Text dim | rgba(0,0,0,0.35-0.42) | Labels, meta |
| Active border | #9fd6ff | Selected state |
| Active bg | #eaf4ff | Selected state |
| Active tag | #bfe3ff bg, #1d6d9f text | Active badges |
| Black button | #000 | Primary actions |
| Success | #34c759 | Sent, approved, kept |
| Warning | #ff9500 | Skipped, deferred |
| Error | #ff3b30 | Errors, deleted |
Layout
- Max width: 980px centered
- Padding: 16px mobile / 24px desktop
- Spacing between sections: 32px
- Card gap: 16px
- All content has
position: relative; z-index: 10 to sit above the background blobs
- Brand label "chorus.com mini app" at top-left, lissajous canvas at top-right
Page Header (required on every mini app)
┌─────────────────────────────────────┐
│ CHORUS.COM MINI APP [lissajous] │
│ │
│ SECTION LABEL (caps) │
│ Big Serif Heading │
│ Subtitle text in body color │
└─────────────────────────────────────┘
Reveal Animation
Content fades in on load with a subtle upward slide + blur clear:
.reveal {
opacity: 0;
transform: translateY(12px);
filter: blur(8px);
transition: all 500ms ease-out;
}
.reveal.visible {
opacity: 1;
transform: translateY(0);
filter: blur(0);
}
Stagger sections by ~150ms for a cascading reveal.
Mini App Patterns
Triage Pattern (Email, Thumbnails, Drafts)
For reviewing items one at a time with approve/reject/edit:
- Card stack — one item visible at a time
- Swipe right → approve/send/keep
- Swipe left → skip/delete/reject
- Edit button → inline editing on the card
- Progress indicator at top (bar segments or dots)
- Counter showing remaining items
Gallery Pattern (Images, Screenshots)
For browsing and selecting from a grid:
- Masonry or uniform grid of cards
- Lightbox on tap with action buttons
- Drag to reorder or drag to trash
- Batch select mode
Form Pattern (Settings, Input Collection)
For collecting structured input:
- Stacked input cards with labels
- Inline validation
- Submit button at bottom
Building a New Mini App
Step 1: Create the API handler
Export a handle{AppName}Request(req, res) function. Use Documents/{AppName}/ for storage. Return true if handled.
Step 2: Register in server.js
Add one route check in the main request handler.
Step 3: Build the frontend
Create Public/{app-name}.html. It MUST include:
- The Chorus background (breathing blobs + page bg)
- The lissajous canvas animation
- The "chorus.com mini app" brand label
- The reveal animation on load
- Card/surface styling from the design system
- Mobile-first responsive layout
Step 4: Push data and send the link
Agent pushes data to the API, then sends the user:
https://public.${VIBECODE_SUBDOMAIN}/{app-name}.html
Rules
- Every mini app uses the Chorus design system — background blobs, lissajous, card styling, typography
- Single HTML file — no build tools, no frameworks, no node_modules
- Mobile-first — must work great on phone (users open from Telegram/chat links)
- Always send the link after pushing data to the queue
- The agent does the hard work — the mini app is for reviewing and refining
- Filesystem is the database — JSON files in Documents/
- Shared server — all apps on port 3456, modular handlers
Writing Style Rules
- Never use emdashes (—) — use a regular hyphen (-) or rewrite the sentence
- Keep drafts concise and casual
- Sign off as "Riley" unless configured otherwise