Fit guide and examples

The jobs that deserve a riff.

runcurve is for a small standalone tool that needs an anonymous URL or embed, isolated backend code, a secret, state, or a file — and still is not worth a repository, framework, build, or release process.

01 / Frontend-only calculator

A quick total calculator

Prompt to your assistant

“Build a public tip calculator. Let a visitor enter a bill and tip percentage, then show the total and per-person amount. Keep it frontend-only.”

Runtime primitives

index.htmlfrontend.jsstyle.cssNo backend

Tip calculator

Interactive frontend preview

No backend

Total

$101.95

$15.55 tip · $50.98 per person

02 / Stateful form

A public guestbook with avatars

Prompt to your assistant

“Build a public guestbook where anyone can sign with a name, message, and optional avatar. Keep entries as documents and avatar files private to this riff.”

Runtime primitives

backend/routes/api/entries.jsctx.dbctx.blobctx.session
Inspect the shipped guestbook backend route
// GET /api/entries  -> recent guestbook entries (documents via ctx.db)
// POST /api/entries -> add one, with an optional avatar file (object via ctx.blob)
//
// Documents live in this riff's own store; the avatar is keyed by the entry id and
// served back through /api/avatar/:id (see backend/routes/api/avatar/[id].js).

export async function GET(ctx) {
  const { docs } = await ctx.db.query("entries", {
    order: { field: "createdAt", dir: "desc" },
    limit: 50,
  });
  return ctx.json({
    entries: docs.map((d) => ({
      id: d.id,
      name: d.data.name,
      message: d.data.message,
      avatar: d.data.avatarKey ? `/api/avatar/${d.id}` : null,
      at: d.createdAt,
    })),
  });
}

export async function POST(ctx) {
  const form = await ctx.req.raw.formData();
  const name = String(form.get("name") ?? "").trim().slice(0, 80);
  const message = String(form.get("message") ?? "").trim().slice(0, 500);
  if (!name || !message) return ctx.json({ error: "name and message are required" }, 400);

  // Create the entry first so the avatar can be keyed by its id.
  const entry = await ctx.db.create(
    "entries",
    { name, message, avatarKey: null },
    { userId: ctx.session.id },
  );

  const file = form.get("avatar");
  if (file && typeof file === "object" && "arrayBuffer" in file) {
    const buf = await file.arrayBuffer();
    if (buf.byteLength > 0 && buf.byteLength <= 2_000_000) {
      const key = `avatars/${entry.id}`;
      await ctx.blob.put(key, buf, {
        contentType: file.type || "application/octet-stream",
        userId: ctx.session.id,
      });
      await ctx.db.update("entries", entry.id, { avatarKey: key });
    }
  }

  return ctx.json({ ok: true, id: entry.id }, 201);
}

Guestbook

Stateful form result

2 entries
M

Mina

This made our team check-in much simpler.

R

Rafael

Added my note from the phone.

Name, message, optional avatar — then sign.

03 / Embeddable shared poll

Where should we eat?

Prompt to your assistant

“Build a public poll so the team can vote on where to eat, show the running tally, and allow one vote per browser. Return an iframe embed snippet too.”

Runtime primitives

Public visibility<iframe>ctx.storectx.session

This is a live public riff in a safe iframe. Vote and watch the shared tally change.

04 / API-backed dashboard

A small forecast board

Prompt to your assistant

“Build a small dashboard that gets Cedar Rapids' forecast from Open-Meteo and shows temperature, rain chance, and wind. Allow backend requests only to api.open-meteo.com.”

Runtime primitives

backend/routes/api/forecast.jsfetchnetwork_mode: allowlistapi.open-meteo.com

Cedar Rapids forecast

Result preview from an allowed API route

HTTPS allowlist

Now

72°

Clear

Rain

20%

Today

Wind

8

mph

Make the small thing useful.

Start with the tested Claude Code connection, then give your assistant one of these prompts.

Connect Claude Code