rabbit-r1-creations

AGENTS.md

Instructions for AI agents (and humans) working in this repo. Read this before creating or modifying anything. It encodes the hard constraints of the Rabbit R1 platform and the conventions this repo follows. Following it is how you avoid breaking things.

This file is also included by CLAUDE.md via @AGENTS.md.

TL;DR rules (read these first)

  1. A Creation lives in creations/<name>/ as three files: index.html (markup + references), styles.css, app.js (split for legibility — never inline <style>/<script>). Sized exactly 240×282 px.
  2. Every Creation must work without the SDK (graceful degradation) so it can be tested in a desktop browser.
  3. Use only the Creations SDK bridges listed below + standard web APIs. No WebSockets. No channel exists for the motorized camera — don’t try to spin it.
  4. Always load Power Grotesk via <link rel="stylesheet" href="../../lib/fonts/fonts.css"> and set font-family: "Power Grotesk", -apple-system, sans-serif. Never CDN fonts, never absolute URLs.
  5. All creationStorage values must be base64-encoded (btoa/atob).
  6. Hardware buttons are the primary input (no reliable touchscreen). Map them deliberately and debounce the scroll wheel (one notch = one action).
  7. Optimize for limited hardware: CSS transitions over JS animation, minimize DOM writes, use transform/opacity.
  8. Don’t add comments that restate code. Don’t add files unless needed. Don’t commit unless asked.
  9. All UI copy is lowercase — buttons, labels, topbar tags, toasts, hints, headers, empty states. The R1’s own interface is lowercase; match it. No title case, no ALL CAPS in visible text.
  10. Dark mode only. Every Creation uses the R1 brand palette (#FE5000 accent on #0a0a0a/#000) — see Brand colors. No light theme, no toggle.
  11. Every Creation loads the shared base: <link href="../../lib/fonts/fonts.css"> → <link href="../../lib/shared/reset.css"> → its own styles.css, in that order. reset.css provides the box-model reset, 240×282 dark viewport, font stack, and palette tokens — don’t duplicate them.

Repo layout

creations/<name>/              one Creation per folder, split into three files:
├── index.html                   markup + <link>/<script> references
├── styles.css                   all CSS
└── app.js                       all JS
lib/fonts/                      shared Power Grotesk WOFF2 + fonts.css (3 weights)
lib/shared/                     shared reset.css (CSS base + tokens) + core.js / store.js (window.R1 helpers)
qr-generator/                   self-host QR generator tool — NOT a Creation, leave at root
README.md                       human-facing overview
AGENTS.md                       this file (source of truth)
CLAUDE.md                       contains `@AGENTS.md`
CNAME                           optional GitHub Pages custom domain

File structure of a Creation

Every Creation is three files in one folder — keep markup, styles, and logic separate so it’s legible and reviewable:

creations/<name>/
├── index.html    # structure only: <link>/<script> references + DOM. No inline <style>/<script>.
├── styles.css    # all CSS
└── app.js        # all JS

Creating a new Creation

  1. mkdir creations/<name> and create three files: index.html, styles.css, app.js.
  2. Start from the boilerplate below (correct viewport, font link, 240×282 sizing, SDK-detection, on-screen dev controls, and the 3-file split).
  3. Implement features using only the SDK bridges + standard web APIs.
  4. Wire hardware input (see Hardware UX conventions).
  5. Test locally (see Testing).
  6. Run the Verification checklist before considering it done.

Boilerplate

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=240, initial-scale=1.0, user-scalable=no">
  <title><Name></title>
  <link rel="stylesheet" href="../../lib/fonts/fonts.css">
  <link rel="stylesheet" href="../../lib/shared/reset.css">
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <div id="app"><!-- your UI --></div>

  <!-- on-screen controls for desktop testing; auto-hidden on device -->
  <div class="devbar" id="devbar"></div>

  <script src="../../lib/shared/core.js"></script>
  <script src="../../lib/shared/store.js"></script> <!-- only if you persist data -->
  <script src="app.js"></script>
</body>
</html>

styles.css (component styles only — the reset, viewport sizing, dark base, font stack, palette tokens, and .devbar show/hide all come from reset.css):

#app {
  width: 240px; height: 282px;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  text-align: center; gap: 10px;
}
/* palette tokens (--accent, --bg, --surface, --text, …) are defined in reset.css;
   override an individual value here only if this Creation needs to deviate. */

app.js:

