← GlanceCONTENT HISTORY

Update to Glance

Snapshot Sep 30, 2026 · 23:08 UTC · version 1.0.0

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "name": "glance-design",
  "description": "Design native iOS Home Screen widgets in Glance layout language. Apple-like, glanceable. Use registry types only — chart with variant, never bar_chart or webview.",
  "included_files": [],
  "skill_md_contents": "---\nname: glance-design\ndescription: Design native iOS Home Screen widgets in Glance layout language. Apple-like, glanceable. Use registry types only — chart with variant, never bar_chart or webview.\n---\n\n# Glance Widget Design Skill\n\nYou design **iOS home screen widgets** that feel native, calm, and glanceable — like Apple built them. This skill covers **visual design and composition only**. Workflow, tools, integrations, and sizing math live in the main agent skill.\n\n**Diversity over templates:** Every widget should fit its data and the user's intent. These are principles and patterns — not a single layout to copy. Vary structure, density, and emphasis. Never force one recipe when another fits better.\n\n---\n\n## Design north star\n\nWidgets should feel like **Apple system UI**: clear hierarchy, generous whitespace, restrained color, SF typography. A user glancing at their home screen should read the widget in under two seconds.\n\n| Trait | What it means in practice |\n|---|---|\n| **Glanceable** | One clear takeaway — a number, status, or headline |\n| **Native** | System fonts, semantic colors, no \"web app\" density |\n| **Calm** | Mostly neutral surfaces; color carries meaning, not decoration |\n| **Intentional** | Every element earns its space — if it doesn't help, remove it |\n\n---\n\n## Color — Apple-native default, user-guided override\n\n### Default (no user color preference)\n\nThink **Settings / Stocks / Weather** — not marketing landing pages.\n\n| Role | Guidance |\n|---|---|\n| **Background** | Omit `background` on root for system adaptive fill, or `#FFFFFF` / `#F2F2F7` (light) — never loud gradients unless the user asks |\n| **Primary text** | `#000000` or omit `color` (system primary) |\n| **Secondary / labels** | `#8E8E93` (iOS secondary label) — captions, timestamps, units |\n| **Tertiary / hints** | `#AEAEB2` — de-emphasized metadata only |\n| **Accent** | **One** semantic accent per widget — e.g. `#007AFF` (system blue) for links/active, `#34C759` success, `#FF9500` warning, `#FF3B30` alert |\n| **Progress / fills** | Muted track `#E5E5EA`, fill = single accent |\n\n**Rules:**\n- Never use more than **one accent hue** unless the user requests a colorful or branded look.\n- Never fill large areas with saturated color — accents belong on small elements (badge, progress fill, status dot, one headline word).\n- Prefer **opacity and weight** over more colors to create hierarchy.\n\n### iOS color format (critical on device)\n\nThe iOS renderer only parses **6-digit hex** (`#RRGGBB`). Values like `#FFFFFFCC` or `#FFFFFF40` are **ignored** and fall back to system `.primary` (often black on light widgets).\n\n- Use solid 6-digit hex for every `color`, `background`, `backgroundColor`, `trackColor`, `fillColor`.\n- For muted/secondary text on dark or gradient backgrounds, pick a lighter solid tint (e.g. `#FFE8F5` on purple gradients) — not alpha suffixes.\n- On colorful widgets, desaturated label colors still beat 8-char hex.\n\n### When the user gives color direction\n\n**Follow the user.** If they say \"dark purple theme\", \"brand pink #E91E8C\", or \"match my logo colors\" — apply their palette to backgrounds, accents, and text while keeping hierarchy and readability. Still use muted variants for labels (`#8E8E93` or a desaturated version of their accent).\n\nIf they share no preference, use the Apple-native defaults above.\n\n---\n\n## Typography\n\nUse **SF Pro** (`sf-pro` or omit `fontFamily`). Reserve `sf-rounded` for playful/countdown widgets; `sf-mono` for code/GitHub hashes only.\n\n| Role | fontSize | fontWeight | color |\n|---|---|---|---|\n| **Hero value** | 24–32 | `bold` or `heavy` | primary |\n| **Section title** | 15–17 | `semibold` | primary |\n| **Body / detail** | 13–15 | `regular` or `medium` | primary or secondary |\n| **Caption / label** | 10–12 | `regular` or `medium` | `#8E8E93` |\n| **Unit / suffix** | 11–13 | `medium` | secondary — on `stat` via `suffix` |\n\n**Limits:** At most **3 distinct font sizes** per widget. At most **2 weights** in active use (e.g. semibold + regular).\n\nAlways set `maxLines: 1` on titles and hero lines in `small` and `medium` widgets. Use `maxLines: 2` sparingly on `large` only when content demands it.\n\n---\n\n## Spacing rhythm\n\nPick one rhythm and stick to it for the whole tree:\n\n| Token | Use |\n|---|---|\n| **8 pt** | Tight gaps inside a row (icon + label) |\n| **12 pt** | Root `padding`, default `spacing` within sections |\n| **16 pt** | Between major sections on `large` widgets |\n\nRoot container: **`padding: 12`** minimum (16 for airy large layouts). Never mix 6 / 10 / 14 randomly.\n\nUse `spacer` with `flex: 1` to push footer elements (badge, timestamp) to the bottom on `large` — don't stack everything at the top.\n\n---\n\n## Visual hierarchy — the one-hero rule\n\nEvery widget needs **one focal point**:\n\n| Size | Typical hero |\n|---|---|\n| `small` | One `stat` value, one large number `text`, or one `avatar` + short label |\n| `medium` | Hero left or top + supporting detail opposite/below |\n| `large` | Hero row + 2–4 supporting metrics or a content block |\n\nEverything else is **supporting** — smaller, lighter color, fewer lines.\n\n**Do not** give equal visual weight to five text lines. If everything is bold and 16 pt, nothing is important.\n\n---\n\n## Static vs dynamic bindings\n\nOnly `{{variable_name}}` becomes a **content field** (upload form, automation ingest, `bindings_required`).\n\n| Use case | Template syntax | In derived schema? |\n|---|---|---|\n| Live data (username, KPI value, event title) | `\"binding\": \"{{username}}\"` | Yes |\n| Fixed section label (\"Profile views today\") | `\"binding\": \"Profile views today\"` | No |\n| Fixed metric name under a ring (\"Revenue\") | `\"binding\": \"Revenue\"` | No |\n| Decorative brand icon (design constant) | `\"binding\": \"https://…/icon.png\"` on `image` | No |\n| SF Symbol | `\"binding\": \"bag.fill\"` on `icon` | No |\n| Progress-dependent caption | `\"binding\": \"{{overall_goal_caption}}\"` | Yes |\n\n**Rule:** If the copy or asset never changes per refresh, use a **plain literal** — not `{{}}`. This keeps the upload flow clean and matches how the goldens are authored.\n\n**Static images on device:** use **HTTPS PNG/JPEG** URLs. Local paths like `/brand-icons/…` only work in React preview.\n\n---\n\n## Golden reference library\n\nSix device-approved examples live in `packages/sdk/goldens/`. They are the **quality bar** — adapt their structure to the user's data; never paste one verbatim with wrong bindings.\n\n**Before designing:** call `list_golden_examples` (filter by `widget_size` or fetch one with `example_id`).\n\n| ID | Size | Pattern | When to adapt |\n|---|---|---|---|\n| **s1-glance-kpi** | small | Gradient + username + 2 stacked KPI stats + static brand icon | Social/account KPIs, branded small metrics |\n| **s2-glance-media** | small | White card: static section label → title → centered thumbnail → caption | Latest post/video/article thumbnails |\n| **m1-split-strip** | medium | Left: static title stack · Right: hero `stat` | Single headline metric strips (monitoring, uptime) |\n| **m2-multi-source** | medium | Left motion panel + right spend card + calendar event rows | Multi-integration dashboards (device + cloud) |\n| **l1-app-performance** | large | Dark 2×2 KPI rings (`z-stack` + `circular_progress`) + quarterly `progress_bar` | Custom business metrics with goal progress |\n| **l2-outfit-of-the-day** | large | Gradient + hero image + shoppable item rows (`href` + `icon`) | Creator commerce, product lists, playful layouts |\n\nLanguage v2 references (same folder, `v2-` prefix). Load with `example_id`. Free users may use agenda. Pro+ goldens need `get_plan` family flags first.\n\n| ID | Size | Plan | When to adapt |\n|---|---|---|---|\n| **v2-vocab-flip** | small | Pro+ views | In-place reveal — whole-card `toggle_flag` + `visibleFlag`, no `views` |\n| **v2-today-agenda** | small | Free | Next few events as a list — `calendar` `agenda` + `event[]` |\n| **v2-revenue-line** | medium | Pro+ charts | Today's total + cumulative hours — `chart` `line`, `showAxes: true` |\n| **v2-weekday-bar** | medium | Pro+ charts | 7-day counts — `chart` `bar`, `showAxes`, `showAverage`, `colorScale` |\n| **v2-share-pie** | small | Pro+ charts | Parts of a whole (≤ 8) — `chart` `pie` + `innerRadius` |\n| **v2-activity-grid** | medium | Pro+ grid | Heat over days/weeks — `grid` `activity` + `colorScale` |\n| **v2-standings-table** | medium | Pro+ table | Compact labeled numbers — `table` 3×3-ish, never 5 columns |\n| **v2-mission-control** | large | Pro+ views + charts | Four-page hub — home summary + bottom nav `Home \\| Dashboard \\| Mail \\| Calendar` |\n\n### Golden techniques worth copying\n\n- **S1:** `gradient` root; static caption literals above dynamic `stat` values; trailing brand `image` with static URL\n- **S2:** explicit `spacer` gaps between title/image/caption; `maxLines` on all text; white `#FFFFFF` root background\n- **M1:** horizontal split with `flex: 1` text column + trailing hero `stat` (`valueFontSize: 40`)\n- **M2:** nested gray cards (`#F2F2F7`); color accent bars beside calendar rows; formatted strings in `text` not `stat`\n- **L1:** ring container **82×82** with `circular_progress` **size 70, lineWidth 5** (prevents stroke clip); value text centered in `z-stack`; `overall_goal_caption` stays dynamic\n- **L2:** `gradient` with `spacing` as gap; `fillContainer` hero image; `href` on shop tap targets; `sf-rounded` for playful headers\n- **v2-vocab-flip:** whole-card `toggle_flag` + `visibleFlag`; prompt stays; answer appears on the same screen; no `views`; reload clears the flag\n- **v2-revenue-line:** horizontal split, no divider; copy + insight + `relative_time` on the left; full-height `chart` `line` `showAxes: true` on the right\n- **v2-weekday-bar:** same split on light `#F2F2F7`; copy + 7-day average on the left; full-height `chart` `bar` with axes, `showAverage`, and `colorScale` bands (red < 10, yellow < 20, green 20+)\n- **v2-mission-control:** Notion monochrome (`#FFFFFF` / `#37352F` / `#9B9A97`). Home is greeting + KPIs + unread mail + agenda. Duplicate a 4-tab bottom nav on `tree` and every named view; selected tab is ink, the rest muted. Named views: `dashboard` (today's revenue — full-width cumulative line, hourly bars beside App / Webapp / B2B mix), `mail` (3 summaries), `calendar` (`columns` `days: 3`). Discover stays on home — home must read as a full widget.\n\n---\n\n## Size patterns (adapt — do not copy blindly)\n\nPrefer picking a **golden reference** above, then adapt. These ASCII sketches are fallbacks when no golden fits:\n\n### Small (170×170) — \"one glance\"\n\nBest for: single KPI, countdown, status, one image thumbnail.\n\n```\nvertical container, padding 12, spacing 8\n  → caption text (12pt, secondary, maxLines 1)     // label\n  → stat OR large text (hero, 26–30pt)             // THE number/word\n  → optional: badge OR relative_time (11pt)        // context\n```\n\nAvoid: horizontal rows of 3+ items, multiple stats, long paragraphs.\n\n### Medium (364×170) — \"headline strip\"\n\nBest for: integration dashboard, social snapshot, calendar next event.\n\n**Pattern A — split row:**\n```\nvertical, padding 12\n  → horizontal row, spacing 12, alignment center\n      → left column (vertical): title + subtitle\n      → right: stat OR circular_progress OR avatar/image\n```\n\n**Pattern B — title + detail row:**\n```\nvertical, padding 12, spacing 8\n  → title text (15pt semibold, maxLines 1)\n  → horizontal row: 2–3 compact stats OR image + text pair\n```\n\nAvoid: more than one horizontal band; cramming 4 columns.\n\n### Large (364×382) — \"dashboard card\"\n\nBest for: multi-metric boards, feed digests, rich integration views.\n\n```\nvertical, padding 12–16, spacing 12\n  → header row (horizontal): title + optional badge\n  → main content: 2–3 stats in a row OR 2×2 grid of images/metrics\n  → spacer (flex 1)                                    // optional\n  → footer: progress_bar OR relative_time OR caption   // optional\n```\n\nUse `divider` between header and body only when it clarifies — not by default.\n\n---\n\n## Component combinations that look polished\n\n| Combo | When to use |\n|---|---|\n| `stat` + caption `text` above | KPI widgets (followers, revenue, streak) |\n| `avatar` + vertical `text` stack in horizontal row | Social / account identity |\n| `image` (cornerRadius 8) + `text` beside | Latest post, thumbnail + caption |\n| `badge` (subtle bg `#E5E5EA`, text primary) | Status chips — \"Live\", \"3 new\" |\n| `progress_bar` (height 6–8, muted track) | Goal completion — one per widget |\n| `z-stack` + full-bleed `image` + `text` with light color | Event spotlight — use sparingly |\n| `status_dot` + `text` | Health / server / pipeline status |\n| `relative_time` or `date` in caption style | \"Updated 2m ago\" footer |\n| `shape` circle + mapped `fillColor` | One number that should read as a state (score, health) |\n| `calendar` agenda + caption | Today's events — do not rebuild event rows from `text` |\n| `chart` line, `showAxes: false`, height 64–80 | Sparkline under a title on medium |\n| `chart` pie + `innerRadius` 0.55 + caption | Donut KPI on small |\n| `chart` bar, `showAxes: true`, `showAverage: true` | Category counts vs a typical day — dashed mean of `y` |\n| `grid` 1×7 week strip | Habit / steps this week |\n| `table` 3 cols × ≤ 4 rows | Standings, ranks, tiny scoreboard |\n| Bottom nav `text` + `set_view` on every view | Mission Control — selected tab ink, others muted. Duplicate the bar; do not hide it. |\n| Whole-card `toggle_flag` + `visibleFlag` on small | Vocab card — prompt stays, answer appears on the same screen |\n\n**`stat` tips:** Use `valueFontSize` 26–32 for hero stats; `labelFontSize` 11; `suffix` for units (`K`, `%`, `°`). Prefer `ig_followers_fmt` style formatted strings in `text` when the integration provides them.\n\n---\n\n## Layout polish\n\n- **Alignment:** `leading` for LTR text blocks; `center` for single hero in small; `trailing` for meta (time, badge) in header rows — but see reload button rule below.\n- **Reload button:** Glance overlays a reload control in the **top-right** (~40×40 pt). Never put hero values or must-read text there; prefer leading clusters for top-band KPIs (label + value side-by-side).\n- **Images:** Always set explicit `width` / `height`; `cornerRadius: 8–12` for photos; `contentMode: \"fill\"` for thumbnails.\n- **Nested cards:** Inner `container` with `background: \"#F2F2F7\"`, `cornerRadius: 10`, `padding: 10` — sparingly, one level deep max.\n- **Full bleed:** Only `gradient` root for dramatic widgets — and only when user wants flair or data suits it (event poster, media highlight).\n\n---\n\n## Anti-patterns — never ship these\n\n| Anti-pattern | Why it fails |\n|---|---|\n| 5+ `text` nodes all 14 pt regular | No hierarchy — wallpaper of words |\n| Rainbow hex on every element | Feels non-native, noisy |\n| Horizontal row of 4 images on `small` | Guaranteed clip / crush |\n| `stat` for currency (`$1,234`) or `%` strings | Type errors + ugly fallback |\n| Root `padding: 0` or missing | Corner clip on home screen |\n| Hero stat / spend in top-trailing corner | Hidden under reload button overlay |\n| `gap`, `borderRadius`, `textAlign` style keys | Silently ignored — use correct keys from main skill |\n| `{{}}` on fixed labels or decorative icons | Forces upload fields; use static literals instead |\n| 8-char hex colors (`#RRGGBBAA`) | iOS ignores them — text falls back to black primary |\n| `bar_chart` / `pie_chart` / `webview` / JS ternaries | Invalid. Use `type: \"chart\"` + `variant`, or a `stops` / `when` map |\n| Chart or views on Free without checking `get_plan` | 403 at save. Offer agenda / v1 stand-in, or upgrade |\n| Five equal tap targets on a small hub | No hero. One primary view, others supporting |\n| Calendar rebuilt as `event_1_title` rows when they asked for a calendar | Use `calendar` + `event[]`. Indexed fields stay valid for v1 lists |\n| Grid `rows`/`columns` as bindings | Those are **ints on the node**. `binding` is the `number[]` |\n| Table `columns: 3` (a number) | Table `columns` must be `\"{{headers}}\"` (`string[]`) |\n| Month calendar with event titles | Month is **dots only**. Use `agenda` or `columns` for titles |\n| Discover / marketing tile that relies on `set_view` | Tiles stay on home. Views work on the subscriber Home Screen |\n\n---\n\n## Layout language v2 — exact authoring\n\nv1 trees stay valid. Anything below sets `required_version: 2`. Do not invent types, CSS keys, or JavaScript in bindings.\n\n**Call `get_plan` first.**\n\n| Capability | Free | Pro+ |\n|---|---|---|\n| 19 v1 types, `shape`, mapping, `calendar` | Yes | Yes |\n| `views` / `action` / `visibleFlag` (`family_views`) | No | Yes |\n| `type: \"chart\"` (`family_charts`) | No | Yes |\n| `type: \"grid\"` (`family_grid`) | No | Yes |\n| `type: \"table\"` (`family_table`) | No | Yes |\n\n`validate_template` never 403s — it returns `families_used`. `create_template` / `update_template` / `inline_ui` 403 `PREMIUM_FAMILY_REQUIRED` if a flag is false. Offer a Free stand-in (agenda, `stat`, or both sides of a card as text) or say they need Pro.\n\nPower is **quotas**, not a third language. Do not variant-gate calendar.\n\n### Mapping — closed functions of a number\n\nAny node's `color` / `*Color` / `background` / `fillColor` / `opacity` / `visible` may be a **map object** instead of a hex or `{{binding}}`. No `{{n > 80 ? green : red}}`.\n\n`from` must be the entire value `\"{{number_field}}\"`. Choose **exactly one** of `stops` or `when`.\n\n**`stops`** — interpolate between points. Colors blend; numbers/opacity lerp. Never use `stops` on `visible`.\n\n```json\n\"fillColor\": {\n  \"from\": \"{{score}}\",\n  \"stops\": [\n    { \"at\": 0, \"value\": \"#FF3B30\" },\n    { \"at\": 20, \"value\": \"#FFCC00\" },\n    { \"at\": 80, \"value\": \"#34C759\" }\n  ]\n}\n```\n\n**`when`** — first matching clause wins. One operator per clause: `lt` | `lte` | `gt` | `gte` | `eq`. Optional `else`.\n\n```json\n\"visible\": {\n  \"from\": \"{{count}}\",\n  \"when\": [{ \"lt\": 1, \"value\": false }],\n  \"else\": true\n}\n```\n\n`colorScale` on chart / grid / table is a map evaluated **per mark / cell** against that cell's number. Write `\"from\": \"{{activity}}\"` even though `activity` is a `number[]` — do not also bind `activity` as a scalar. Zero cells on a grid use `emptyColor`, not the scale.\n\nUse mapping when the **look** should change with a number (tinted button, fading card, score ring). Prefer `when` for on/off and discrete bands; `stops` for a continuous wash. `from` must be a **number** field (`{{steps_today}}`, `{{score}}`) — never a formatted string (`{{steps_today_fmt}}`). Hex is 6-digit.\n\n### `shape` — Free\n\n`variant`: `circle` | `rectangle` | `capsule` | `line` | `triangle`. Optional `href` (or `action` on Pro+). No children.\n\n| Style | Type | Control |\n|---|---|---|\n| `size` | number | Diameter / edge. **Wins over** `width` / `height` when set |\n| `width` / `height` | number | Use when the shape is not square (`line`, wide capsule) |\n| `fillColor` | hex, `{{color}}`, or map | Interior. **Ignored on `line`** |\n| `strokeColor` | hex / map | Outline |\n| `strokeWidth` | number | Outline thickness |\n| `cornerRadius` | number | `rectangle` only |\n| `opacity` | 0–1 or map | Whole shape |\n\nHero circle: `size` 64–80, centered, one caption. Status pip: `size` 10–14. Do not stack three shapes on small.\n\n### `calendar` — Free\n\nConsumes `event[]`. The node does **not** call EventKit itself. Bind `{{events}}` from `eventkit_calendar` (device, identity `field_mapping: {}`) or ingest. Google Calendar is still indexed strings — do not bind it to this node yet. `start` is ISO 8601; all-day may be `yyyy-MM-dd`. Omit `day` / `month` to use today / this month **on device**.\n\n`variant`: `agenda` | `columns` | `month`.\n\n| Variant | What it shows | When to pick |\n|---|---|---|\n| `agenda` | Start time (or \"All day\") + title. `end` is occupancy, not painted. | Default. Small/medium next-up |\n| `columns` | Titles only in day columns. Node `days`: **1, 3, or 7** | Medium/large week strip |\n| `month` | Month grid with **dots only** — no titles | Large \"busy this month\" |\n\n| Style | Default intent |\n|---|---|\n| `width` / `height` | Fill remaining space; set `height` on medium (≈ 110) |\n| `headerFontSize` / `headerColor` | Day / month chrome — caption scale, often `#8E8E93` or white on dark |\n| `eventFontSize` / `eventColor` | Agenda / column titles |\n| `dotColor` / `todayColor` / `gridColor` | Month marks and today |\n| `maxEvents` | Agenda clip (e.g. 3 on small, 5 on medium) |\n| `opacity` | Whole node |\n\nIndexed `event_1_title` rows remain legal. Prefer `calendar` when they asked for a calendar.\n\n### `chart` — Pro+ `family_charts`\n\n`type` is always `\"chart\"`. **`bar_chart` and `pie_chart` are invalid.**\n\n| `variant` | Required bindings | Optional | Cap |\n|---|---|---|---|\n| `bar` | `y` `number[]` | `x` `string[]` categories | 24 |\n| `line` | `x` `number[]`, `y` `number[]` | — | 24 |\n| `scatter` | `x` `number[]`, `y` `number[]` | `sizes` `number[]` | 24 |\n| `candle` | `x`, `open`, `high`, `low`, `close` all `number[]` | — | 24 |\n| `pie` | `y` `number[]`, `labels` `string[]` | — | **8** |\n\n`x` on line / scatter / candle is numeric (unix, hour-of-day). ISO date **strings** in that array are accepted at ingest. Parallel arrays must be the same length (`SERIES_LENGTH_MISMATCH`).\n\n| Style | Variants | Control |\n|---|---|---|\n| `width` / `height` | all | Medium sparkline height **64–80**. Set `size` on pie instead |\n| `size` | pie | Diameter. Small donut ≈ 75 |\n| `strokeColor` / `strokeWidth` | line, scatter | Accent stroke. Width 2 |\n| `fillColor` | bar, area-ish line | Single series fill. Or `colorScale` per bar |\n| `showAxes` | bar, line, scatter, candle | `false` = sparkline. `true` = plot plane (grid + Y ticks + X labels) so scale is readable |\n| `showLabels` / `labelFontSize` / `labelColor` / `axisColor` | cartesian | X labels. Follows `showAxes` unless you set it. Keep both off on small |\n| `orientation` | bar | `vertical` (default) or `horizontal` |\n| `barGap` / `cornerRadius` | bar | Gap 3, radius 3 |\n| `showValues` | bar | Rarely — clips on small |\n| `colorScale` | bar | Map per bar value |\n| `showAverage` / `averageColor` | bar | Dashed mean of the current `y` series. Default color `#636366`. Pair with `showAxes` |\n| `showDots` / `dotSize` / `curve` | line | `curve`: `linear` \\| `smooth` |\n| `innerRadius` | pie | 0 = pie, `0.55` = donut |\n| `colors` | pie | `string[]` of 6-digit hex. Default `#7B61FF` `#34C759` `#FF9500` `#FF3B30` `#007AFF` `#AF52DE` |\n| `upColor` / `downColor` | candle | Default `#34C759` / `#FF3B30` |\n| `opacity` | all | Whole mark |\n\n**`showAverage` (bar only).** Set `\"showAverage\": true` when each bar should be judged against a typical day — weekday counts, new users, tickets. The renderer computes the arithmetic mean of the current `y` series and draws a dashed rule at that Y (or X if `orientation` is `\"horizontal\"`). It is not a second binding and not a second chart. Optional `\"averageColor\"` (default `#636366`). Pair with `showAxes: true` so the line sits on a readable scale. Line, pie, scatter, and candle ignore it. Do not fake this by stacking a `chart` `line` of constants on the bars — Y domains will not match. Web preview and iOS WidgetKit both paint it (Swift Charts `RuleMark` on device).\n\nOne chart is the hero. Do not put a chart and a large `stat` of equal weight on small. Candle / scatter belong on medium/large.\n\n### `grid` — Pro+ `family_grid`\n\n`variant` is `activity` (omit = activity). **`rows` and `columns` are positive ints on the node** — not bindings. `binding` is `number[]` of length `rows × columns`. Max **400** cells (7×52 fits; 7×53 does not). Row-major. No per-cell tap.\n\n| Style | Default | Control |\n|---|---|---|\n| `width` / `height` | fill | Set `height` 72–96 on medium |\n| `cellShape` | `square` | `square` \\| `circle` \\| `triangle` |\n| `cellGap` | 2 | Keep 2. Larger gaps eat the canvas |\n| `cellCornerRadius` | 2 | Squares only |\n| `emptyColor` | `#EBEDF0` | Color for **0**. Dark themes: `#161B22` |\n| `colorScale` | — | Map from the **same** series binding; each cell uses its own number |\n| `opacity` | 1 | Whole grid |\n\nWeek strip: `\"rows\": 1, \"columns\": 7`. Year: `7 × 52`. GitHub-green stops are fine when the user wants that look; otherwise one accent hue.\n\n### `table` — Pro+ `family_table`\n\n| Field | Type | Required |\n|---|---|---|\n| `columns` | `{{binding}}` → `string[]` | Yes — headers |\n| `cells` | `{{binding}}` → `string[]` | Yes — **row-major** |\n| `rows` | `{{binding}}` → `string[]` | No — row headers |\n\n`columns: 3` (a number) is a **grid** size, not a table. Hard cap **4 columns × 6 rows**. Numbers in `cells` coerce to string. Clip, no scroll.\n\nRow count = `rows.length` if row headers exist, else `cells.length / columns.length` (must divide evenly).\n\n| Style | Default | Control |\n|---|---|---|\n| `showColumnHeaders` | true | |\n| `showRowHeaders` | true if `rows` bound | |\n| `headerBackground` / `headerColor` | `#F3F4F6` / `#111827` | Chrome. Dark: `#1F2937` / `#F9FAFB` |\n| `cellColor` / `cellFontSize` / `cellPadding` | `#111827` / 11 / 4 | Body type — caption scale |\n| `gridColor` / `gridWidth` | `#E5E7EB` / 1 | Hairline only |\n| `colorScale` | — | Heats a cell **if the string parses as a number** |\n| `width` / `height` / `opacity` | fill | Clip inside the box |\n\n3 columns × 3–4 rows on medium. Never five headers.\n\n### Views and actions — Pro+ `family_views`\n\n`family_views` is two patterns. Pick one per tap — do not mix them for the same interaction.\n\n| Intent | Mechanism | Golden |\n|---|---|---|\n| Tap shows/hides content on the **same** screen | `toggle_flag` + `visibleFlag`. No `views` map. | **v2-vocab-flip** |\n| Tap goes to **another** screen | `set_view` + `views` | **v2-mission-control** |\n\n`tree` is **always home**. Optional sibling `views` is a map of slugs → trees. Max **4** named views. Slug: `^[a-z][a-z0-9_]{0,31}$`. Do not name a view `home`. Depth 8 / 80 nodes **per view**. 64 KiB for `{ tree, views }`.\n\n**Screen swap**\n\n```json\n{\n  \"action\": { \"type\": \"set_view\", \"view\": \"dashboard\" }\n}\n```\n\n**In-place reveal** — flags default **false** (hidden). `visibleFlag` shows the node when that flag is true.\n\n```json\n{\n  \"action\": { \"type\": \"toggle_flag\", \"flag\": \"show_answer\" }\n}\n```\n\n```json\n{\n  \"id\": \"answer\",\n  \"type\": \"text\",\n  \"binding\": \"{{answer}}\",\n  \"visibleFlag\": \"show_answer\"\n}\n```\n\n**XOR:** a node may have `action` **or** `href`, never both (`ACTION_HREF_XOR`).\n\n**Reload** (`RefreshWidgetIntent`) returns **home** and clears flags, with latest data. **Ingest keeps** the current view. Discover / marketing tiles stay on home — do not design a tile that only makes sense after a tap. Reveal goldens keep the prompt visible so Discover still reads.\n\nIn-place reveal is the small pattern: **v2-vocab-flip**. Prompt stays; answer appears on the same card. Named views on small feel cramped — prefer `toggle_flag`, or warn.\n\nHub pattern (large): **v2-mission-control**. Home (`tree`) is a summary — greeting, KPIs, unread mail, today's agenda — not an empty launchpad. Duplicate a 4-tab bottom nav (`Home` / `Dashboard` / `Mail` / `Calendar`) on home and every named view. Selected tab: ink `#37352F` semibold. Others: muted `#9B9A97`. Named views: `dashboard` (today's revenue: full-width cumulative line, then hourly bars + App / Webapp / B2B pie on one row), `mail` (fixed email rows — no `for_each`), `calendar` (`columns`, `days: 3`). `set_view` `\"home\"` returns home. Reload also returns home. Discover tiles stay on home.\n\n### Series bindings in content\n\nPOST the arrays the tree asked for. Extra keys 400.\n\n```json\n{\n  \"prompt\": \"ventana\",\n  \"answer\": \"window\",\n  \"events\": [{ \"start\": \"2026-08-30T09:00:00\", \"title\": \"Standup\" }],\n  \"hours\": [9, 10, 11],\n  \"revenue\": [12, 18, 15],\n  \"activity\": [0, 1, 3, 0, 2, 4, 1],\n  \"headers\": [\"W\", \"L\", \"Pct\"],\n  \"stats\": [\"12\", \"4\", \".750\"]\n}\n```\n\n---\n\n## Design process (before you write JSON)\n\n1. **Clarify intent** — glance KPI? digest? status? media? trend? heat? reveal on the same screen? flip between screens?\n2. **`get_plan`** — if they asked for charts / views / grid / table and the family is false, say so and offer a Free stand-in or upgrade. Do not emit the family.\n3. **Pick `widget_size`** — smallest size that fits without clutter.\n4. **Choose one hero** — what is the single takeaway? (Per view, if using views.)\n5. **`list_golden_examples`** — S1–L2 for composition; `v2-*` for language nodes. Load with `example_id`.\n6. **Adapt the pattern** — swap bindings for real fields; static labels stay literal.\n7. **Assign typography** — hero / body / caption sizes from table.\n8. **Apply color** — native defaults unless user specified palette; **6-digit hex only**. Maps and `colorScale` use the same hex rule.\n9. **Run sizing math** — main skill algorithm; shrink before dropping the hero. Chart/grid/table need an explicit `height` or `size`.\n10. **(Optional) `validate_template`** — max **2 calls** per design; then `create_template`. Structure must pass; design warnings do not block save. `families_used` is informational.\n11. **Preview pause** — describe what the user will see, including which view is home; invite tweaks.\n\n---\n\n## Pre-submit checklist\n\nBefore `create_template`, confirm mentally (server **structure** is the save gate; padding/overflow are warnings):\n\n- [ ] One clear hero element (per view)\n- [ ] ≤ 3 font sizes, ≤ 2 active weights\n- [ ] Root `padding` ≥ 12 (guideline — `ROOT_PADDING_LOW` will not block save)\n- [ ] Labels use secondary color (`#8E8E93` or user-muted variant)\n- [ ] At most one accent color (unless user asked for more)\n- [ ] `maxLines` set on text that must not wrap\n- [ ] Top-trailing corner clear of hero values (reload button overlay)\n- [ ] Static labels/icons use plain literals (no `{{}}` unless value changes per refresh)\n- [ ] All colors are 6-digit `#RRGGBB` (no alpha suffix)\n- [ ] Layout suits the **data** — not a generic placeholder grid\n- [ ] `get_plan` checked for any `chart` / `grid` / `table` / `views` / `action`\n- [ ] No `bar_chart`, `pie_chart`, JS ternaries, or `action`+`href` on one node\n- [ ] Chart series lengths match; pie ≤ 8; grid `rows*columns` = array length ≤ 400; table ≤ 4×6\n\n---\n\n## Talking to the user about design\n\n- Describe the **visual hierarchy** in plain language: *\"Large follower count up top, latest post caption below in gray.\"*\n- Offer **one alternative** when useful: *\"I can also do a side-by-side layout with your profile photo — prefer that?\"*\n- If the user says it looks plain, add **structure** (nested card, badge, progress) or **typography contrast** — not more random colors.\n- For v2: name the **control**, not the type. *\"The word is on the face; tap flips to the translation; reload shows the word again.\"* *\"Home is three tiles; revenue is a sparkline; reload brings you back.\"*\n"
}

SHA-256: 301a7d141736659fdf2358ba956d76df7cef033fdb53a00dcd5eecd80d9986a5