# Notch card presets — data schemas

Morphy ships a set of **beautiful, pre-built card renderers**. You send only the
**data**; Morphy owns the layout, the typography, the colors, the scrolling. This
is the fast, pretty path — use it whenever a preset fits. You never write CSS for
these, and the card always looks on-brand.

## How to send a preset

A card is a **registry action**. Put one `<mac_actions>` block in your `[Mac]` reply
(it's stripped from TTS, never spoken) with a `card` action whose `data` is the
preset's payload:

```
<mac_actions>
[ { "type": "card", "preset": "PRESET_NAME", "data": { ...preset data... } } ]
</mac_actions>
```

- `preset` is the name (lowercase) from the sections below.
- `data` is a **single JSON object** — the fields documented for that preset.
  **Each example below shows exactly that `data` object** (drop it in under `"data"`).
- Use valid JSON: double-quote keys/strings, escape newlines inside strings as `\n`
  (multi-line text is fine for `email`/`text` bodies — it renders with real breaks).
- Unknown fields are ignored; missing optional fields just don't render.
- An unknown `preset` or malformed JSON shows **no card** (and nothing leaks into
  your spoken reply) — a typo fails safe, not loud.
- You can include other actions (`point`, `spotlight`) in the **same array** — they
  run in order. See `SKILL.md`.

> **Legacy:** the older `<notch_card type="PRESET">{ …data… }</notch_card>` tag still
> works (same renderer), but the `card` action above is the canonical form — one
> envelope for every Mac capability.

The same voice rule always applies: **don't read the card's contents aloud** — the
voice is a short lead-in, the card carries the detail. See `SKILL.md`.

---

## Presets

> Each block below is the **`data` object** for that preset — wrap it as shown in
> "How to send a preset" above.

### `stat` — one big number
A single hero value with a label and caption. Time, countdown, %, rate, score.

| Field | Req | Notes |
|---|---|---|
| `label` | ✓ | small uppercase tag, e.g. `"TOKYO"` |
| `value` | ✓ | the hero string, e.g. `"8:14 PM"` |
| `caption` | – | muted line under it |

```
{ "label": "TOKYO", "value": "8:14 PM", "caption": "Thursday, May 28" }
```

### `info` — header + two key/value tiles
Two metrics side by side under a title.

| Field | Req | Notes |
|---|---|---|
| `title` | ✓ | bold header |
| `subtitle` | – | muted line under the title |
| `left_label`, `left_value` | ✓ | first tile |
| `right_label`, `right_value` | ✓ | second tile |

```
{ "title": "Connection", "subtitle": "VPN · Frankfurt",
  "left_label": "Latency", "left_value": "42 ms",
  "right_label": "Loss", "right_value": "0%" }
```

### `email` — a single email
Sender with an auto-generated initial avatar, subject, time, and a body the user
can **scroll** if it's long. Perfect for "read me that email."

| Field | Req | Notes |
|---|---|---|
| `from` | ✓ | sender name |
| `body` | ✓ | full text; use `\n` for paragraphs |
| `subject` | – | shown under the sender, truncated |
| `time` | – | top-right, e.g. `"2:14 PM"` |
| `initial` | – | avatar letter; **auto-derived from `from`** if omitted |

```
{ "from": "Alex Chen", "time": "2:14 PM", "subject": "Migration plan",
  "body": "Can we move the cutover to Tuesday?\n\nStaging looked clean last night." }
```

### `list` — ranked / labelled rows
A scrolling list. Use for unread mail, todos, news, search results, top-N.

| Field | Req | Notes |
|---|---|---|
| `title` | ✓ | header |
| `items` | ✓ | array of objects (see below) |
| `page`, `pages` | – | if both set and `pages > 1`, draws page dots; say "next" in voice and resend with the next `page` |

Each item: `{ "title": ✓, "meta"?: "secondary line", "value"?: "right-aligned tag", "index"?: "1" }`

```
{ "title": "Unread mail", "page": 1, "pages": 3,
  "items": [
    { "index": "1", "title": "Alex Chen", "meta": "Migration plan", "value": "2m" },
    { "index": "2", "title": "Figma", "meta": "3 new comments", "value": "1h" },
    { "index": "3", "title": "GitHub", "meta": "CI passed on main", "value": "3h" }
  ] }
```

### `calendar` — day + events
Day hero with a scrolling event list and a built-in empty state.

| Field | Req | Notes |
|---|---|---|
| `weekday` | ✓ | e.g. `"Thu"` |
| `date` | ✓ | e.g. `"May 28"` |
| `count` | – | top-right, e.g. `"4 events"` |
| `events` | ✓ | array; if empty, renders "Nothing scheduled." |

Each event: `{ "time": "10:00", "title": "Stand-up", "duration"?: "30m" }`

```
{ "weekday": "Thu", "date": "May 28", "count": "3 events",
  "events": [
    { "time": "10:00", "title": "Stand-up", "duration": "30m" },
    { "time": "14:00", "title": "Design review", "duration": "1h" },
    { "time": "16:30", "title": "1:1 with Sam", "duration": "30m" }
  ] }
```

### `weather` — current conditions
Big temp, an **auto-selected line-art glyph** (from `condition`), high/low, and an
optional third tile.

| Field | Req | Notes |
|---|---|---|
| `location` | ✓ | uppercase label |
| `temp` | ✓ | number/string; a `°` is appended by the template |
| `condition` | ✓ | text; also picks the glyph (keywords: clear/sun, partly, cloud, rain, snow, storm/thunder, fog/mist) |
| `high`, `low` | ✓ | `°` appended |
| `extra_label`, `extra_value` | – | optional third tile (e.g. Wind) |

```
{ "location": "San Francisco", "temp": "17", "condition": "Partly cloudy",
  "high": "19", "low": "12", "extra_label": "Wind", "extra_value": "12mph" }
```

### `ticker` — finance quote
Symbol, name, price, a **color-coded change** (green ↑ / red ↓, inferred from a
leading `-`), and an **auto-generated sparkline** from a number array.

| Field | Req | Notes |
|---|---|---|
| `symbol` | ✓ | e.g. `"TSLA"` |
| `price` | ✓ | e.g. `"$248.50"` |
| `change` | ✓ | e.g. `"+2.4%"` or `"-1.1%"` (sign drives the color) |
| `name` | – | company name, truncated |
| `points` | – | array of numbers → the sparkline chart (needs ≥ 2 values) |

> **To get the little chart, you MUST include `points`.** Without it the card
> renders price + change but **no chart**. Fetch ~8–30 recent values (intraday
> prices, or the last N daily closes) and pass them as a plain number array —
> exact scale doesn't matter, the sparkline auto-normalizes. If you genuinely
> can't get a series, omit `points` and the card still looks fine without it.

```
{ "symbol": "TSLA", "name": "Tesla Inc", "price": "$248.50", "change": "+2.4%",
  "points": [241, 239, 244, 242, 247, 245, 250, 248.5] }
```

### `text` — title + long body
A title (optional) and a body the user can **scroll** when it overflows. Use for
summaries, explanations, "read me this", a quote, a paragraph answer.

| Field | Req | Notes |
|---|---|---|
| `body` | ✓ | the text; `\n` for paragraphs |
| `title` | – | header (with a divider) if present |
| `tag` | – | small label top-right (e.g. `"#482"`) |

```
{ "title": "PR summary", "tag": "#482",
  "body": "Refactors the notch pipeline into a preset renderer.\n\nAdds nine presets the user can scroll." }
```

### `comparison` — two columns
Side-by-side options, each with a value and a note.

| Field | Req | Notes |
|---|---|---|
| `title` | ✓ | header |
| `left_title`, `left_value` | ✓ | first column |
| `right_title`, `right_value` | ✓ | second column |
| `left_note`, `right_note` | – | muted line under each value |

```
{ "title": "Postgres vs SQLite",
  "left_title": "Postgres", "left_value": "Concurrent ✓", "left_note": "Heavier setup",
  "right_title": "SQLite", "right_value": "Single-writer", "right_note": "Zero setup" }
```

---

## When no preset fits → go custom

If the answer needs a shape these don't cover, hand-write the whole card with
`<notch_html>…</notch_html>` instead (see `SKILL.md` → custom cards). Use the raw
tag, not a JSON string — you write real markup with no escaping. If you build a
custom card you'll reuse, save it to `frequentSnippets/` so next time is a
copy-fill-send. When a custom card you've made gets requested a lot, tell Bruno —
it's a candidate for a real shipped preset.

## Notes

- **Scrollable.** Long `email` / `list` / `calendar` / `text` content is scrollable —
  the user scrolls it with the trackpad (a thin scrollbar appears). For very long
  lists you can still paginate via voice ("say next").
- **Keep it to one idea.** The canvas is tiny (383 × 147 pt). A `list` realistically
  shows ~4 rows before it has to scroll — lead with the most important ones.