(function () {
  "use strict";

  // hardware → handler wiring (also called by the dev controls)
  function onWheel(dir) {}      // dir = +1 (up) or -1 (down)
  function onPTT() {}           // side button click = primary action
  function onLongPress() {}     // long press = mode switch / destructive

  // lib/shared/core.js exposes window.R1 — bindControls() wires the 4 hardware
  // events AND, when there's no SDK, the desktop dev harness (devbar + arrow/space/g keys).
  R1.bindControls({ wheel: onWheel, ptt: onPTT, longPress: onLongPress, devbar: "devbar" });

  // other R1 helpers: R1.hasSDK, R1.$(id), R1.toast(el, msg, ms). load store.js for R1.store.

  window.onPluginMessage = function (data) {
    // data.data is a JSON string when useLLM was true
  };
})();

Creations SDK reference

The complete documented API. pluginId is injected/overridden by the system — never set it yourself.

PluginMessageHandler — send to server / LLM

PluginMessageHandler.postMessage(JSON.stringify({
  message: "text",
  useLLM: true,            // optional: get an LLM reply (arrives via window.onPluginMessage)
  wantsR1Response: true,   // optional (default false): speak the reply through the R1 speaker
  wantsJournalEntry: true  // optional (default false): log to the journal
}));

closeWebView — quit the Creation

closeWebView.postMessage("");

TouchEventHandler — synthesize touch

TouchEventHandler.postMessage(JSON.stringify({ type: "tap"|"down"|"up"|"move"|"cancel", x: 100, y: 200 }));

window.creationStorage — persistent storage (per-plugin isolated)

All values must be base64. Returns null if missing.

await window.creationStorage.plain.setItem('k', btoa(val));      // unencrypted
const v = atob(await window.creationStorage.plain.getItem('k')); // throws if null → guard it
await window.creationStorage.plain.removeItem('k');
await window.creationStorage.plain.clear();
// .secure.* has the same API (hardware-encrypted, Android M+)

window.creationSensors.accelerometer

const ok = await window.creationSensors.accelerometer.isAvailable();
window.creationSensors.accelerometer.start((d) => {
  // d = { x, y, z } normalized -1..1
}, { frequency: 60 });
window.creationSensors.accelerometer.stop();

window.onPluginMessage — receive replies

window.onPluginMessage = function (data) {
  // data.message (string), data.pluginId, data.data (JSON string when useLLM)
  if (data.data) { const parsed = JSON.parse(data.data); /* use it */ }
};

Hardware events (window.addEventListener)

scrollUp, scrollDown, sideClick, longPressStart, longPressEnd.

A double side-button press fires two sideClick events ~50 ms apart.

Standard web APIs (camera, mic, speaker)

Available via getUserMedia etc. in the WebView. The motorized rotating camera has no SDK control — to change framing, switch the active videoinput device / facingMode.

Hardware UX conventions

The R1’s reliable inputs are the scroll wheel and the side button (PTT). Design for those, not touch.

Brand colors

Every Creation ships dark mode only — no light theme, no toggle. The R1’s small, dim-friendly screen reads best on a dark field, and one shared palette keeps creations recognizably on-brand.

These live in lib/shared/reset.css as CSS custom properties (so every Creation shares them). Reference var(--…) everywhere; override an individual value in your styles.css only if a Creation needs to deviate:

:root {
  --accent: #FE5000;      /* R1 orange — primary action, selection, brand */
  --bg: #0a0a0a;          /* app background */
  --bg-media: #000;       /* camera / image surfaces */
  --surface: #141414;     /* cards, bars, thumbs */
  --text: #ffffff;
  --text-muted: #b8b8b8;
  --text-dim: #6a6a6a;
  --border: #161616;
}

UI copy is always lowercase

The R1’s own interface is lowercase end to end — match it. All user-facing copy is lowercase: buttons, labels, topbar tags, toasts, hints, section headers, and empty-state text. No title case, no ALL CAPS.

Typography

Performance (the hardware is limited)

Testing

A desktop browser has no SDK bridges and (in most dev setups) cannot grant camera/mic permission (NotAllowedError). Design for this.

  1. python3 -m http.server 8000 from the repo root.
  2. Open the Creation at http://localhost:8000/creations/<name>/ in a browser resized to 240×282.
  3. Confirm the SDK-absent path works: on-screen dev controls + keyboard (↑/↓ = wheel, Space/Enter = PTT, g = long press) drive the same handlers as the hardware events.
  4. Verify camera-dependent features fail gracefully (clear message), since the preview denies getUserMedia.
  5. Re-run the Verification checklist.

For storage testing without the device, fall back to localStorage when window.creationStorage is absent (see creations/camera/app.js for the pattern).

Verification checklist (run before “done”)

Common mistakes (don’t do these)

License / assets