@compilr-dev/sdk 0.18.10 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/canvas/assets.d.ts +99 -0
  2. package/dist/canvas/assets.js +111 -0
  3. package/dist/canvas/color.d.ts +85 -0
  4. package/dist/canvas/color.js +216 -0
  5. package/dist/canvas/index.d.ts +8 -0
  6. package/dist/canvas/index.js +16 -0
  7. package/dist/canvas/quality-checks.d.ts +75 -0
  8. package/dist/canvas/quality-checks.js +217 -0
  9. package/dist/canvas/ramp.d.ts +45 -0
  10. package/dist/canvas/ramp.js +98 -0
  11. package/dist/capabilities/packs.js +16 -2
  12. package/dist/platform/context.d.ts +16 -2
  13. package/dist/platform/context.js +13 -1
  14. package/dist/platform/repositories.d.ts +4 -0
  15. package/dist/platform/sqlite/canvas-repository.d.ts +9 -0
  16. package/dist/platform/sqlite/canvas-repository.js +52 -0
  17. package/dist/platform/sqlite/db.js +29 -0
  18. package/dist/platform/sqlite/schema.d.ts +1 -1
  19. package/dist/platform/sqlite/schema.js +1 -1
  20. package/dist/platform/tools/canvas-tools.d.ts +12 -2
  21. package/dist/platform/tools/canvas-tools.js +316 -6
  22. package/dist/platform/tools/image-tools.d.ts +9 -2
  23. package/dist/platform/tools/image-tools.js +4 -2
  24. package/dist/platform/tools/index.js +10 -2
  25. package/dist/platform/tools/project-tools.js +3 -2
  26. package/dist/skills/canvas-exemplars.d.ts +8 -0
  27. package/dist/skills/canvas-exemplars.js +36 -0
  28. package/dist/skills/canvas-icons.d.ts +6 -0
  29. package/dist/skills/canvas-icons.js +84 -0
  30. package/dist/skills/canvas-skills.js +207 -20
  31. package/dist/skills/platform-skills.d.ts +2 -0
  32. package/dist/skills/platform-skills.js +8 -0
  33. package/dist/team/tool-config.js +4 -0
  34. package/package.json +1 -1
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Worked exemplars — the samples an agent reads before authoring.
3
+ *
4
+ * ⚠️ GENERATED by scripts/build-canvas-exemplars.mjs from the design handoff. Do not
5
+ * edit by hand; re-run the script.
6
+ *
7
+ * ⚠️ WHY EXEMPLARS AND NOT MORE ADJECTIVES. The previous guidance described quality in
8
+ * words — "clean, minimal, generous whitespace" — so every session re-derived taste from
9
+ * scratch. Shown a real screenshot the model produces good work immediately, so what was
10
+ * missing was never capability; it was a reference.
11
+ *
12
+ * ⚠️ TWO SKILLS, NOT ONE. Together they are ~7.7K tokens and an agent authoring an
13
+ * infographic has no use for the app-screen markup, so canvas_guide serves them
14
+ * separately and each costs only what it is worth.
15
+ */
16
+ import { defineSkill } from '@compilr-dev/agents';
17
+ /** The light theme-flat exemplar, ready to adapt. */
18
+ export const THEME_FLAT_EXEMPLAR = '<div style="--canvas-bg: #E9EAEE; --canvas-fg: #1B1C1F; --canvas-muted: #63656D; --canvas-accent: #C2410C; --canvas-card: #FFFFFF; --canvas-border: #D5D7DE; --canvas-surface-1: #FFFFFF; --canvas-surface-2: #F2F3F6; --canvas-hairline: #D5D7DE; width: 1000px; background: var(--canvas-bg); font-family: system-ui, -apple-system, \'Segoe UI\', Roboto, sans-serif; color: var(--canvas-fg); padding: 40px;">\n\n <div style="display: flex; align-items: baseline; justify-content: space-between; gap: 24px; margin-bottom: 24px;">\n <div style="font-size: 11px; letter-spacing: 0.14em; font-weight: 600; color: var(--canvas-muted);">Q3 DELIVERY REVIEW &nbsp;&middot;&nbsp; PLATFORM</div>\n <div style="font-size: 11px; letter-spacing: 0.08em; color: var(--canvas-muted);">as of 6 Sep 2026</div>\n </div>\n\n <div style="display: grid; grid-template-columns: 1.35fr 1fr; gap: 16px; margin-bottom: 16px;">\n <div style="background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); padding: 30px 32px;">\n <div style="font-size: 11px; letter-spacing: 0.12em; font-weight: 600; color: var(--canvas-accent); margin-bottom: 16px;">THE QUARTER IN ONE NUMBER</div>\n <div style="font-size: 84px; line-height: 0.94; font-weight: 700; letter-spacing: -0.035em; font-variant-numeric: tabular-nums; margin-bottom: 12px;">41<span style="font-size: 40px; font-weight: 600; color: var(--canvas-muted);"> of 47</span></div>\n <div style="font-size: 20px; line-height: 1.35; font-weight: 600; letter-spacing: -0.01em; margin-bottom: 14px; max-width: 22em;">commitments shipped, and the six that slipped share one cause.</div>\n <p style="font-size: 14px; line-height: 1.65; color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg)); margin: 0;">Every delayed item was blocked on the migration, not on capacity. Team throughput rose 12% over Q2 while the queue behind a single dependency grew, which is why headcount is the wrong lever here.</p>\n </div>\n\n <div style="display: grid; grid-template-rows: repeat(3, 1fr); gap: 16px;">\n <div style="background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); padding: 18px 20px; display: flex; align-items: baseline; justify-content: space-between; gap: 16px;">\n <div>\n <div style="font-size: 10px; letter-spacing: 0.12em; font-weight: 600; color: var(--canvas-muted); margin-bottom: 6px;">CYCLE TIME</div>\n <div style="font-size: 34px; line-height: 1; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums;">6.2<span style="font-size: 15px; font-weight: 500; color: var(--canvas-muted);"> days</span></div>\n </div>\n <div style="font-size: 13px; font-weight: 600; color: var(--canvas-accent); flex: none;">&#9660; 1.8</div>\n </div>\n <div style="background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); padding: 18px 20px; display: flex; align-items: baseline; justify-content: space-between; gap: 16px;">\n <div>\n <div style="font-size: 10px; letter-spacing: 0.12em; font-weight: 600; color: var(--canvas-muted); margin-bottom: 6px;">ESCAPED DEFECTS</div>\n <div style="font-size: 34px; line-height: 1; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums;">3</div>\n </div>\n <div style="font-size: 13px; font-weight: 600; color: var(--canvas-muted); flex: none;">&#9660; 5</div>\n </div>\n <div style="background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); padding: 18px 20px; display: flex; align-items: baseline; justify-content: space-between; gap: 16px;">\n <div>\n <div style="font-size: 10px; letter-spacing: 0.12em; font-weight: 600; color: var(--canvas-muted); margin-bottom: 6px;">BLOCKED, NOW</div>\n <div style="font-size: 34px; line-height: 1; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums;">6</div>\n </div>\n <div style="font-size: 13px; font-weight: 600; color: var(--canvas-muted); flex: none;">&#9650; 4</div>\n </div>\n </div>\n </div>\n\n <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">\n\n <div style="background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); padding: 22px 24px 20px;">\n <div style="font-size: 10px; letter-spacing: 0.12em; font-weight: 600; color: var(--canvas-muted); margin-bottom: 18px;">SHIPPED PER SPRINT</div>\n <svg width="100%" height="150" viewBox="0 0 420 150" style="display: block; overflow: visible;">\n <line x1="0" y1="126" x2="420" y2="126" style="stroke: var(--canvas-hairline, var(--canvas-border));" stroke-width="1"></line>\n <rect x="10" y="72" width="46" height="54" style="fill: color-mix(in oklab, var(--canvas-fg) 22%, var(--canvas-bg));"></rect>\n <text x="33" y="62" text-anchor="middle" font-size="13" font-weight="700" style="fill: var(--canvas-fg); font-family: system-ui, sans-serif;">6</text>\n <text x="33" y="142" text-anchor="middle" font-size="11" style="fill: var(--canvas-muted); font-family: system-ui, sans-serif;">S1</text>\n <rect x="76" y="45" width="46" height="81" style="fill: color-mix(in oklab, var(--canvas-fg) 22%, var(--canvas-bg));"></rect>\n <text x="99" y="35" text-anchor="middle" font-size="13" font-weight="700" style="fill: var(--canvas-fg); font-family: system-ui, sans-serif;">9</text>\n <text x="99" y="142" text-anchor="middle" font-size="11" style="fill: var(--canvas-muted); font-family: system-ui, sans-serif;">S2</text>\n <rect x="142" y="54" width="46" height="72" style="fill: color-mix(in oklab, var(--canvas-fg) 22%, var(--canvas-bg));"></rect>\n <text x="165" y="44" text-anchor="middle" font-size="13" font-weight="700" style="fill: var(--canvas-fg); font-family: system-ui, sans-serif;">8</text>\n <text x="165" y="142" text-anchor="middle" font-size="11" style="fill: var(--canvas-muted); font-family: system-ui, sans-serif;">S3</text>\n <rect x="208" y="27" width="46" height="99" style="fill: color-mix(in oklab, var(--canvas-fg) 22%, var(--canvas-bg));"></rect>\n <text x="231" y="17" text-anchor="middle" font-size="13" font-weight="700" style="fill: var(--canvas-fg); font-family: system-ui, sans-serif;">11</text>\n <text x="231" y="142" text-anchor="middle" font-size="11" style="fill: var(--canvas-muted); font-family: system-ui, sans-serif;">S4</text>\n <rect x="274" y="99" width="46" height="27" style="fill: var(--canvas-accent);"></rect>\n <text x="297" y="89" text-anchor="middle" font-size="13" font-weight="700" style="fill: var(--canvas-accent); font-family: system-ui, sans-serif;">3</text>\n <text x="297" y="142" text-anchor="middle" font-size="11" style="fill: var(--canvas-muted); font-family: system-ui, sans-serif;">S5</text>\n <rect x="340" y="90" width="46" height="36" style="fill: color-mix(in oklab, var(--canvas-fg) 22%, var(--canvas-bg));"></rect>\n <text x="363" y="80" text-anchor="middle" font-size="13" font-weight="700" style="fill: var(--canvas-fg); font-family: system-ui, sans-serif;">4</text>\n <text x="363" y="142" text-anchor="middle" font-size="11" style="fill: var(--canvas-muted); font-family: system-ui, sans-serif;">S6</text>\n </svg>\n <div style="font-size: 13px; line-height: 1.55; color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg)); margin-top: 20px; border-top: 1px solid var(--canvas-hairline, var(--canvas-border)); padding-top: 12px;">Sprint 5 is when the migration landed in the critical path.</div>\n </div>\n\n <div style="background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); padding: 22px 24px 20px;">\n <div style="font-size: 10px; letter-spacing: 0.12em; font-weight: 600; color: var(--canvas-muted); margin-bottom: 14px;">THE SIX THAT SLIPPED</div>\n <div style="background: var(--canvas-surface-2, var(--canvas-bg)); border: 1px solid var(--canvas-hairline, var(--canvas-border));">\n <div style="display: grid; grid-template-columns: 1fr 88px 74px; gap: 12px; padding: 8px 14px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 10px; letter-spacing: 0.1em; font-weight: 600; color: var(--canvas-muted);"><span>COMMITMENT</span><span>BLOCKED BY</span><span style="text-align: right;">DAYS</span></div>\n <div style="display: grid; grid-template-columns: 1fr 88px 74px; gap: 12px; padding: 10px 14px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 13px; align-items: baseline;"><span style="font-weight: 600;">Billing export</span><span style="color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg));">migration</span><span style="text-align: right; font-variant-numeric: tabular-nums;">21</span></div>\n <div style="display: grid; grid-template-columns: 1fr 88px 74px; gap: 12px; padding: 10px 14px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 13px; align-items: baseline;"><span style="font-weight: 600;">Audit trail</span><span style="color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg));">migration</span><span style="text-align: right; font-variant-numeric: tabular-nums;">18</span></div>\n <div style="display: grid; grid-template-columns: 1fr 88px 74px; gap: 12px; padding: 10px 14px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 13px; align-items: baseline;"><span style="font-weight: 600;">SSO for teams</span><span style="color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg));">migration</span><span style="text-align: right; font-variant-numeric: tabular-nums;">14</span></div>\n <div style="display: grid; grid-template-columns: 1fr 88px 74px; gap: 12px; padding: 10px 14px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 13px; align-items: baseline;"><span style="font-weight: 600;">Usage alerts</span><span style="color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg));">migration</span><span style="text-align: right; font-variant-numeric: tabular-nums;">11</span></div>\n <div style="display: grid; grid-template-columns: 1fr 88px 74px; gap: 12px; padding: 10px 14px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 13px; align-items: baseline;"><span style="font-weight: 600;">Retention job</span><span style="color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg));">migration</span><span style="text-align: right; font-variant-numeric: tabular-nums;">9</span></div>\n <div style="display: grid; grid-template-columns: 1fr 88px 74px; gap: 12px; padding: 10px 14px; font-size: 13px; align-items: baseline;"><span style="font-weight: 600;">Region failover</span><span style="color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg));">migration</span><span style="text-align: right; font-variant-numeric: tabular-nums;">6</span></div>\n </div>\n <div style="font-size: 13px; line-height: 1.55; color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg)); margin-top: 16px;">One dependency, 79 days of accumulated delay.</div>\n </div>\n </div>\n\n <div style="border-top: 1px solid var(--canvas-hairline, var(--canvas-border)); margin-top: 26px; padding-top: 14px; display: flex; align-items: baseline; justify-content: space-between; gap: 24px; font-size: 11px; letter-spacing: 0.06em; color: var(--canvas-muted);">\n <span>PLATFORM &nbsp;&middot;&nbsp; QUARTERLY REVIEW</span>\n <span>Source: delivery board, sprints 1&ndash;6</span>\n </div>\n </div>';
19
+ /** The same markup in dark: only these nine values change. */
20
+ export const THEME_FLAT_DARK_TOKENS = '--canvas-bg: #17181C; --canvas-fg: #ECEDF1; --canvas-muted: #9A9CA6; --canvas-accent: #FB7B4B; --canvas-card: #22242A; --canvas-border: #31333B; --canvas-surface-1: #22242A; --canvas-surface-2: #1B1D22; --canvas-hairline: #31333B';
21
+ /** The app-screen exemplar — a single UI screen, four surfaces. */
22
+ export const APP_SCREEN_EXEMPLAR = '<div style="--canvas-bg: #E9EAEE; --canvas-fg: #1B1C1F; --canvas-muted: #63656D; --canvas-accent: #C2410C; --canvas-card: #FFFFFF; --canvas-border: #D5D7DE; --canvas-surface-1: #FFFFFF; --canvas-surface-2: #F2F3F6; --canvas-hairline: #DCDEE4; width: 1000px; background: var(--canvas-surface-1, var(--canvas-card)); font-family: system-ui, -apple-system, \'Segoe UI\', Roboto, sans-serif; color: var(--canvas-fg); display: grid; grid-template-columns: 232px 1fr;">\n\n <div style="border-right: 1px solid var(--canvas-hairline, var(--canvas-border)); display: flex; flex-direction: column; min-height: 640px;">\n <div style="padding: 18px 18px 16px; display: flex; align-items: center; gap: 10px;">\n <span style="width: 28px; height: 28px; background: var(--canvas-fg); color: var(--canvas-surface-1, var(--canvas-card)); font-size: 11px; font-weight: 700; letter-spacing: 0.02em; display: flex; align-items: center; justify-content: center; flex: none;">CO</span>\n <span style="font-size: 15px; font-weight: 700; letter-spacing: -0.01em;">Cohort</span>\n </div>\n\n <div style="padding: 6px 10px 0; display: flex; flex-direction: column; gap: 1px;">\n <div style="display: flex; align-items: center; gap: 10px; padding: 10px 12px; background: color-mix(in oklab, var(--canvas-accent) 10%, var(--canvas-surface-1, var(--canvas-card))); box-shadow: inset 2px 0 0 var(--canvas-accent); font-size: 13.5px; font-weight: 600;">\n <span style="font-size: 13px; flex: none; color: var(--canvas-accent);">&#9632;</span>Overview\n </div>\n <div style="display: flex; align-items: center; gap: 10px; padding: 10px 12px; font-size: 13.5px; color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">\n <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="flex: none; color: var(--canvas-muted);"><circle cx="6" cy="6" r="2.5"></circle><path d="M2 13.5c.8-2.2 2.2-3.3 4-3.3s3.2 1.1 4 3.3"></path><path d="M11 4.2a2.5 2.5 0 0 1 0 4.6"></path></svg>Applicants\n <span style="margin-left: auto; font-size: 12px; color: var(--canvas-muted); font-variant-numeric: tabular-nums;">248</span>\n </div>\n <div style="display: flex; align-items: center; gap: 10px; padding: 10px 12px; font-size: 13.5px; color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">\n <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="flex: none; color: var(--canvas-muted);"><rect x="2.5" y="3.5" width="11" height="10" rx="1"></rect><path d="M2.5 6.5h11M5.5 2v2.5M10.5 2v2.5"></path></svg>Interviews\n <span style="margin-left: auto; font-size: 12px; color: var(--canvas-muted); font-variant-numeric: tabular-nums;">67</span>\n </div>\n <div style="display: flex; align-items: center; gap: 10px; padding: 10px 12px; font-size: 13.5px; color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">\n <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="flex: none; color: var(--canvas-muted);"><path d="M9.5 2H4.5A1.5 1.5 0 0 0 3 3.5v9A1.5 1.5 0 0 0 4.5 14h7a1.5 1.5 0 0 0 1.5-1.5V5.5z"></path><path d="M9.5 2v3.5H13"></path></svg>Documents\n </div>\n <div style="display: flex; align-items: center; gap: 10px; padding: 10px 12px; font-size: 13.5px; color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">\n <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="flex: none; color: var(--canvas-muted);"><path d="M2 14h12"></path><path d="M4.5 14V9M8 14V4.5M11.5 14V7"></path></svg>Reports\n </div>\n <div style="display: flex; align-items: center; gap: 10px; padding: 10px 12px; font-size: 13.5px; color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">\n <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="flex: none; color: var(--canvas-muted);"><path d="M2 5.5A1.5 1.5 0 0 1 3.5 4h2.2l1.3 1.6h5.5A1.5 1.5 0 0 1 14 7.1v5.4A1.5 1.5 0 0 1 12.5 14h-9A1.5 1.5 0 0 1 2 12.5z"></path></svg>Programmes\n <span style="margin-left: auto; font-size: 12px; color: var(--canvas-muted); font-variant-numeric: tabular-nums;">12</span>\n </div>\n </div>\n\n <div style="margin-top: auto; border-top: 1px solid var(--canvas-hairline, var(--canvas-border)); padding: 12px 22px; font-size: 13.5px; color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">Settings</div>\n <div style="border-top: 1px solid var(--canvas-hairline, var(--canvas-border)); padding: 14px 18px; display: flex; align-items: center; gap: 10px;">\n <span style="width: 30px; height: 30px; background: var(--canvas-surface-2, var(--canvas-bg)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 11px; font-weight: 600; color: var(--canvas-muted); display: flex; align-items: center; justify-content: center; flex: none;">RM</span>\n <span>\n <span style="display: block; font-size: 13px; font-weight: 600;">Rita Marchetti</span>\n <span style="display: block; font-size: 11px; color: var(--canvas-muted);">Admissions lead</span>\n </span>\n </div>\n </div>\n\n <div style="display: flex; flex-direction: column;">\n <div style="padding: 20px 28px 18px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); display: flex; align-items: flex-end; justify-content: space-between; gap: 24px;">\n <div>\n <div style="font-size: 11px; letter-spacing: 0.06em; color: var(--canvas-muted); margin-bottom: 6px;">Autumn 2027 &nbsp;/&nbsp; Admissions</div>\n <div style="font-size: 26px; font-weight: 700; letter-spacing: -0.022em;">Overview</div>\n </div>\n <div style="display: flex; align-items: center; gap: 8px; flex: none;">\n <span style="font-size: 13px; padding: 8px 14px; border: 1px solid var(--canvas-hairline, var(--canvas-border)); color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">Export</span>\n <span style="font-size: 13px; font-weight: 600; padding: 9px 15px; background: var(--canvas-fg); color: var(--canvas-surface-1, var(--canvas-card));">New applicant</span>\n </div>\n </div>\n <div style="background: var(--canvas-surface-2, var(--canvas-bg)); padding: 20px 28px 26px; flex: 1; display: flex; flex-direction: column; gap: 16px;">\n\n <div style="display: grid; grid-template-columns: 1.45fr 1fr; gap: 16px;">\n <div style="background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); padding: 22px 24px;">\n <div style="display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 14px;">\n <div style="font-size: 10px; letter-spacing: 0.12em; font-weight: 600; color: var(--canvas-accent);">THIS WEEK</div>\n <div style="font-size: 11px; color: var(--canvas-muted);">updated 4 min ago</div>\n </div>\n <div style="font-size: 27px; line-height: 1.2; font-weight: 700; letter-spacing: -0.022em; margin-bottom: 10px; max-width: 20em;">Interviews are keeping pace. Document review is not.</div>\n <p style="font-size: 13.5px; line-height: 1.65; color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg)); margin: 0 0 16px;">248 applicants across 12 programmes, and 67 have reached interview. The queue sits at document review, where 41 files have waited more than five days &mdash; two programmes account for 30 of them.</p>\n <span style="display: inline-block; font-size: 12.5px; font-weight: 600; color: var(--canvas-accent); border-bottom: 1px solid var(--canvas-accent); padding-bottom: 1px;">Review the 41 waiting files</span>\n </div>\n\n <div style="background: var(--canvas-fg); color: var(--canvas-surface-1, var(--canvas-card)); padding: 20px 22px; display: flex; flex-direction: column; gap: 14px;">\n <div style="display: flex; align-items: baseline; justify-content: space-between; gap: 12px;">\n <span style="font-size: 10px; letter-spacing: 0.12em; font-weight: 600;">PIPELINE</span>\n <span style="font-size: 10px; letter-spacing: 0.06em; opacity: 0.62;">stage by stage</span>\n </div>\n <div style="display: flex; flex-direction: column; gap: 11px;">\n <div>\n <div style="display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 12px;"><span style="opacity: 0.75;">Applied</span><span style="font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums;">248</span></div>\n <div style="height: 4px; background: rgba(255,255,255,0.16); margin-top: 5px;"><div style="height: 4px; width: 100%; background: rgba(255,255,255,0.62);"></div></div>\n </div>\n <div>\n <div style="display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 12px;"><span style="opacity: 0.75;">Documents in</span><span style="font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums;">193</span></div>\n <div style="height: 4px; background: rgba(255,255,255,0.16); margin-top: 5px;"><div style="height: 4px; width: 78%; background: rgba(255,255,255,0.62);"></div></div>\n </div>\n <div>\n <div style="display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 12px;"><span style="opacity: 0.75;">Reviewed</span><span style="font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums;">152</span></div>\n <div style="height: 4px; background: rgba(255,255,255,0.16); margin-top: 5px;"><div style="height: 4px; width: 61%; background: var(--canvas-accent);"></div></div>\n </div>\n <div>\n <div style="display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 12px;"><span style="opacity: 0.75;">Interviewed</span><span style="font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums;">67</span></div>\n <div style="height: 4px; background: rgba(255,255,255,0.16); margin-top: 5px;"><div style="height: 4px; width: 27%; background: rgba(255,255,255,0.62);"></div></div>\n </div>\n </div>\n <div style="margin-top: auto; border-top: 1px solid rgba(255,255,255,0.18); padding-top: 11px; font-size: 12px; opacity: 0.82;">41 files stalled at review</div>\n </div>\n </div>\n\n <div style="display: flex; align-items: center; gap: 8px;">\n <span style="font-size: 10px; letter-spacing: 0.12em; font-weight: 600; color: var(--canvas-muted); margin-right: 4px;">FACULTY</span>\n <span style="font-size: 12.5px; font-weight: 600; padding: 6px 12px; background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-fg);">All <span style="color: var(--canvas-muted); font-weight: 400;">12</span></span>\n <span style="font-size: 12.5px; padding: 6px 12px; background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">Design <span style="color: var(--canvas-muted);">4</span></span>\n <span style="font-size: 12.5px; padding: 6px 12px; background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">Business <span style="color: var(--canvas-muted);">5</span></span>\n <span style="font-size: 12.5px; padding: 6px 12px; background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">Engineering <span style="color: var(--canvas-muted);">3</span></span>\n </div>\n\n <div style="background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border));">\n <div style="padding: 13px 18px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); display: flex; align-items: baseline; justify-content: space-between; gap: 16px;">\n <span style="font-size: 14px; font-weight: 600;">Programmes needing attention</span>\n <span style="font-size: 12px; color: var(--canvas-muted);">4 of 12</span>\n </div>\n <div style="display: grid; grid-template-columns: 1.6fr 120px 84px 84px 92px; gap: 14px; padding: 9px 18px; background: var(--canvas-surface-2, var(--canvas-bg)); border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 10px; letter-spacing: 0.1em; font-weight: 600; color: var(--canvas-muted);">\n <span>PROGRAMME</span><span>REVIEW</span><span style="text-align: right;">APPLIED</span><span style="text-align: right;">OFFERS</span><span style="text-align: right;">WAITING</span>\n </div>\n <div style="display: grid; grid-template-columns: 1.6fr 120px 84px 84px 92px; gap: 14px; padding: 12px 18px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 13px; align-items: center;">\n <span style="font-weight: 600;">MSc Product Design</span>\n <span style="height: 5px; background: var(--canvas-surface-2, var(--canvas-bg));"><span style="display: block; height: 5px; width: 38%; background: var(--canvas-accent);"></span></span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">31</span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">4</span>\n <span style="text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; color: var(--canvas-accent);">17</span>\n </div>\n <div style="display: grid; grid-template-columns: 1.6fr 120px 84px 84px 92px; gap: 14px; padding: 12px 18px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 13px; align-items: center;">\n <span style="font-weight: 600;">MBA Sustainable Business</span>\n <span style="height: 5px; background: var(--canvas-surface-2, var(--canvas-bg));"><span style="display: block; height: 5px; width: 46%; background: var(--canvas-accent);"></span></span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">28</span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">6</span>\n <span style="text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; color: var(--canvas-accent);">13</span>\n </div>\n <div style="display: grid; grid-template-columns: 1.6fr 120px 84px 84px 92px; gap: 14px; padding: 12px 18px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 13px; align-items: center;">\n <span style="font-weight: 600;">MA Curation</span>\n <span style="height: 5px; background: var(--canvas-surface-2, var(--canvas-bg));"><span style="display: block; height: 5px; width: 71%; background: color-mix(in oklab, var(--canvas-fg) 32%, var(--canvas-bg));"></span></span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">19</span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">5</span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">7</span>\n </div>\n <div style="display: grid; grid-template-columns: 1.6fr 120px 84px 84px 92px; gap: 14px; padding: 12px 18px; font-size: 13px; align-items: center;">\n <span style="font-weight: 600;">MEng Robotics</span>\n <span style="height: 5px; background: var(--canvas-surface-2, var(--canvas-bg));"><span style="display: block; height: 5px; width: 83%; background: color-mix(in oklab, var(--canvas-fg) 32%, var(--canvas-bg));"></span></span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">24</span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">9</span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">4</span>\n </div>\n </div>\n </div>\n </div>\n </div>';
23
+ export const canvasExemplarInfographicSkill = defineSkill({
24
+ name: 'canvas-exemplar-infographic',
25
+ description: 'A worked theme-flat infographic to read before authoring one — sandbox-legal markup ' +
26
+ 'showing the hierarchy, grid and restraint the style calls for.',
27
+ tags: ['visual', 'canvas', 'design', 'exemplar'],
28
+ prompt: 'A worked **theme-flat infographic**. Read it, then build yours to the same shape.\n\n⚠️ This is a reference to ADAPT, not a template to fill in. Copying the words produces\na canvas about someone else\'s quarter. What to carry over is the SHAPE: one dominant\nelement, a claim rather than a label as the headline, the accent used once, figures that\nreconcile, density that varies.\n\n⚠️ It is already sandbox-legal — system fonts, inline styles, theme tokens, an explicit\nbackground, no network. Keep the outer div that declares the tokens: it is what lets the\ncanvas render on any theme, and the var(--canvas-surface-1, var(--canvas-card)) fallbacks\nkeep it working on a host that has not shipped the elevation ramp.\n\n```html\n<div style="--canvas-bg: #E9EAEE; --canvas-fg: #1B1C1F; --canvas-muted: #63656D; --canvas-accent: #C2410C; --canvas-card: #FFFFFF; --canvas-border: #D5D7DE; --canvas-surface-1: #FFFFFF; --canvas-surface-2: #F2F3F6; --canvas-hairline: #D5D7DE; width: 1000px; background: var(--canvas-bg); font-family: system-ui, -apple-system, \'Segoe UI\', Roboto, sans-serif; color: var(--canvas-fg); padding: 40px;">\n\n <div style="display: flex; align-items: baseline; justify-content: space-between; gap: 24px; margin-bottom: 24px;">\n <div style="font-size: 11px; letter-spacing: 0.14em; font-weight: 600; color: var(--canvas-muted);">Q3 DELIVERY REVIEW &nbsp;&middot;&nbsp; PLATFORM</div>\n <div style="font-size: 11px; letter-spacing: 0.08em; color: var(--canvas-muted);">as of 6 Sep 2026</div>\n </div>\n\n <div style="display: grid; grid-template-columns: 1.35fr 1fr; gap: 16px; margin-bottom: 16px;">\n <div style="background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); padding: 30px 32px;">\n <div style="font-size: 11px; letter-spacing: 0.12em; font-weight: 600; color: var(--canvas-accent); margin-bottom: 16px;">THE QUARTER IN ONE NUMBER</div>\n <div style="font-size: 84px; line-height: 0.94; font-weight: 700; letter-spacing: -0.035em; font-variant-numeric: tabular-nums; margin-bottom: 12px;">41<span style="font-size: 40px; font-weight: 600; color: var(--canvas-muted);"> of 47</span></div>\n <div style="font-size: 20px; line-height: 1.35; font-weight: 600; letter-spacing: -0.01em; margin-bottom: 14px; max-width: 22em;">commitments shipped, and the six that slipped share one cause.</div>\n <p style="font-size: 14px; line-height: 1.65; color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg)); margin: 0;">Every delayed item was blocked on the migration, not on capacity. Team throughput rose 12% over Q2 while the queue behind a single dependency grew, which is why headcount is the wrong lever here.</p>\n </div>\n\n <div style="display: grid; grid-template-rows: repeat(3, 1fr); gap: 16px;">\n <div style="background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); padding: 18px 20px; display: flex; align-items: baseline; justify-content: space-between; gap: 16px;">\n <div>\n <div style="font-size: 10px; letter-spacing: 0.12em; font-weight: 600; color: var(--canvas-muted); margin-bottom: 6px;">CYCLE TIME</div>\n <div style="font-size: 34px; line-height: 1; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums;">6.2<span style="font-size: 15px; font-weight: 500; color: var(--canvas-muted);"> days</span></div>\n </div>\n <div style="font-size: 13px; font-weight: 600; color: var(--canvas-accent); flex: none;">&#9660; 1.8</div>\n </div>\n <div style="background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); padding: 18px 20px; display: flex; align-items: baseline; justify-content: space-between; gap: 16px;">\n <div>\n <div style="font-size: 10px; letter-spacing: 0.12em; font-weight: 600; color: var(--canvas-muted); margin-bottom: 6px;">ESCAPED DEFECTS</div>\n <div style="font-size: 34px; line-height: 1; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums;">3</div>\n </div>\n <div style="font-size: 13px; font-weight: 600; color: var(--canvas-muted); flex: none;">&#9660; 5</div>\n </div>\n <div style="background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); padding: 18px 20px; display: flex; align-items: baseline; justify-content: space-between; gap: 16px;">\n <div>\n <div style="font-size: 10px; letter-spacing: 0.12em; font-weight: 600; color: var(--canvas-muted); margin-bottom: 6px;">BLOCKED, NOW</div>\n <div style="font-size: 34px; line-height: 1; font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums;">6</div>\n </div>\n <div style="font-size: 13px; font-weight: 600; color: var(--canvas-muted); flex: none;">&#9650; 4</div>\n </div>\n </div>\n </div>\n\n <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 16px;">\n\n <div style="background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); padding: 22px 24px 20px;">\n <div style="font-size: 10px; letter-spacing: 0.12em; font-weight: 600; color: var(--canvas-muted); margin-bottom: 18px;">SHIPPED PER SPRINT</div>\n <svg width="100%" height="150" viewBox="0 0 420 150" style="display: block; overflow: visible;">\n <line x1="0" y1="126" x2="420" y2="126" style="stroke: var(--canvas-hairline, var(--canvas-border));" stroke-width="1"></line>\n <rect x="10" y="72" width="46" height="54" style="fill: color-mix(in oklab, var(--canvas-fg) 22%, var(--canvas-bg));"></rect>\n <text x="33" y="62" text-anchor="middle" font-size="13" font-weight="700" style="fill: var(--canvas-fg); font-family: system-ui, sans-serif;">6</text>\n <text x="33" y="142" text-anchor="middle" font-size="11" style="fill: var(--canvas-muted); font-family: system-ui, sans-serif;">S1</text>\n <rect x="76" y="45" width="46" height="81" style="fill: color-mix(in oklab, var(--canvas-fg) 22%, var(--canvas-bg));"></rect>\n <text x="99" y="35" text-anchor="middle" font-size="13" font-weight="700" style="fill: var(--canvas-fg); font-family: system-ui, sans-serif;">9</text>\n <text x="99" y="142" text-anchor="middle" font-size="11" style="fill: var(--canvas-muted); font-family: system-ui, sans-serif;">S2</text>\n <rect x="142" y="54" width="46" height="72" style="fill: color-mix(in oklab, var(--canvas-fg) 22%, var(--canvas-bg));"></rect>\n <text x="165" y="44" text-anchor="middle" font-size="13" font-weight="700" style="fill: var(--canvas-fg); font-family: system-ui, sans-serif;">8</text>\n <text x="165" y="142" text-anchor="middle" font-size="11" style="fill: var(--canvas-muted); font-family: system-ui, sans-serif;">S3</text>\n <rect x="208" y="27" width="46" height="99" style="fill: color-mix(in oklab, var(--canvas-fg) 22%, var(--canvas-bg));"></rect>\n <text x="231" y="17" text-anchor="middle" font-size="13" font-weight="700" style="fill: var(--canvas-fg); font-family: system-ui, sans-serif;">11</text>\n <text x="231" y="142" text-anchor="middle" font-size="11" style="fill: var(--canvas-muted); font-family: system-ui, sans-serif;">S4</text>\n <rect x="274" y="99" width="46" height="27" style="fill: var(--canvas-accent);"></rect>\n <text x="297" y="89" text-anchor="middle" font-size="13" font-weight="700" style="fill: var(--canvas-accent); font-family: system-ui, sans-serif;">3</text>\n <text x="297" y="142" text-anchor="middle" font-size="11" style="fill: var(--canvas-muted); font-family: system-ui, sans-serif;">S5</text>\n <rect x="340" y="90" width="46" height="36" style="fill: color-mix(in oklab, var(--canvas-fg) 22%, var(--canvas-bg));"></rect>\n <text x="363" y="80" text-anchor="middle" font-size="13" font-weight="700" style="fill: var(--canvas-fg); font-family: system-ui, sans-serif;">4</text>\n <text x="363" y="142" text-anchor="middle" font-size="11" style="fill: var(--canvas-muted); font-family: system-ui, sans-serif;">S6</text>\n </svg>\n <div style="font-size: 13px; line-height: 1.55; color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg)); margin-top: 20px; border-top: 1px solid var(--canvas-hairline, var(--canvas-border)); padding-top: 12px;">Sprint 5 is when the migration landed in the critical path.</div>\n </div>\n\n <div style="background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); padding: 22px 24px 20px;">\n <div style="font-size: 10px; letter-spacing: 0.12em; font-weight: 600; color: var(--canvas-muted); margin-bottom: 14px;">THE SIX THAT SLIPPED</div>\n <div style="background: var(--canvas-surface-2, var(--canvas-bg)); border: 1px solid var(--canvas-hairline, var(--canvas-border));">\n <div style="display: grid; grid-template-columns: 1fr 88px 74px; gap: 12px; padding: 8px 14px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 10px; letter-spacing: 0.1em; font-weight: 600; color: var(--canvas-muted);"><span>COMMITMENT</span><span>BLOCKED BY</span><span style="text-align: right;">DAYS</span></div>\n <div style="display: grid; grid-template-columns: 1fr 88px 74px; gap: 12px; padding: 10px 14px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 13px; align-items: baseline;"><span style="font-weight: 600;">Billing export</span><span style="color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg));">migration</span><span style="text-align: right; font-variant-numeric: tabular-nums;">21</span></div>\n <div style="display: grid; grid-template-columns: 1fr 88px 74px; gap: 12px; padding: 10px 14px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 13px; align-items: baseline;"><span style="font-weight: 600;">Audit trail</span><span style="color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg));">migration</span><span style="text-align: right; font-variant-numeric: tabular-nums;">18</span></div>\n <div style="display: grid; grid-template-columns: 1fr 88px 74px; gap: 12px; padding: 10px 14px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 13px; align-items: baseline;"><span style="font-weight: 600;">SSO for teams</span><span style="color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg));">migration</span><span style="text-align: right; font-variant-numeric: tabular-nums;">14</span></div>\n <div style="display: grid; grid-template-columns: 1fr 88px 74px; gap: 12px; padding: 10px 14px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 13px; align-items: baseline;"><span style="font-weight: 600;">Usage alerts</span><span style="color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg));">migration</span><span style="text-align: right; font-variant-numeric: tabular-nums;">11</span></div>\n <div style="display: grid; grid-template-columns: 1fr 88px 74px; gap: 12px; padding: 10px 14px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 13px; align-items: baseline;"><span style="font-weight: 600;">Retention job</span><span style="color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg));">migration</span><span style="text-align: right; font-variant-numeric: tabular-nums;">9</span></div>\n <div style="display: grid; grid-template-columns: 1fr 88px 74px; gap: 12px; padding: 10px 14px; font-size: 13px; align-items: baseline;"><span style="font-weight: 600;">Region failover</span><span style="color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg));">migration</span><span style="text-align: right; font-variant-numeric: tabular-nums;">6</span></div>\n </div>\n <div style="font-size: 13px; line-height: 1.55; color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg)); margin-top: 16px;">One dependency, 79 days of accumulated delay.</div>\n </div>\n </div>\n\n <div style="border-top: 1px solid var(--canvas-hairline, var(--canvas-border)); margin-top: 26px; padding-top: 14px; display: flex; align-items: baseline; justify-content: space-between; gap: 24px; font-size: 11px; letter-spacing: 0.06em; color: var(--canvas-muted);">\n <span>PLATFORM &nbsp;&middot;&nbsp; QUARTERLY REVIEW</span>\n <span>Source: delivery board, sprints 1&ndash;6</span>\n </div>\n </div>\n```\n\n## The same design, dark\n\nNothing about the markup changes — only these nine values on the wrapper. A palette swap\nis not a different design, and if you find yourself restructuring for dark, something in\nthe structure was carrying the colour\'s weight.\n\n```\n--canvas-bg: #17181C; --canvas-fg: #ECEDF1; --canvas-muted: #9A9CA6; --canvas-accent: #FB7B4B; --canvas-card: #22242A; --canvas-border: #31333B; --canvas-surface-1: #22242A; --canvas-surface-2: #1B1D22; --canvas-hairline: #31333B\n```',
29
+ });
30
+ export const canvasExemplarAppScreenSkill = defineSkill({
31
+ name: 'canvas-exemplar-app-screen',
32
+ description: 'A worked app-screen mock to read before drawing a UI — four surfaces, one active ' +
33
+ 'state, a finding rather than a page name.',
34
+ tags: ['visual', 'canvas', 'design', 'exemplar'],
35
+ prompt: 'A worked **app-screen mock** — one screen of an application, not a wireframe.\n\n⚠️ This is a reference to ADAPT, not a template to fill in. Copying the words produces\na canvas about someone else\'s quarter. What to carry over is the SHAPE: one dominant\nelement, a claim rather than a label as the headline, the accent used once, figures that\nreconcile, density that varies.\n\n⚠️ It is already sandbox-legal — system fonts, inline styles, theme tokens, an explicit\nbackground, no network. Keep the outer div that declares the tokens: it is what lets the\ncanvas render on any theme, and the var(--canvas-surface-1, var(--canvas-card)) fallbacks\nkeep it working on a host that has not shipped the elevation ramp.\n\n⚠️ Note what it does NOT draw: no traffic lights, no browser tab bar, no URL field.\nWindow chrome costs vertical space and dates the mock; the app\'s own header is the frame.\n\n```html\n<div style="--canvas-bg: #E9EAEE; --canvas-fg: #1B1C1F; --canvas-muted: #63656D; --canvas-accent: #C2410C; --canvas-card: #FFFFFF; --canvas-border: #D5D7DE; --canvas-surface-1: #FFFFFF; --canvas-surface-2: #F2F3F6; --canvas-hairline: #DCDEE4; width: 1000px; background: var(--canvas-surface-1, var(--canvas-card)); font-family: system-ui, -apple-system, \'Segoe UI\', Roboto, sans-serif; color: var(--canvas-fg); display: grid; grid-template-columns: 232px 1fr;">\n\n <div style="border-right: 1px solid var(--canvas-hairline, var(--canvas-border)); display: flex; flex-direction: column; min-height: 640px;">\n <div style="padding: 18px 18px 16px; display: flex; align-items: center; gap: 10px;">\n <span style="width: 28px; height: 28px; background: var(--canvas-fg); color: var(--canvas-surface-1, var(--canvas-card)); font-size: 11px; font-weight: 700; letter-spacing: 0.02em; display: flex; align-items: center; justify-content: center; flex: none;">CO</span>\n <span style="font-size: 15px; font-weight: 700; letter-spacing: -0.01em;">Cohort</span>\n </div>\n\n <div style="padding: 6px 10px 0; display: flex; flex-direction: column; gap: 1px;">\n <div style="display: flex; align-items: center; gap: 10px; padding: 10px 12px; background: color-mix(in oklab, var(--canvas-accent) 10%, var(--canvas-surface-1, var(--canvas-card))); box-shadow: inset 2px 0 0 var(--canvas-accent); font-size: 13.5px; font-weight: 600;">\n <span style="font-size: 13px; flex: none; color: var(--canvas-accent);">&#9632;</span>Overview\n </div>\n <div style="display: flex; align-items: center; gap: 10px; padding: 10px 12px; font-size: 13.5px; color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">\n <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="flex: none; color: var(--canvas-muted);"><circle cx="6" cy="6" r="2.5"></circle><path d="M2 13.5c.8-2.2 2.2-3.3 4-3.3s3.2 1.1 4 3.3"></path><path d="M11 4.2a2.5 2.5 0 0 1 0 4.6"></path></svg>Applicants\n <span style="margin-left: auto; font-size: 12px; color: var(--canvas-muted); font-variant-numeric: tabular-nums;">248</span>\n </div>\n <div style="display: flex; align-items: center; gap: 10px; padding: 10px 12px; font-size: 13.5px; color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">\n <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="flex: none; color: var(--canvas-muted);"><rect x="2.5" y="3.5" width="11" height="10" rx="1"></rect><path d="M2.5 6.5h11M5.5 2v2.5M10.5 2v2.5"></path></svg>Interviews\n <span style="margin-left: auto; font-size: 12px; color: var(--canvas-muted); font-variant-numeric: tabular-nums;">67</span>\n </div>\n <div style="display: flex; align-items: center; gap: 10px; padding: 10px 12px; font-size: 13.5px; color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">\n <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="flex: none; color: var(--canvas-muted);"><path d="M9.5 2H4.5A1.5 1.5 0 0 0 3 3.5v9A1.5 1.5 0 0 0 4.5 14h7a1.5 1.5 0 0 0 1.5-1.5V5.5z"></path><path d="M9.5 2v3.5H13"></path></svg>Documents\n </div>\n <div style="display: flex; align-items: center; gap: 10px; padding: 10px 12px; font-size: 13.5px; color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">\n <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="flex: none; color: var(--canvas-muted);"><path d="M2 14h12"></path><path d="M4.5 14V9M8 14V4.5M11.5 14V7"></path></svg>Reports\n </div>\n <div style="display: flex; align-items: center; gap: 10px; padding: 10px 12px; font-size: 13.5px; color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">\n <svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="flex: none; color: var(--canvas-muted);"><path d="M2 5.5A1.5 1.5 0 0 1 3.5 4h2.2l1.3 1.6h5.5A1.5 1.5 0 0 1 14 7.1v5.4A1.5 1.5 0 0 1 12.5 14h-9A1.5 1.5 0 0 1 2 12.5z"></path></svg>Programmes\n <span style="margin-left: auto; font-size: 12px; color: var(--canvas-muted); font-variant-numeric: tabular-nums;">12</span>\n </div>\n </div>\n\n <div style="margin-top: auto; border-top: 1px solid var(--canvas-hairline, var(--canvas-border)); padding: 12px 22px; font-size: 13.5px; color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">Settings</div>\n <div style="border-top: 1px solid var(--canvas-hairline, var(--canvas-border)); padding: 14px 18px; display: flex; align-items: center; gap: 10px;">\n <span style="width: 30px; height: 30px; background: var(--canvas-surface-2, var(--canvas-bg)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 11px; font-weight: 600; color: var(--canvas-muted); display: flex; align-items: center; justify-content: center; flex: none;">RM</span>\n <span>\n <span style="display: block; font-size: 13px; font-weight: 600;">Rita Marchetti</span>\n <span style="display: block; font-size: 11px; color: var(--canvas-muted);">Admissions lead</span>\n </span>\n </div>\n </div>\n\n <div style="display: flex; flex-direction: column;">\n <div style="padding: 20px 28px 18px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); display: flex; align-items: flex-end; justify-content: space-between; gap: 24px;">\n <div>\n <div style="font-size: 11px; letter-spacing: 0.06em; color: var(--canvas-muted); margin-bottom: 6px;">Autumn 2027 &nbsp;/&nbsp; Admissions</div>\n <div style="font-size: 26px; font-weight: 700; letter-spacing: -0.022em;">Overview</div>\n </div>\n <div style="display: flex; align-items: center; gap: 8px; flex: none;">\n <span style="font-size: 13px; padding: 8px 14px; border: 1px solid var(--canvas-hairline, var(--canvas-border)); color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">Export</span>\n <span style="font-size: 13px; font-weight: 600; padding: 9px 15px; background: var(--canvas-fg); color: var(--canvas-surface-1, var(--canvas-card));">New applicant</span>\n </div>\n </div>\n <div style="background: var(--canvas-surface-2, var(--canvas-bg)); padding: 20px 28px 26px; flex: 1; display: flex; flex-direction: column; gap: 16px;">\n\n <div style="display: grid; grid-template-columns: 1.45fr 1fr; gap: 16px;">\n <div style="background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); padding: 22px 24px;">\n <div style="display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-bottom: 14px;">\n <div style="font-size: 10px; letter-spacing: 0.12em; font-weight: 600; color: var(--canvas-accent);">THIS WEEK</div>\n <div style="font-size: 11px; color: var(--canvas-muted);">updated 4 min ago</div>\n </div>\n <div style="font-size: 27px; line-height: 1.2; font-weight: 700; letter-spacing: -0.022em; margin-bottom: 10px; max-width: 20em;">Interviews are keeping pace. Document review is not.</div>\n <p style="font-size: 13.5px; line-height: 1.65; color: color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg)); margin: 0 0 16px;">248 applicants across 12 programmes, and 67 have reached interview. The queue sits at document review, where 41 files have waited more than five days &mdash; two programmes account for 30 of them.</p>\n <span style="display: inline-block; font-size: 12.5px; font-weight: 600; color: var(--canvas-accent); border-bottom: 1px solid var(--canvas-accent); padding-bottom: 1px;">Review the 41 waiting files</span>\n </div>\n\n <div style="background: var(--canvas-fg); color: var(--canvas-surface-1, var(--canvas-card)); padding: 20px 22px; display: flex; flex-direction: column; gap: 14px;">\n <div style="display: flex; align-items: baseline; justify-content: space-between; gap: 12px;">\n <span style="font-size: 10px; letter-spacing: 0.12em; font-weight: 600;">PIPELINE</span>\n <span style="font-size: 10px; letter-spacing: 0.06em; opacity: 0.62;">stage by stage</span>\n </div>\n <div style="display: flex; flex-direction: column; gap: 11px;">\n <div>\n <div style="display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 12px;"><span style="opacity: 0.75;">Applied</span><span style="font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums;">248</span></div>\n <div style="height: 4px; background: rgba(255,255,255,0.16); margin-top: 5px;"><div style="height: 4px; width: 100%; background: rgba(255,255,255,0.62);"></div></div>\n </div>\n <div>\n <div style="display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 12px;"><span style="opacity: 0.75;">Documents in</span><span style="font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums;">193</span></div>\n <div style="height: 4px; background: rgba(255,255,255,0.16); margin-top: 5px;"><div style="height: 4px; width: 78%; background: rgba(255,255,255,0.62);"></div></div>\n </div>\n <div>\n <div style="display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 12px;"><span style="opacity: 0.75;">Reviewed</span><span style="font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums;">152</span></div>\n <div style="height: 4px; background: rgba(255,255,255,0.16); margin-top: 5px;"><div style="height: 4px; width: 61%; background: var(--canvas-accent);"></div></div>\n </div>\n <div>\n <div style="display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 12px;"><span style="opacity: 0.75;">Interviewed</span><span style="font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums;">67</span></div>\n <div style="height: 4px; background: rgba(255,255,255,0.16); margin-top: 5px;"><div style="height: 4px; width: 27%; background: rgba(255,255,255,0.62);"></div></div>\n </div>\n </div>\n <div style="margin-top: auto; border-top: 1px solid rgba(255,255,255,0.18); padding-top: 11px; font-size: 12px; opacity: 0.82;">41 files stalled at review</div>\n </div>\n </div>\n\n <div style="display: flex; align-items: center; gap: 8px;">\n <span style="font-size: 10px; letter-spacing: 0.12em; font-weight: 600; color: var(--canvas-muted); margin-right: 4px;">FACULTY</span>\n <span style="font-size: 12.5px; font-weight: 600; padding: 6px 12px; background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-fg);">All <span style="color: var(--canvas-muted); font-weight: 400;">12</span></span>\n <span style="font-size: 12.5px; padding: 6px 12px; background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">Design <span style="color: var(--canvas-muted);">4</span></span>\n <span style="font-size: 12.5px; padding: 6px 12px; background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">Business <span style="color: var(--canvas-muted);">5</span></span>\n <span style="font-size: 12.5px; padding: 6px 12px; background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border)); color: color-mix(in oklab, var(--canvas-fg) 82%, var(--canvas-bg));">Engineering <span style="color: var(--canvas-muted);">3</span></span>\n </div>\n\n <div style="background: var(--canvas-surface-1, var(--canvas-card)); border: 1px solid var(--canvas-hairline, var(--canvas-border));">\n <div style="padding: 13px 18px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); display: flex; align-items: baseline; justify-content: space-between; gap: 16px;">\n <span style="font-size: 14px; font-weight: 600;">Programmes needing attention</span>\n <span style="font-size: 12px; color: var(--canvas-muted);">4 of 12</span>\n </div>\n <div style="display: grid; grid-template-columns: 1.6fr 120px 84px 84px 92px; gap: 14px; padding: 9px 18px; background: var(--canvas-surface-2, var(--canvas-bg)); border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 10px; letter-spacing: 0.1em; font-weight: 600; color: var(--canvas-muted);">\n <span>PROGRAMME</span><span>REVIEW</span><span style="text-align: right;">APPLIED</span><span style="text-align: right;">OFFERS</span><span style="text-align: right;">WAITING</span>\n </div>\n <div style="display: grid; grid-template-columns: 1.6fr 120px 84px 84px 92px; gap: 14px; padding: 12px 18px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 13px; align-items: center;">\n <span style="font-weight: 600;">MSc Product Design</span>\n <span style="height: 5px; background: var(--canvas-surface-2, var(--canvas-bg));"><span style="display: block; height: 5px; width: 38%; background: var(--canvas-accent);"></span></span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">31</span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">4</span>\n <span style="text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; color: var(--canvas-accent);">17</span>\n </div>\n <div style="display: grid; grid-template-columns: 1.6fr 120px 84px 84px 92px; gap: 14px; padding: 12px 18px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 13px; align-items: center;">\n <span style="font-weight: 600;">MBA Sustainable Business</span>\n <span style="height: 5px; background: var(--canvas-surface-2, var(--canvas-bg));"><span style="display: block; height: 5px; width: 46%; background: var(--canvas-accent);"></span></span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">28</span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">6</span>\n <span style="text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; color: var(--canvas-accent);">13</span>\n </div>\n <div style="display: grid; grid-template-columns: 1.6fr 120px 84px 84px 92px; gap: 14px; padding: 12px 18px; border-bottom: 1px solid var(--canvas-hairline, var(--canvas-border)); font-size: 13px; align-items: center;">\n <span style="font-weight: 600;">MA Curation</span>\n <span style="height: 5px; background: var(--canvas-surface-2, var(--canvas-bg));"><span style="display: block; height: 5px; width: 71%; background: color-mix(in oklab, var(--canvas-fg) 32%, var(--canvas-bg));"></span></span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">19</span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">5</span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">7</span>\n </div>\n <div style="display: grid; grid-template-columns: 1.6fr 120px 84px 84px 92px; gap: 14px; padding: 12px 18px; font-size: 13px; align-items: center;">\n <span style="font-weight: 600;">MEng Robotics</span>\n <span style="height: 5px; background: var(--canvas-surface-2, var(--canvas-bg));"><span style="display: block; height: 5px; width: 83%; background: color-mix(in oklab, var(--canvas-fg) 32%, var(--canvas-bg));"></span></span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">24</span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">9</span>\n <span style="text-align: right; font-variant-numeric: tabular-nums;">4</span>\n </div>\n </div>\n </div>\n </div>\n </div>\n```',
36
+ });
@@ -0,0 +1,6 @@
1
+ /** Identical for every icon; the agent emits this and drops one body inside. */
2
+ export declare const ICON_WRAPPER: string;
3
+ export declare const ICON_BODIES: Record<string, string>;
4
+ /** The one deliberate exception to stroke-only. */
5
+ export declare const ACTIVE_MARKER = "<rect x=\"4\" y=\"4\" width=\"8\" height=\"8\" fill=\"currentColor\" stroke=\"none\"/>";
6
+ export declare const canvasIconsSkill: import("@compilr-dev/agents").Skill;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * The twenty-shape icon set a canvas may use.
3
+ *
4
+ * ⚠️ WHY A FIXED SET. `infographic`'s "icons & shapes" section said "inline SVG,
5
+ * consistent stroke width" and gave the agent nothing to be consistent WITH, so every
6
+ * canvas invented its own paths — and where invention felt too hard, emoji arrived
7
+ * instead. The counter-example in the design handoff has a bar chart, a clock, a snake
8
+ * and an hourglass as its stat-tile icons.
9
+ *
10
+ * ⚠️ SMALL ON PURPOSE. One wrapper plus twenty bodies is a few hundred tokens; twenty
11
+ * complete SVG elements would be several thousand, which is the difference between a set
12
+ * an agent reads and one it skips.
13
+ */
14
+ import { defineSkill } from '@compilr-dev/agents';
15
+ /** Identical for every icon; the agent emits this and drops one body inside. */
16
+ export const ICON_WRAPPER = '<svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" ' +
17
+ 'stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">BODY</svg>';
18
+ export const ICON_BODIES = {
19
+ grid: '<rect x="2" y="2" width="12" height="12"/><path d="M8 2v12M2 8h12"/>',
20
+ list: '<path d="M5.5 4H14M5.5 8H14M5.5 12H14M2.5 4h.01M2.5 8h.01M2.5 12h.01"/>',
21
+ users: '<circle cx="6" cy="6" r="2.5"/><path d="M2 13.5c.8-2.2 2.2-3.3 4-3.3s3.2 1.1 4 3.3"/><path d="M11 4.2a2.5 2.5 0 0 1 0 4.6"/>',
22
+ document: '<path d="M9.5 2H4.5A1.5 1.5 0 0 0 3 3.5v9A1.5 1.5 0 0 0 4.5 14h7a1.5 1.5 0 0 0 1.5-1.5V5.5z"/><path d="M9.5 2v3.5H13"/>',
23
+ folder: '<path d="M2 5.5A1.5 1.5 0 0 1 3.5 4h2.2l1.3 1.6h5.5A1.5 1.5 0 0 1 14 7.1v5.4A1.5 1.5 0 0 1 12.5 14h-9A1.5 1.5 0 0 1 2 12.5z"/>',
24
+ chart: '<path d="M2 14h12"/><path d="M4.5 14V9M8 14V4.5M11.5 14V7"/>',
25
+ calendar: '<rect x="2.5" y="3.5" width="11" height="10" rx="1"/><path d="M2.5 6.5h11M5.5 2v2.5M10.5 2v2.5"/>',
26
+ clock: '<circle cx="8" cy="8" r="6"/><path d="M8 4.5V8l2.5 1.5"/>',
27
+ search: '<circle cx="7" cy="7" r="4.5"/><path d="M10.5 10.5 14 14"/>',
28
+ settings: '<path d="M2 5h6.5M13 5h1M2 11h1M7.5 11H14"/><circle cx="10.5" cy="5" r="1.6"/><circle cx="5" cy="11" r="1.6"/>',
29
+ tag: '<path d="M8.6 2H13a1 1 0 0 1 1 1v4.4l-6.3 6.3a1 1 0 0 1-1.4 0L2.3 9.7a1 1 0 0 1 0-1.4z"/><path d="M11 5h.01"/>',
30
+ 'add-card': '<rect x="2" y="2.5" width="12" height="11" rx="1"/><path d="M8 6v5M5.5 8.5h5"/>',
31
+ plus: '<path d="M8 3v10M3 8h10"/>',
32
+ check: '<path d="M3 8.5 6.5 12 13 4.5"/>',
33
+ close: '<path d="M4 4l8 8M12 4l-8 8"/>',
34
+ alert: '<path d="M8 2.5 14.5 13.5h-13z"/><path d="M8 6.5v3M8 11.6h.01"/>',
35
+ chevron: '<path d="M6 3.5 10.5 8 6 12.5"/>',
36
+ external: '<path d="M6.5 9.5 14 2M14 2H9.5M14 2v4.5"/><path d="M12 10v2.5A1.5 1.5 0 0 1 10.5 14h-7A1.5 1.5 0 0 1 2 12.5v-7A1.5 1.5 0 0 1 3.5 4H6"/>',
37
+ 'arrow-up': '<path d="M8 13V3M4 7l4-4 4 4"/>',
38
+ 'arrow-down': '<path d="M8 3v10M4 9l4 4 4-4"/>',
39
+ };
40
+ /** The one deliberate exception to stroke-only. */
41
+ export const ACTIVE_MARKER = '<rect x="4" y="4" width="8" height="8" fill="currentColor" stroke="none"/>';
42
+ const table = Object.entries(ICON_BODIES)
43
+ .map(([name, body]) => `| \`${name}\` | \`${body}\` |`)
44
+ .join('\n');
45
+ export const canvasIconsSkill = defineSkill({
46
+ name: 'canvas-icons',
47
+ description: 'The twenty-shape icon set for canvases — one SVG wrapper, twenty bodies, plus the ' +
48
+ 'rules that keep a set of icons from becoming decoration.',
49
+ tags: ['visual', 'canvas', 'design', 'icons'],
50
+ prompt: `Twenty shapes, one stroke weight. Use these rather than inventing a path — and
51
+ rather than an emoji, which is what a canvas reaches for when drawing feels hard.
52
+
53
+ ## The wrapper — identical for every icon
54
+
55
+ \`\`\`html
56
+ ${ICON_WRAPPER}
57
+ \`\`\`
58
+
59
+ ## The bodies
60
+
61
+ | name | body |
62
+ |---|---|
63
+ ${table}
64
+
65
+ ## Rules
66
+
67
+ 1. **\`currentColor\`, never a token.** Colour the parent; the icon inherits.
68
+ \`stroke="var(--canvas-accent)"\` on an icon is how a cluster drifts out of the palette.
69
+ 2. **16 grid, not 24.** Stroke 1.5 on a 16 grid holds detail down to 14px, where nav rows
70
+ and table cells live. A 24 grid scaled to 14 puts hairlines below one device pixel.
71
+ 3. **14px is the floor.** Below it, drop the icon — do not shrink it.
72
+ 4. **Scale with the \`width\`/\`height\` attributes only.** Never edit the geometry.
73
+ 5. **Stroke only, \`fill="none"\`.** One exception, the active-nav marker, deliberately the
74
+ only solid mark on a screen: \`${ACTIVE_MARKER}\`
75
+ 6. **Twenty is a budget, not a start.** If a canvas needs a twenty-first, use a word. A
76
+ text label always beats a guessed glyph.
77
+ 7. **One icon per row at most** — leading, or none. Icons on both sides of a row, or one
78
+ per stat tile, is decoration; that is the slot emoji creep into.
79
+
80
+ ## Deltas
81
+
82
+ \`arrow-up\` and \`arrow-down\` carry direction beside a figure. Both inherit
83
+ \`currentColor\`, so the arrow can never disagree with its number.`,
84
+ });
@@ -35,19 +35,56 @@ export const canvasSkill = defineSkill({
35
35
 
36
36
  ## STEPS
37
37
 
38
- 1. **Collect the essentials from the user FIRST — this is the standard approach.** A canvas is an expensive authoring pass; building it on guessed requirements wastes that pass and lands wide of what the user wanted. So before you author, use **\`ask_user\`** (batch several questions into ONE call) to gather what the canvas needs:
39
- - **Purpose & audience** — what is it for, who reads it?
40
- - **Canvas type** — infographic, carousel, or board? (Offer the choice unless obvious.)
41
- - **Key content / data** — the actual points, numbers, sections, or slides to include. Don't invent data.
42
- - **Style / brand** — theme-matched (default), a specific palette/brand, or a named **aesthetic**? For infographics & carousels you can offer a look from the \`canvas-styles\` skill (skeuomorphic, glass, brutalist, editorial, retro) as part of THIS same question — don't default every canvas to the flat theme look.
43
- For open design directions (which layout, which visual approach), present concrete options with **\`propose_alternatives\`** instead of guessing.
44
- **Skip questions the user already answered**, and if they gave full detail or explicitly said "just make it / surprise me / your call", go straight to authoring. Keep it to ONE focused round — a short question batch, never a long interview — then build.
38
+ ⚠️ **Two passes, not one.** Most weak canvases are weak because layout and decoration got
39
+ decided in the same breath, and the decoration ends up compensating for a structure that
40
+ never worked. Structure first, with the real content; skin it second, from a recipe.
45
41
 
46
- 2. **Build the canvas in small steps — never one giant tool call.** A canvas is HTML/SVG you emit as tool arguments; a single very large \`canvas_write\` can overrun the output limit, get cut off mid-arguments, and fail. So author it incrementally, and emit each tool call immediately with NO prose preamble (narration competes with the HTML for the same output budget):
42
+ 1. **Ask purpose, audience and content — never taste.** Use **\`ask_user\`** (batch into ONE
43
+ call):
44
+ - **Purpose & audience** — what is it for, who reads it?
45
+ - **Key content / data** — the actual points, numbers, sections or slides. Don't invent
46
+ data; if you need a figure you were not given, ask for it.
47
+
48
+ ⚠️ **Do NOT ask which style or aesthetic they want.** "Brutalist or editorial?" asks
49
+ someone to imagine two things they have never seen, in a vocabulary they did not choose,
50
+ before the canvas exists — and then commits you to whatever comes back. Purpose,
51
+ audience and content are answerable from what the user already knows. Style is
52
+ answerable from what they can SEE, which is step 8.
53
+
54
+ **Skip the round entirely** when they said "just make it / surprise me / your call" or
55
+ already gave full detail. This round asks LESS than it used to, not more.
56
+
57
+ ⚠️ **IF THE QUESTION ROUND FAILS, SAY SO AND ASK AGAIN — do not author anyway.** An
58
+ \`ask_user\` call can die on the output limit, and the failure looks like nothing: no
59
+ answers arrive and authoring simply continues on invented content. That is how a canvas
60
+ ends up full of placeholder names nobody asked for. If the round did not come back, send
61
+ ONE short retry; if that fails too, tell the user what you need in one line and stop.
62
+ Inventing the brief wastes the whole pass.
63
+
64
+ 2. **Route the type and style from the content**, using the cheatsheet in
65
+ \`canvas_guide("styles")\`. One screen of an app → \`infographic\` + the app-screen style.
66
+ Several options compared → \`board\`. Slides → \`carousel\`. Data or a one-pager →
67
+ \`infographic\` + theme-flat.
68
+
69
+ ⚠️ **REAL IMAGES GO IN AS ASSETS.** If the user supplied photos, logos or screenshots, put
70
+ each one in with \`canvas_asset_add\` (canvas_id, path, ref) and reference it as
71
+ \`<img src="asset:REF" alt="what it shows">\`. Do **not** paste base64 into the markup — it
72
+ would work, and it would bury the document under hundreds of KB that \`canvas_edit\` then has
73
+ to patch around. Do **not** draw a grey placeholder box when a real image was provided:
74
+ that is the single thing that makes a mock unreviewable. \`canvas_asset_list\` shows what is
75
+ stored and what the markup actually uses.
76
+
77
+ 3. **Pass one — structure, with the FINAL content.** No colour decisions, no type scale yet:
78
+ get the real words and real numbers into the real layout. A structure that works with
79
+ placeholder text is not a structure.
80
+
81
+ Build it in small steps — never one giant tool call. A canvas is HTML/SVG you emit as
82
+ tool arguments; a single very large \`canvas_write\` can overrun the output limit, get cut
83
+ off mid-arguments, and fail. Emit each call immediately with NO prose preamble
84
+ (narration competes with the HTML for the same output budget):
47
85
  - **2a. First \`canvas_write\`** (type, title, html) = a COMPACT skeleton: the \`<style>\` block, the overall layout, and just the first section or heading. This is the ONLY way to create a canvas — describing it in chat does nothing.
48
86
  - **2b. Then \`canvas_edit\` (operation=append)** to add each remaining section, one call per section. The preview grows as you go, and every call stays small and reliable. (You can also leave \`<!-- placeholder -->\` markers in 2a and str_replace them.)
49
87
  - Put the whole thing in one \`canvas_write\` ONLY if it is genuinely small (a single stat card / short poster).
50
-
51
88
  HTML constraints for every write:
52
89
  - Do NOT include \`<html>\`, \`<head>\`, \`<meta>\`, \`<!doctype>\`, or your own CSP — the host injects the sandbox and CSP. Just the body content + a \`<style>\` and optional \`<script>\`.
53
90
  - The sandbox is \`allow-scripts\` with NO same-origin and CSP \`default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob:\`. So: inline \`<style>\`/\`<script>\` only, no external fonts/CSS/JS/network, no remote images (use inline SVG or data: URIs).
@@ -56,7 +93,13 @@ export const canvasSkill = defineSkill({
56
93
  - Make it look intentional: a clear grid, strong type scale, generous spacing. Use \`var(--canvas-accent)\` sparingly for emphasis. Prefer inline SVG for shapes/charts/icons (fill/stroke with the theme vars).
57
94
  - For a **carousel**, wrap each slide in \`<section data-sheet>…</section>\` — a natural unit to add one per \`canvas_edit\` append.
58
95
 
59
- 3. **Add a Tweaks controls manifest when it helps** (the \`controls\` argument). Each control is \`{ type, param, label, default, …type config }\` where type ∈ slider | number | toggle | select | color | text.
96
+ 4. **Pass two — skin it.** Read \`canvas_guide\` for your style's recipe and its exemplar
97
+ (\`exemplar-infographic\` or \`exemplar-app-screen\`), then apply the values. This pass is
98
+ mechanical, because the recipe supplies every number — surfaces, type ladder, spacing
99
+ scale, accent budget. Edit in place with \`canvas_edit\`; pass one already put the content
100
+ where it belongs.
101
+
102
+ 5. **Add a Tweaks controls manifest when it helps** (the \`controls\` argument). Each control is \`{ type, param, label, default, …type config }\` where type ∈ slider | number | toggle | select | color | text.
60
103
  - **⚠️ EVERY control you declare MUST be bound in the HTML — a declared-but-unbound control is a dead knob that visibly does nothing when the user drags it. Declaring the control is only half the job; you MUST also reference its param.** Bind each param one of these ways:
61
104
  - CSS custom property (most common): use \`var(--param)\` directly in your styles — e.g. a \`headline\` slider means \`font-size: var(--headline)\` (NOT a literal \`48px\`), an \`accent\` color means \`color: var(--accent)\`. The host sets \`--param\` on the root and updates it live. Include a unit in the CSS if the raw value is a number: \`font-size: calc(var(--headline) * 1px)\`.
62
105
  - Text binding: \`<span data-bind="param"></span>\` — its text is replaced with the value.
@@ -65,13 +108,55 @@ export const canvasSkill = defineSkill({
65
108
  - Give every canvas at least 2–4 sensible controls (accent color, a headline size slider, a toggle to show/hide a section). Seed \`default\` values that MATCH what you authored (a slider default of 48 means the HTML's initial \`var(--headline)\` should read 48).
66
109
  - **Before finishing, re-check: is each control's \`param\` actually referenced in the HTML?** If not, either bind it or drop the control — never ship a dead knob.
67
110
 
68
- 4. **To EDIT an existing canvas, do NOT re-send the whole document.** First call \`canvas_get\` with \`outline=true\` (or a \`startLine\`/\`maxLines\` slice) to find the exact snippet, then \`canvas_edit\` with \`operation=str_replace\` (old_str must be unique — include surrounding context — or set replace_all=true), or append/prepend. Reserve \`canvas_write\` with \`canvas_id\` for a full intentional rewrite.
111
+ 6. **To EDIT an existing canvas, do NOT re-send the whole document.** First call \`canvas_get\` with \`outline=true\` (or a \`startLine\`/\`maxLines\` slice) to find the exact snippet, then \`canvas_edit\` with \`operation=str_replace\` (old_str must be unique — include surrounding context — or set replace_all=true), or append/prepend. Reserve \`canvas_write\` with \`canvas_id\` for a full intentional rewrite.
112
+
113
+ 7. **Validate.** Call \`canvas_validate\` (canvas_id) — a fast static check for content that
114
+ won't render (Mermaid/Markdown/CDN), a board missing its \`data-board\` bounds or with
115
+ nodes off-canvas, sparse output, hardcoded colours that ignore the theme, a card that
116
+ shares its ground's value, text under 4.5:1, too many hues, and dead Tweaks. Fix every
117
+ **error** and address applicable **warnings**.
118
+
119
+ 8. **The gate — screenshot, verdict, fix, screenshot AGAIN.** Call \`canvas_screenshot\`
120
+ (canvas_id), then give all six verdicts explicitly, in this form:
121
+
122
+ \`01 pass · 02 fail → fixed · 03 pass · 04 pass · 05 fail → fixed · 06 pass\`
123
+
124
+ | # | check | the question to ask |
125
+ |---|---|---|
126
+ | 01 | One dominant element | Squint: what do you see FIRST? If the answer is "the whole thing at once", nothing dominates. The largest element should be ≥2x the next and hold the FINDING, not merely the first metric. |
127
+ | 02 | Cards differ from their ground | Cover the borders with your thumb — can you still see the cards? If the layout only exists because of its lines, the ramp is missing. |
128
+ | 03 | Nothing under 4.5:1 | Check every label against the surface it ACTUALLY sits on, not against the page. |
129
+ | 04 | Every value is specific | Do the numbers add up? Could a reader challenge one? A total that does not reconcile with its rows is what makes a canvas feel machine-made. |
130
+ | 05 | Density varies | Blur it until the text goes — is there a composition left? A uniform grid of equal boxes is a form. |
131
+ | 06 | One accent, one family | Count the hues, then count the reasons. More hues than reasons means colour is decoration. |
69
132
 
70
- 5. **Validate before you hand it off.** Once the canvas is composed, call \`canvas_validate\` (canvas_id) — a fast static check that catches content that won't render (Mermaid/Markdown/CDN), a board missing its \`data-board\` bounds or with nodes off-canvas, sparse output, and hardcoded colors that ignore the theme tokens. Fix any **errors** (they render broken) and address applicable **warnings**, then move on.
133
+ ⚠️ **Naming each check is what forces looking at it.** Asked for a summary judgement, a
134
+ model writes "looks good" every time.
71
135
 
72
- 6. **See it before you trust it (if you can).** If you're vision-capable, call \`canvas_screenshot\` (canvas_id) — it renders the canvas and returns the image so you can catch what static checks can't: overflow / cut-off content, unreadable or low-contrast text, cramped or empty areas, broken layout. If something's off, fix it with \`canvas_edit\` and screenshot again. (No-op on text-only models / hosts that can't render.)
136
+ ⚠️ **FAILING IS NORMAL** — most first drafts fail two of these. A failed check is
137
+ something to EDIT, not something to argue with. Do not write a justification.
73
138
 
74
- 7. **After writing, tell the user what you made in one line** and point them at the Tweaks they can adjust. The canvas opens in its own tab.
139
+ ⚠️ **A \`canvas_validate\` WARNING IS A FAILED CHECK, not an opinion to weigh.** If it
140
+ reported too many hues, 06 is a fail; if it reported a card on its ground, 02 is a fail.
141
+ Fix it and re-screenshot. Answering a warning with why the canvas is an exception — "the
142
+ tile colours are deliberately varied", "this is a committed aesthetic" — is the
143
+ justification this step exists to stop, and it is how a draft self-passes six out of six
144
+ while visibly breaking one of them.
145
+
146
+ ⚠️ **The second screenshot is the one that matters**, because it catches a fix that broke
147
+ something else. Cap at two rounds; if something still fails, hand off and say so in one
148
+ clause.
149
+
150
+ Checks 02, 03 and 06 are also computed by \`canvas_validate\`, so on a text-only model run
151
+ the gate on 01, 04 and 05 — they need judgement, not eyes.
152
+
153
+ ⚠️ **Do not report the gate to the user.** The verdicts are working notes.
154
+
155
+ 9. **One line, then offer two alternates as PICTURES.** Say what you made and point at the
156
+ Tweaks. Then use **\`propose_alternatives\`** with **two** further skins of the same
157
+ canvas — never a list of style names. The content is already fixed, so each variant
158
+ costs one \`canvas_write\` and the user picks by looking. This is the aesthetic menu,
159
+ moved to the only point where the choice is cheap for them and informative for you.
75
160
 
76
161
  ## Rules
77
162
  - CALL THE TOOL — don't describe the HTML you "would" write and stop. A canvas only exists once the tool succeeds.
@@ -177,6 +262,18 @@ export const boardSkill = defineSkill({
177
262
  ## SHOW, don't TELL (the #1 rule)
178
263
  A board is a VISUAL surface — its value is that it *depicts* things, not that it holds text. If your board is mostly paragraphs and bullet lists, you've built a text document on a grid and failed the medium. That is what the "propose alternatives" text tool is for — a board must EARN its place by being visual.
179
264
  - **Depict, don't describe.** Exploring website / UI options? DRAW each option as a **wireframe mock** — a browser/window frame with a nav bar, a hero block, and content sections as labeled BOXES — laid side by side so they can be compared at a glance. Do NOT write "Option A: terminal-first hero, CLI-style product list…" as prose; SKETCH it.
265
+ - ⚠️ **LOW FIDELITY MEANS NO COLOUR, NO IMAGERY AND NO TYPE HIERARCHY. IT DOES NOT MEAN
266
+ NO CONTENT.** Grey bars where headings, prices and product names belong are a
267
+ placeholder for THINKING, and they make every layout look equally plausible because
268
+ none of them has to survive real copy. Boxes stay grey **for images only**. Every line
269
+ of text is written out: real nav items, real product names, real prices, a real
270
+ headline. A wireframe with placeholder text has not been designed yet — and the user
271
+ cannot react to it, which is the whole point of showing them one.
272
+ - ⚠️ **This is for COMPARING several options, at low fidelity.** For ONE screen at full
273
+ fidelity — "mock up the admissions dashboard" — use \`infographic\` with the
274
+ **app-screen** style instead (\`canvas_guide("styles")\`), and draw NO window chrome
275
+ there. The frame below earns its place only because several options sitting side by
276
+ side need something to separate them.
180
277
  - **Architecture / flow?** Draw boxes-and-arrows (nodes + inline-SVG connectors), not a written description of the components.
181
278
  - **Mind-map?** A central node with branches and sub-nodes, not an outline.
182
279
  - Text belongs INSIDE the visual as short labels (titles, captions, a few key words) — never as the primary content of a node.
@@ -187,7 +284,7 @@ Build each screen mock as nested boxes so it reads as a *picture of a page*:
187
284
  <div style="position:absolute;left:80px;top:120px;width:520px;background:var(--canvas-card);
188
285
  border:1px solid var(--canvas-border);border-radius:8px;overflow:hidden">
189
286
  <div style="height:28px;background:var(--canvas-bg);display:flex;align-items:center;gap:6px;padding:0 10px">
190
- <span style="width:8px;height:8px;border-radius:50%;background:var(--canvas-muted)"></span> …traffic lights…
287
+ <span style="width:8px;height:8px;border-radius:50%;background:var(--canvas-muted)"></span> …dots that say "this is a window"…
191
288
  </div>
192
289
  <div style="height:44px;border-bottom:1px solid var(--canvas-border);display:flex;align-items:center;
193
290
  justify-content:space-between;padding:0 16px"><b style="color:var(--canvas-fg)">logo</b><span style="color:var(--canvas-muted)">nav · nav · nav</span></div>
@@ -231,8 +328,8 @@ Give each mock a small title above it ("OPTION A — Terminal-first"). Two or th
231
328
  // =============================================================================
232
329
  export const canvasStylesSkill = defineSkill({
233
330
  name: 'canvas-styles',
234
- description: 'The visual STYLE CATALOGUE for canvases — six named aesthetics (theme-flat, skeuomorphic, ' +
235
- 'glassmorphism, brutalist, editorial/print, retro/synthwave) each with a concrete, sandbox-safe, ' +
331
+ description: 'The visual STYLE CATALOGUE for canvases — seven named aesthetics (theme-flat, skeuomorphic, ' +
332
+ 'glassmorphism, brutalist, editorial/print, retro/synthwave, app-screen) each with a concrete, sandbox-safe, ' +
236
333
  'export-safe CSS recipe. Consult when picking or offering a look beyond the default theme-matched flat style.',
237
334
  tags: ['visual', 'canvas', 'style', 'aesthetic', 'design'],
238
335
  prompt: `The STYLE CATALOGUE for canvases — pick a coherent visual aesthetic instead of defaulting to the flat theme look. Design guidance; author with the canvas tools (see the \`canvas\` skill for mechanics). Applies to **infographic** and **carousel** (a board is a diagram surface — keep it theme-flat unless asked).
@@ -246,14 +343,59 @@ export const canvasStylesSkill = defineSkill({
246
343
  - **EXPORT-SAFE background.** Always set an EXPLICIT background (never \`transparent\`) on the outer wrapper (and each \`<section data-sheet>\`). A look that leans on the page background breaks on the WHITE PDF export.
247
344
  - **\`backdrop-filter\` is fragile.** It renders on screen and in \`canvas_screenshot\`, but PDF export MAY drop it — so for glass, give every panel a semi-opaque SOLID fallback fill too, so it still reads without the blur.
248
345
  - **You MAY override the theme vars** for a committed aesthetic (declare your own palette in \`<style>\`), but keep text/background contrast strong. For "theme-matched", use \`var(--canvas-*)\` as-is. Either way, set the bg explicitly.
346
+ - ⚠️ **This licenses a PALETTE, not a free hand with hue.** Committing to an aesthetic
347
+ changes which colours you use; it does not raise the budget. One accent, three uses,
348
+ greys for everything else still applies — a canvas that answers the hue-count warning
349
+ with "the tile colours are deliberately varied" has used the permission to excuse the
350
+ exact thing the rule forbids. Colour encodes categories a reader must tell apart, or
351
+ it is decoration.
352
+ - ⚠️ **Check muted against the page GROUND, not the card.** A muted grey that passes on a
353
+ white card can fail on the ground two steps below it. Test every token against the
354
+ darkest surface it will actually sit on.
249
355
  - Keep Tweaks working: still expose 2–4 bound controls (accent, a size slider, a toggle) even inside a strong style.
250
356
 
251
357
  ## The six styles
252
358
 
253
359
  ### 1. theme-flat — DEFAULT
254
360
  Clean, modern, theme-driven. Reach for it for data, dashboards, and anything "serious/neutral".
255
- - Font: \`system-ui, -apple-system, "Segoe UI", Roboto, sans-serif\`. Palette: the \`var(--canvas-*)\` tokens as-is.
256
- - Flat surfaces, thin \`1px var(--canvas-border)\` lines, subtle or no shadow, radius 0–8px, strong type scale, generous whitespace, \`var(--canvas-accent)\` used sparingly.
361
+
362
+ ⚠️ This recipe is LITERAL VALUES, not adjectives. "Generous whitespace" and "used
363
+ sparingly" are what the old entry said, and they produced a canvas that followed every
364
+ instruction and still looked machine-made. Read \`canvas_guide("exemplar-infographic")\`
365
+ for a worked example — it is worth more than everything below.
366
+
367
+ **Surfaces — three, never two.** Radius **0**, and **no shadows at all**: the ramp is the
368
+ depth.
369
+ - page ground \`var(--canvas-bg)\` · raised card \`var(--canvas-surface-1, var(--canvas-card))\` · inset (tables, code) \`var(--canvas-surface-2, var(--canvas-card))\`
370
+ - hairlines \`1px solid var(--canvas-hairline, var(--canvas-border))\`
371
+ - A card that shares its ground's value is a rectangle of text. If you ever find yourself
372
+ relying on the border to show where a card is, the ramp is missing.
373
+
374
+ **Type — one family, \`system-ui, -apple-system, "Segoe UI", Roboto, sans-serif\`.**
375
+ - hero figure **72–88px / 700 / -0.035em** · standfirst **20px / 600** · stat **34px / 700**
376
+ · body **14px / 1.65** · label **10–11px / 600 / 0.12em / uppercase**
377
+ - \`font-variant-numeric: tabular-nums\` on **every** figure, or columns of numbers jitter.
378
+
379
+ **Spacing — an 8px scale.** Gaps 16 · card padding 18–32 · outer padding 40.
380
+
381
+ **The canvas fills its frame.** The root takes \`width: 100%\` with an inner \`max-width\` for
382
+ the measure — never a fixed outer width. The viewer is not a fixed size, and a canvas that
383
+ leaves a dead strip down one side looks broken regardless of what is inside it.
384
+
385
+ **Grid — asymmetric tracks**, e.g. \`grid-template-columns: 1.35fr 1fr\`. Equal columns make
386
+ a canvas read as a form.
387
+
388
+ **Accent — three times maximum**, and they are: one kicker, one delta, one bar. Everything
389
+ else is greys.
390
+
391
+ **Dominance is geometric.** The hero figure is ~2.5x the next largest type, in a card ~1.35x
392
+ its neighbour. Bolder is not bigger.
393
+
394
+ **Two derived colours** the token set does not carry, and which get invented badly:
395
+ - body copy \`color-mix(in oklab, var(--canvas-fg) 78%, var(--canvas-bg))\`
396
+ - chart neutral \`color-mix(in oklab, var(--canvas-fg) 22%, var(--canvas-bg))\`
397
+
398
+ Both work in either polarity, so they need no host support.
257
399
 
258
400
  ### 2. skeuomorphic — tactile, physical
259
401
  Soft, real-material surfaces — great for device/app-UI mockups and "premium product" one-pagers.
@@ -287,9 +429,54 @@ Hype/launch/gaming/music. Dark base, neon glow, chrome.
287
429
  - Neon palette: hot pink \`#ff2bd6\`, cyan \`#22d3ee\`, violet \`#a855f7\`. Glow on headings/borders: \`text-shadow:0 0 8px currentColor,0 0 22px currentColor\` and \`box-shadow:0 0 12px #22d3ee,inset 0 0 12px rgba(34,211,238,.3)\`.
288
430
  - Optional perspective grid at the bottom via repeating linear-gradients or an inline SVG. Chrome/gradient text with \`background:linear-gradient(#fff,#8ab4ff);-webkit-background-clip:text;color:transparent\`. (Glow via \`text-shadow\` exports fine.)
289
431
 
432
+ ### 7. app-screen — a single UI screen
433
+ A mockup of one screen of an application. Read \`canvas_guide("exemplar-app-screen")\` before
434
+ drawing one.
435
+
436
+ ⚠️ **Route a mockup here, not to \`board\`.** A single screen is one composition, so the
437
+ container is \`infographic\`; \`board\` teaches low-fidelity wireframe boxes for comparing
438
+ options side by side, which is not what anyone means by "mock up the dashboard".
439
+
440
+ **Four surfaces, in this order** — this is what makes a screen read as an application
441
+ rather than a document:
442
+ - chrome (rail, header) at \`surface-1\` · **the work canvas INSET at \`surface-2\`** ·
443
+ cards back up to \`surface-1\` on top of it · table headers at \`surface-2\`
444
+
445
+ **Exactly one active state**, and no hover states drawn at all. A static mock showing three
446
+ highlighted rows reads as a bug. The active row gets a 10% accent tint plus a 2px inset
447
+ accent edge; one filter chip gets the \`--canvas-fg\` border and the rest carry hairlines.
448
+
449
+ **One primary action, and it is INK, not accent** (\`background: var(--canvas-fg)\`). The
450
+ accent is reserved for the thing that needs attention. Two filled buttons side by side is
451
+ the commonest mock tell; the secondary is a hairline outline.
452
+
453
+ **State a finding, not a page name.** A real dashboard says "Overview" and stops; a MOCK
454
+ has to argue why the screen is worth building, so the largest text is a claim and the
455
+ accent marks its evidence. "Interviews are keeping pace. Document review is not." — not
456
+ "Admissions Dashboard".
457
+
458
+ **Numbers must reconcile.** 248 applied → 193 documents in → 152 reviewed → 67 interviewed,
459
+ and the listed programmes account for the 41 waiting. Figures that contradict each other
460
+ are what make a mock feel machine-made, more than any styling choice. If you were not given
461
+ real data, ask for it rather than inventing it.
462
+
463
+ **Do not draw the OS.** No traffic lights, no browser tab bar, no URL field, no dock. Window
464
+ chrome costs vertical space and dates the mock; the app's own header is the frame. Add it
465
+ only when the point being made is about the window itself.
466
+
467
+ Icons: use the set in \`canvas_guide("icons")\` — never an emoji, never a guessed path.
468
+
469
+ ⚠️ **No icon is better than a wrong icon.** When the set has no shape for a concept, set
470
+ the label alone. A drinking glass on a Ceramics tile or an up-arrow on a Candles tile costs
471
+ more credibility than empty space would, and improvising an SVG path to fill the slot costs
472
+ more still. The set is for UI affordances — nav, actions, status. It is not illustration.
473
+
290
474
  ## Pick-by-content cheatsheet
475
+ - **One screen of an app / a UI mockup** → \`infographic\` + **app-screen**. Several options
476
+ compared side by side → \`board\` + wireframe. (Asking for "a mockup" used to route to one
477
+ of three types, none of which was right.)
291
478
  - **Data / stats / dashboard / neutral** → theme-flat (or glass for a hero moment)
292
- - **App / device / product-UI showcase** → skeuomorphic or glass
479
+ - **A device or product SHOWCASE, not its screen** → skeuomorphic or glass
293
480
  - **Manifesto / dev-tool / bold statement** → brutalist
294
481
  - **Long-form / essay / report / "serious"** → editorial
295
482
  - **Launch / hype / gaming / music / 80s** → retro