@teamblind-chorus/ui 1.0.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 (191) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +112 -0
  3. package/agents/AGENTS.md +143 -0
  4. package/agents/DESIGN.md +1311 -0
  5. package/agents/LOVABLE.md +472 -0
  6. package/agents/anti-patterns.md +533 -0
  7. package/agents/catalog.md +232 -0
  8. package/agents/components/avatar-rail/avatar-rail.family.json +46 -0
  9. package/agents/components/avatar-rail/avatar-rail.md +103 -0
  10. package/agents/components/avatar-rail/avatar-rail.spec.json +160 -0
  11. package/agents/components/badge/badge.family.json +45 -0
  12. package/agents/components/badge/badge.md +10 -0
  13. package/agents/components/badge/role.md +100 -0
  14. package/agents/components/badge/role.spec.json +75 -0
  15. package/agents/components/badge/update.md +132 -0
  16. package/agents/components/badge/update.spec.json +114 -0
  17. package/agents/components/banner/banner.family.json +28 -0
  18. package/agents/components/banner/banner.md +136 -0
  19. package/agents/components/banner/banner.spec.json +136 -0
  20. package/agents/components/bottom-sheet/bottom-sheet.family.json +29 -0
  21. package/agents/components/bottom-sheet/bottom-sheet.md +176 -0
  22. package/agents/components/bottom-sheet/bottom-sheet.spec.json +168 -0
  23. package/agents/components/bubble/bubble.family.json +29 -0
  24. package/agents/components/bubble/bubble.md +134 -0
  25. package/agents/components/bubble/bubble.spec.json +91 -0
  26. package/agents/components/button/button.family.json +76 -0
  27. package/agents/components/button/button.md +31 -0
  28. package/agents/components/button/check.md +138 -0
  29. package/agents/components/button/check.spec.json +161 -0
  30. package/agents/components/button/fab.md +161 -0
  31. package/agents/components/button/fab.spec.json +106 -0
  32. package/agents/components/button/icon.md +141 -0
  33. package/agents/components/button/icon.spec.json +164 -0
  34. package/agents/components/button/standard.md +219 -0
  35. package/agents/components/button/standard.spec.json +205 -0
  36. package/agents/components/button/text.md +186 -0
  37. package/agents/components/button/text.spec.json +215 -0
  38. package/agents/components/button/toggle.md +108 -0
  39. package/agents/components/button/toggle.spec.json +124 -0
  40. package/agents/components/button/toolbar.md +189 -0
  41. package/agents/components/button/toolbar.spec.json +109 -0
  42. package/agents/components/carousel/carousel.family.json +41 -0
  43. package/agents/components/carousel/carousel.md +40 -0
  44. package/agents/components/carousel/post.md +148 -0
  45. package/agents/components/carousel/post.spec.json +229 -0
  46. package/agents/components/carousel/profile.md +184 -0
  47. package/agents/components/carousel/profile.spec.json +219 -0
  48. package/agents/components/chip/chip.family.json +37 -0
  49. package/agents/components/chip/chip.md +10 -0
  50. package/agents/components/chip/filter.md +212 -0
  51. package/agents/components/chip/filter.spec.json +124 -0
  52. package/agents/components/chip/tag.md +137 -0
  53. package/agents/components/chip/tag.spec.json +104 -0
  54. package/agents/components/dialog/dialog.family.json +29 -0
  55. package/agents/components/dialog/dialog.md +113 -0
  56. package/agents/components/dialog/dialog.spec.json +156 -0
  57. package/agents/components/directory-list/directory-list.family.json +46 -0
  58. package/agents/components/directory-list/directory-list.md +87 -0
  59. package/agents/components/directory-list/directory-list.spec.json +104 -0
  60. package/agents/components/divider/divider.family.json +28 -0
  61. package/agents/components/divider/divider.md +78 -0
  62. package/agents/components/divider/divider.spec.json +51 -0
  63. package/agents/components/feed/ad.md +108 -0
  64. package/agents/components/feed/ad.spec.json +187 -0
  65. package/agents/components/feed/feed.family.json +48 -0
  66. package/agents/components/feed/feed.md +30 -0
  67. package/agents/components/feed/post.md +240 -0
  68. package/agents/components/feed/post.spec.json +361 -0
  69. package/agents/components/form-field/form-field.family.json +50 -0
  70. package/agents/components/form-field/form-field.md +11 -0
  71. package/agents/components/form-field/input.md +198 -0
  72. package/agents/components/form-field/input.spec.json +202 -0
  73. package/agents/components/form-field/search.md +81 -0
  74. package/agents/components/form-field/search.spec.json +135 -0
  75. package/agents/components/form-field/select.md +101 -0
  76. package/agents/components/form-field/select.spec.json +194 -0
  77. package/agents/components/form-field/textarea.md +89 -0
  78. package/agents/components/form-field/textarea.spec.json +176 -0
  79. package/agents/components/header/header.family.json +43 -0
  80. package/agents/components/header/header.md +18 -0
  81. package/agents/components/header/main.md +101 -0
  82. package/agents/components/header/main.spec.json +117 -0
  83. package/agents/components/header/sub.md +129 -0
  84. package/agents/components/header/sub.spec.json +81 -0
  85. package/agents/components/list/accordion.md +183 -0
  86. package/agents/components/list/accordion.spec.json +201 -0
  87. package/agents/components/list/entry.md +280 -0
  88. package/agents/components/list/entry.spec.json +237 -0
  89. package/agents/components/list/list.family.json +75 -0
  90. package/agents/components/list/list.md +24 -0
  91. package/agents/components/list/radio.md +144 -0
  92. package/agents/components/list/radio.spec.json +186 -0
  93. package/agents/components/list/standard.md +262 -0
  94. package/agents/components/list/standard.spec.json +221 -0
  95. package/agents/components/metadata/compact.md +69 -0
  96. package/agents/components/metadata/compact.spec.json +69 -0
  97. package/agents/components/metadata/metadata.family.json +42 -0
  98. package/agents/components/metadata/metadata.md +26 -0
  99. package/agents/components/metadata/standard.md +104 -0
  100. package/agents/components/metadata/standard.spec.json +152 -0
  101. package/agents/components/nav-card/nav-card.family.json +29 -0
  102. package/agents/components/nav-card/nav-card.md +179 -0
  103. package/agents/components/nav-card/nav-card.spec.json +161 -0
  104. package/agents/components/nav-list/nav-list.family.json +46 -0
  105. package/agents/components/nav-list/nav-list.md +91 -0
  106. package/agents/components/nav-list/nav-list.spec.json +107 -0
  107. package/agents/components/navigation-bar/main.md +201 -0
  108. package/agents/components/navigation-bar/main.spec.json +109 -0
  109. package/agents/components/navigation-bar/navigation-bar.family.json +44 -0
  110. package/agents/components/navigation-bar/navigation-bar.md +21 -0
  111. package/agents/components/navigation-bar/search.md +96 -0
  112. package/agents/components/navigation-bar/search.spec.json +142 -0
  113. package/agents/components/navigation-bar/sub.md +174 -0
  114. package/agents/components/navigation-bar/sub.spec.json +123 -0
  115. package/agents/components/page-shell/page-shell.family.json +22 -0
  116. package/agents/components/page-shell/page-shell.md +51 -0
  117. package/agents/components/profile-header/profile-header.family.json +29 -0
  118. package/agents/components/profile-header/profile-header.md +149 -0
  119. package/agents/components/profile-header/profile-header.spec.json +200 -0
  120. package/agents/components/progress/progress.family.json +27 -0
  121. package/agents/components/progress/progress.md +38 -0
  122. package/agents/components/progress/progress.spec.json +67 -0
  123. package/agents/components/side-sheet/side-sheet.family.json +30 -0
  124. package/agents/components/side-sheet/side-sheet.md +154 -0
  125. package/agents/components/side-sheet/side-sheet.spec.json +109 -0
  126. package/agents/components/skeleton/skeleton.family.json +28 -0
  127. package/agents/components/skeleton/skeleton.md +123 -0
  128. package/agents/components/skeleton/skeleton.spec.json +73 -0
  129. package/agents/components/status-tag/status-tag.family.json +26 -0
  130. package/agents/components/status-tag/status-tag.md +114 -0
  131. package/agents/components/status-tag/status-tag.spec.json +69 -0
  132. package/agents/components/suggestion-list/suggestion-list.family.json +46 -0
  133. package/agents/components/suggestion-list/suggestion-list.md +91 -0
  134. package/agents/components/suggestion-list/suggestion-list.spec.json +178 -0
  135. package/agents/components/switch/switch.family.json +27 -0
  136. package/agents/components/switch/switch.md +114 -0
  137. package/agents/components/switch/switch.spec.json +123 -0
  138. package/agents/components/tab-bar/tab-bar.family.json +27 -0
  139. package/agents/components/tab-bar/tab-bar.md +178 -0
  140. package/agents/components/tab-bar/tab-bar.spec.json +184 -0
  141. package/agents/components/tabs/rounded.md +150 -0
  142. package/agents/components/tabs/rounded.spec.json +140 -0
  143. package/agents/components/tabs/segmented.md +114 -0
  144. package/agents/components/tabs/segmented.spec.json +100 -0
  145. package/agents/components/tabs/tabs.family.json +59 -0
  146. package/agents/components/tabs/tabs.md +18 -0
  147. package/agents/components/tabs/underline.md +147 -0
  148. package/agents/components/tabs/underline.spec.json +139 -0
  149. package/agents/components/thumbnail/thumbnail.family.json +28 -0
  150. package/agents/components/thumbnail/thumbnail.md +152 -0
  151. package/agents/components/thumbnail/thumbnail.spec.json +172 -0
  152. package/agents/components/toast/toast.family.json +28 -0
  153. package/agents/components/toast/toast.md +133 -0
  154. package/agents/components/toast/toast.spec.json +89 -0
  155. package/agents/components/tooltip/tooltip.family.json +29 -0
  156. package/agents/components/tooltip/tooltip.md +139 -0
  157. package/agents/components/tooltip/tooltip.spec.json +110 -0
  158. package/agents/compose.md +240 -0
  159. package/agents/icons.json +831 -0
  160. package/agents/images.md +66 -0
  161. package/agents/manifest.json +87 -0
  162. package/agents/patterns/README.md +59 -0
  163. package/agents/patterns/actions.md +50 -0
  164. package/agents/patterns/browsing.md +52 -0
  165. package/agents/patterns/communications.md +56 -0
  166. package/agents/patterns/layout.md +72 -0
  167. package/agents/patterns/modals.md +50 -0
  168. package/agents/patterns/visual.md +55 -0
  169. package/agents/reconstruct.md +55 -0
  170. package/agents/scoped-adoption.md +111 -0
  171. package/agents/tokens.usage.json +1657 -0
  172. package/agents/usage.json +422 -0
  173. package/dist/icons/index.cjs +1332 -0
  174. package/dist/icons/index.cjs.map +1 -0
  175. package/dist/icons/index.d.cts +228 -0
  176. package/dist/icons/index.d.ts +228 -0
  177. package/dist/icons/index.js +1114 -0
  178. package/dist/icons/index.js.map +1 -0
  179. package/dist/index.cjs +5905 -0
  180. package/dist/index.cjs.map +1 -0
  181. package/dist/index.d.cts +896 -0
  182. package/dist/index.d.ts +896 -0
  183. package/dist/index.js +5847 -0
  184. package/dist/index.js.map +1 -0
  185. package/dist/styles.css +5765 -0
  186. package/eslint/README.md +79 -0
  187. package/eslint/index.js +78 -0
  188. package/eslint/rules.js +472 -0
  189. package/eslint/test.mjs +135 -0
  190. package/package.json +96 -0
  191. package/placeholder.png +0 -0
@@ -0,0 +1,66 @@
1
+ # Images — generate vs. placeholder
2
+
3
+ How to fill Chorus image slots (`Thumbnail.src`, `Feed.thumbnail`, `FeedAd.media`, `ProfileHeader` avatar/cover, `ProfileCarousel.items[].cover`, every `"assetType": "image"` slot) so a screen reads as finished — not a wall of placeholders, and not hallucinated real brands.
4
+
5
+ This is the **method** behind AGENTS.md rule #9 ("when the composition gives a clear subject, swap the placeholder for a context-appropriate image"). Rule #9 names the goal; this file is the decision tree, the sanctioned generation path, and the handoff report.
6
+
7
+ ## The decision tree (run per image slot)
8
+
9
+ ```
10
+ Is a real asset given (data field, URL, upload)?
11
+ └─ yes → use it. Done.
12
+ Does the composition imply a CLEAR subject?
13
+ ├─ no → /placeholder.png. (No subject to depict — the branded placeholder is honest, not unfinished-by-mistake.)
14
+ └─ yes → is the subject BRAND-SAFE TO SYNTHESIZE?
15
+ ├─ yes (abstract avatar, generic cover band, topic/content thumbnail,
16
+ │ illustrative scene) → GENERATE it (see below).
17
+ └─ no (a real company's actual logo, a real named person's face,
18
+ a copyrighted mark) → /placeholder.png + TODO.
19
+ Never synthesize a real brand/person — a plausible fake is worse
20
+ than an honest placeholder.
21
+ ```
22
+
23
+ **The hybrid rule in one line:** generate where the subject is clear *and* safe to invent (avatars, covers, post thumbnails, illustrations); placeholder where the subject is real-world-specific (actual logos, real people) or absent — and always *report* what stayed on placeholder.
24
+
25
+ ## Generate ≠ invent a stock URL
26
+
27
+ anti-patterns §13 forbids "an invented stock URL" / inline-SVG wordmark / `display:none` as a placeholder workaround. That prohibition stands. It is **not** the sanctioned path here. Sanctioned generation is: produce the asset with your image-generation tool (e.g. `imagegen`), upload it through your asset pipeline (e.g. `lovable-assets`), and store the returned URL in the data field. The difference: a generated, uploaded, self-hosted asset is a real asset the app owns; a pasted `images.unsplash.com/…` URL is an unowned, breakable, off-contract reference. Generate-and-host = allowed; link-to-someone-else's-image = forbidden.
28
+
29
+ ### Prompt shape per slot
30
+
31
+ - **Avatar (person / anonymous member)** — abstract or illustrative profile, no real face: "minimal flat illustrative avatar, neutral studio background, no text." Square. Feed/List/Metadata leading.
32
+ - **Channel / org logo** — only if generic/fictional. A real company → placeholder + TODO. Generic → "simple geometric monogram logo, flat, single accent color, transparent or neutral fill."
33
+ - **Cover band (ProfileHeader / Carousel cover)** — "calm abstract gradient/texture banner, on-brand neutral palette, no text, wide aspect." Wide.
34
+ - **Post thumbnail** — match the body topic: "editorial photo of <topic>, natural light, uncluttered." 80×80 crop-safe.
35
+
36
+ Keep generated imagery calm and on-palette so it sits under Chorus tokens; the slot's footprint/chrome never changes — **only the `src` URL changes**.
37
+
38
+ ## Make the data model image-ready (the upstream fix)
39
+
40
+ The most common cause of placeholder-everywhere is a mock/data model with **no image field** — so the screen has nothing to bind, and every slot hardcodes `/placeholder.png`. Fix it at the type, once:
41
+
42
+ ```ts
43
+ // ❌ Drift root — no image field, so every Thumbnail/Feed slot hardcodes the placeholder
44
+ type Author = { id: string; handle: string; role: string };
45
+ type Post = { id: string; title: string; body: string };
46
+
47
+ // ✅ Image-ready — every entity that feeds an image slot carries the field,
48
+ // initialized to the placeholder. Swapping in a real/generated image is then
49
+ // a one-field edit, never a schema migration.
50
+ type Author = { id: string; handle: string; role: string; avatarUrl: string }; // = "/placeholder.png" until filled
51
+ type Post = { id: string; title: string; body: string; thumbnailUrl: string };
52
+ ```
53
+
54
+ When you scaffold mock data, **add the image field and seed it** (placeholder, or a generated URL per the tree above) — don't omit the field and hardcode `/placeholder.png` at the call site. A seeded field is swappable; a hardcoded `src` is a find-and-replace across screens.
55
+
56
+ ## Report what stayed on placeholder (the handoff)
57
+
58
+ A placeholder left silently reads as "done." After composing a screen (or a batch), **post an image-status line** so remaining slots are a visible TODO, not a mistake:
59
+
60
+ > `Images: 4 real · 6 generated · 3 on placeholder — TODO: <ChannelLogo (real Acme logo), 2× real-author avatars>. Say the word to generate/swap.`
61
+
62
+ List the placeholder slots and *why* each stayed (real-brand/real-person → needs a real asset; no subject → intentionally blank). This makes the hybrid policy auditable and lets the user green-light the real-asset items in one turn.
63
+
64
+ ---
65
+
66
+ See also: [anti-patterns.md §13](anti-patterns.md) (placeholder 404 / forbidden workarounds), AGENTS.md rule #9 (the image-slot contract), and each `*/*.spec.json` image-slot description.
@@ -0,0 +1,87 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "name": "@teamblind-chorus/design-system",
4
+ "version": "0.0.0",
5
+ "description": "Machine-readable inventory of the Chorus design system. External consumers (Claude Design, the docs site, AI agents, design-tool plugins) MUST read this file as the single entry point — never crawl schema/ to infer the system's shape. The file is hand-authored and kept in lock-step with schema/components/<family>/ folders; any new family or sub-component lands here before it lands anywhere else.",
6
+ "agentEntrypoint": "../AGENTS.md",
7
+ "catalog": "catalog.md",
8
+ "design": "DESIGN.md",
9
+ "tokens": {
10
+ "reference": "tokens/reference.json",
11
+ "system": "tokens/system.json",
12
+ "component": "tokens/component.json",
13
+ "resolved": {
14
+ "light": "tokens/resolved.light.json",
15
+ "dark": "tokens/resolved.dark.json",
16
+ "web": "tokens/resolved.web.json",
17
+ "webDark": "tokens/resolved.web.dark.json"
18
+ },
19
+ "build": "tokens/build-resolved.mjs",
20
+ "note": "Three-tier source: `ref.*` raw, `sys.*` semantic (the default tier for bindings), `comp.*` reserved-but-empty. External renderers should read the `resolved.*` bundles — flat `path → { $value, $type }` maps with all `{ref.…}` references dereferenced and theme/breakpoint overlays applied. `resolved.light.json` and `resolved.dark.json` are full bundles (one entry per token); `resolved.web.json` and `resolved.web.dark.json` are sparse — only the tokens that change at the `web` (≥800px) breakpoint. Regenerate with `node schema/tokens/build-resolved.mjs` (or `npm run build:tokens`) whenever a source file changes."
21
+ },
22
+ "icons": {
23
+ "svg": "icons/svg/",
24
+ "manifest": "icons/icons.json",
25
+ "build": "../packages/ui/src/icons/build-icons.mjs",
26
+ "generated": "../packages/ui/src/icons/index.js",
27
+ "exportSubpath": "@teamblind-chorus/ui/icons",
28
+ "note": "Schema-first, like tokens: the source artwork (`icons/svg/*.svg`) and manifest (`icons/icons.json` — per-icon keywords + alias map) live here; `packages/ui` generates the consumable React module (`index.js`, exported as `@teamblind-chorus/ui/icons`) via `npm run build:icons`. Product surfaces import the generated module, never the schema sources. Regenerate whenever an svg or the manifest changes."
29
+ },
30
+ "schemas": {
31
+ "family": "family.schema.json",
32
+ "spec": "spec.schema.json"
33
+ },
34
+ "packages": {
35
+ "components": {
36
+ "name": "@teamblind-chorus/ui",
37
+ "entry": "packages/ui/src/index.js",
38
+ "icons": "packages/ui/src/icons/index.js",
39
+ "styles": "packages/ui/src/styles.css",
40
+ "iconsExportSubpath": "@teamblind-chorus/ui/icons",
41
+ "stylesExportSubpath": "@teamblind-chorus/ui/styles.css",
42
+ "published": false,
43
+ "distribution": "source",
44
+ "note": "Workspace-only, source-distributed. External tools either (a) read JSX directly from `entry` (compile JSX themselves) or (b) consume the structural contract via `schema/components/<family>/<sub>.spec.json` and re-render with their own renderer. Either path MUST also load `styles.css` once at the app entry — the JSX emits inline `--<component>-*` plumbing vars but expects the static rules in `styles.css` to receive them. The package is marked `private: true` and intentionally pinned at version `0.0.0` until a publishing decision is made."
45
+ },
46
+ "tokens": {
47
+ "name": "@teamblind-chorus/tokens",
48
+ "published": false,
49
+ "distribution": "source",
50
+ "note": "Workspace-only. External tools should prefer the `tokens.resolved.*` bundles above over the three-tier source files unless they specifically need the reference graph."
51
+ }
52
+ },
53
+ "components": [
54
+ { "family": "badge", "root": "components/badge/badge.family.json" },
55
+ { "family": "banner", "root": "components/banner/banner.family.json" },
56
+ { "family": "bottom-sheet", "root": "components/bottom-sheet/bottom-sheet.family.json" },
57
+ { "family": "bubble", "root": "components/bubble/bubble.family.json" },
58
+ { "family": "button", "root": "components/button/button.family.json" },
59
+ { "family": "suggestion-list", "root": "components/suggestion-list/suggestion-list.family.json" },
60
+ { "family": "avatar-rail", "root": "components/avatar-rail/avatar-rail.family.json" },
61
+ { "family": "chip", "root": "components/chip/chip.family.json" },
62
+ { "family": "dialog", "root": "components/dialog/dialog.family.json" },
63
+ { "family": "divider", "root": "components/divider/divider.family.json" },
64
+ { "family": "directory-list", "root": "components/directory-list/directory-list.family.json" },
65
+ { "family": "feed", "root": "components/feed/feed.family.json" },
66
+ { "family": "form-field", "root": "components/form-field/form-field.family.json" },
67
+ { "family": "header", "root": "components/header/header.family.json" },
68
+ { "family": "list", "root": "components/list/list.family.json" },
69
+ { "family": "metadata", "root": "components/metadata/metadata.family.json" },
70
+ { "family": "nav-card", "root": "components/nav-card/nav-card.family.json" },
71
+ { "family": "nav-list", "root": "components/nav-list/nav-list.family.json" },
72
+ { "family": "navigation-bar", "root": "components/navigation-bar/navigation-bar.family.json" },
73
+ { "family": "page-shell", "root": "components/page-shell/page-shell.family.json" },
74
+ { "family": "profile-header", "root": "components/profile-header/profile-header.family.json" },
75
+ { "family": "progress", "root": "components/progress/progress.family.json" },
76
+ { "family": "carousel", "root": "components/carousel/carousel.family.json" },
77
+ { "family": "side-sheet", "root": "components/side-sheet/side-sheet.family.json" },
78
+ { "family": "skeleton", "root": "components/skeleton/skeleton.family.json" },
79
+ { "family": "status-tag", "root": "components/status-tag/status-tag.family.json" },
80
+ { "family": "switch", "root": "components/switch/switch.family.json" },
81
+ { "family": "tab-bar", "root": "components/tab-bar/tab-bar.family.json" },
82
+ { "family": "tabs", "root": "components/tabs/tabs.family.json" },
83
+ { "family": "thumbnail", "root": "components/thumbnail/thumbnail.family.json" },
84
+ { "family": "toast", "root": "components/toast/toast.family.json" },
85
+ { "family": "tooltip", "root": "components/tooltip/tooltip.family.json" }
86
+ ]
87
+ }
@@ -0,0 +1,59 @@
1
+ # Patterns
2
+
3
+ Canonical visual references for Chorus. Each pattern pairs an **image board** (`<slug>.png`) with a spec note (`<slug>.md`) covering intent, layout, tokens, and components. Boards are grouped by *what the surface does* — each `.png` is a montage of representative screens so an agent can learn a category's recurring patterns and visuals at a glance.
4
+
5
+ Schemas say *what is allowed*; patterns say *what good looks like*.
6
+
7
+ ## Precedence (read this first)
8
+
9
+ Patterns are **descriptive**, not prescriptive. The list below is **authority on conflict**, distinct from [`AGENTS.md`](../AGENTS.md) "Read order" (the *intake sequence*). Read order = where to look first; precedence = who wins when two sources disagree.
10
+
11
+ 1. **Hard rules** in [`AGENTS.md`](../AGENTS.md) — always win.
12
+ 2. **Component specs** in [`schema/components/<family>/<sub>.spec.json`](../schema/components) and [`schema/DESIGN.md`](../schema/DESIGN.md) — authoritative for props, slots, sizes, appearances, states, and cross-cutting design rules.
13
+ 3. **Resolved tokens** in [`schema/tokens/`](../schema/tokens) — authoritative for color/type/space/radius values.
14
+ 4. **Patterns (this directory)** — visual reference and rationale only. If a pattern note conflicts with a spec or token, the spec/token wins; treat the pattern as out of date and fix it.
15
+
16
+ ## How agents should use this
17
+
18
+ 1. Scan for the closest intent match (use `status: canonical` first).
19
+ 2. Read the `.md` for load-bearing tokens, components, and layout decisions.
20
+ 3. Pull the image in if pixel-level alignment matters (spacing rhythm, hierarchy, dark/light parity).
21
+ 4. Cross-check against `*.spec.json` and `resolved.*.json` — those are the contract; this is the inspiration.
22
+
23
+ ## File layout
24
+
25
+ ```
26
+ patterns/
27
+ README.md # this file — index
28
+ <slug>.png # image board (montage of representative screens), sRGB; dark variant: <slug>.dark.png
29
+ <slug>.md # spec note for the board
30
+ ```
31
+
32
+ Each board groups screens by *function*, not by destination. A single screen's patterns may be referenced from more than one board (e.g. the Company feed appears in both [layout](layout.md) and [browsing](browsing.md)).
33
+
34
+ ## Index
35
+
36
+ <!-- Keep alphabetical. Add a row when you add a board. -->
37
+
38
+ | Board | Status | Covers |
39
+ |-------|--------|--------|
40
+ | [actions](actions.md) | canonical | Forms, inputs, checklists, filters, and primary-commit surfaces — verification, registration, reviews, Filter Settings, create shortcuts. |
41
+ | [browsing](browsing.md) | canonical | Entity discovery & navigation — channel drawers, all/followed lists, Explore rails, search suggestions, company feed; Follow/Unfollow toggles. |
42
+ | [communications](communications.md) | canonical | Authored content & engagement — posts, polls, gated previews, comment threads, channel picker, offer/poll compose. |
43
+ | [layout](layout.md) | canonical | Page scaffolding & nav chrome — Home, Company, Explore, Jobs, Notifications + channel profile; bars, tabs, filter rails, shared gutter. |
44
+ | [modals](modals.md) | canonical | Overlay surfaces — confirmation & info Dialogs, full-screen disclosure, bottom-anchored CTAs, BottomSheet purchase flows. |
45
+ | [visual](visual.md) | canonical | Image-bearing surfaces — empty-state illustrations, photo grids, brand/media cards, category tiles, illustration banners; the `/placeholder.png` image-area contract. |
46
+
47
+ > Board slugs use single-word `lowercase` names.
48
+
49
+ ## Status values
50
+
51
+ - **canonical** — signed-off reference. Agents should bias toward matching this.
52
+ - **draft** — proposed direction; do not assume tokens/components are final.
53
+ - **deprecated** — kept for diffing against newer canonical versions. Do not copy.
54
+
55
+ ## Adding a board
56
+
57
+ 1. Drop `<slug>.png` (a montage of representative screens; optional `<slug>.dark.png`) here. Prefer device-frame-free crops at component-pixel density.
58
+ 2. Create `<slug>.md` with frontmatter (`name`, `image`, `status`) and the sections used by existing boards: **Intent**, **Board cells** (one line per screen in the montage), **Layout**, **Tokens in use**, **Components**, **Notes**.
59
+ 3. Add a row to the index above.
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: actions
3
+ image: ./actions.png
4
+ status: canonical
5
+ ---
6
+
7
+ ## Intent
8
+
9
+ Task-completion surfaces — the user supplies input and commits. Verification forms, keyword/post registration, service-agreement checklists, comment composition, job filter settings, and the home content-creation shortcuts. Each surface is keyboard-first and terminates in **one** high-emphasis primary commit (a full-width or trailing blue `button / standard`), with neutral escapes parked around it. Reach here for the anatomy of "fill something in, then commit it".
10
+
11
+ ## Board cells
12
+
13
+ 1. **Work-email verification** — `navigation-bar / sub` (Company), explainer copy, `form-field / input` (`name@amazon.com`), primary "verify" CTA, neutral "Back", keyboard up.
14
+ 2. **Keyword registration** — title `form-field / input`, primary "Register" CTA, helper text.
15
+ 3. **Service-agreement checklist** — `button / check` agreement rows, `form-field / input` email, primary "Next" CTA, keyboard up.
16
+ 4. **Comment composition** — post body above a reply composer: `form-field / textarea` + image-attach `button / icon`, two privacy `button / check` toggles ("Company name undisclosed" / "Company colleague"), primary "Register" CTA, keyboard up.
17
+ 5. **Filter Settings** — `form-field / search` company filter, multi-select company checklist, primary "Apply", "Reset Filter" text reset.
18
+ 6. **Home action shortcuts** — Create a Post / Share a Story / Create a Story entry card on the feed.
19
+
20
+ ## Layout
21
+
22
+ - **Single primary commit per surface** — one blue `button / standard` (filled primary). It anchors the bottom of the form or trails the input; never two competing primaries.
23
+ - **Escapes are subordinate** — "Back"/"Reset Filter"/"Not now" render as `button / text`, visually lighter than the commit.
24
+ - **Inputs stack vertically** with helper/placeholder text in secondary color directly under or inside the field.
25
+ - **Checklists** are `button / check` rows (commit option state) or a search-filtered selection list; the commit (`Apply`/`Next`) lives at the bottom.
26
+
27
+ ## Tokens in use
28
+
29
+ - **color**: primary CTA `sys.color.primary` bg + `sys.color.onPrimary` label; input fill `sys.color.surfaceContainerLow`; placeholder + helper `sys.color.onSurfaceVariant`; text escapes in `sys.color.onSurface` (neutral) or accent blue (link-shape).
30
+ - **spacing**: `sys.layout.stack.lg` between form blocks, `sys.layout.stack.md` within a block; page shell pays `sys.layout.page.md` once.
31
+ - **typography**: field + body input `sys.typo.body.md`; titles `sys.typo.heading.md` bold; helper `sys.typo.body.sm` secondary; CTA `sys.typo.label.lg`.
32
+ - **radius**: inputs/buttons `sys.radius.md`; filter chips fully rounded.
33
+
34
+ ## Components
35
+
36
+ - [[form-field/input]] — verification, title, email entry.
37
+ - [[form-field/textarea]] — multiline comment body.
38
+ - [[form-field/search]] — company filter in Filter Settings.
39
+ - [[button/standard]] — the primary commit (verify / Register / Next / Apply).
40
+ - [[button/text]] — Back, Reset Filter, secondary escapes.
41
+ - [[button/check]] — agreement rows + multi-select company checklist.
42
+ - [[navigation-bar/sub]] · [[navigation-bar/search]] — top chrome.
43
+
44
+ ## Notes
45
+
46
+ - **FormField is `visualReuse: "locked"`** (AGENTS.md principle 5) — use it only for real text entry, never borrowed for shape.
47
+ - **Apply / Reset is a pair**: `Apply` = primary `button / standard`; `Reset Filter` = `button / text`. Don't promote Reset to a second filled button.
48
+ - **Comment composer pairs privacy toggles with the commit** — the "Company name undisclosed" / "Company colleague" options are `button / check`, the image attach is a `button / icon`, and the commit is the single primary "Register"; don't model the privacy options as a second commit.
49
+ - Checkmarks and grid glyphs are **SVG icon components**, never typed `✓` characters (rule 10).
50
+ - Source mixes EN/KR; demo strings render English per AGENTS.md rule 7.
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: browsing
3
+ image: ./browsing.png
4
+ status: canonical
5
+ ---
6
+
7
+ ## Intent
8
+
9
+ Entity discovery and navigation — channel drawers, all/followed channel lists, Explore trending rails, search empty/suggestion states, and the company feed. Lists of entities (channels, companies, posts) are the spine; the per-row commit is **Follow / Unfollow** (`button / toggle`), and the **whole row is the nav click target**. Reach here for "browse a set of things and follow/open one".
10
+
11
+ ## Board cells
12
+
13
+ 1. **My Channel drawer** — grouped `nav-list` (Favorites, Channels I'm Following) + "Create Channel" header action; the channel side-navigation.
14
+ 2. **All Channels** — channel `list / entry` with a Followed-Channels section carrying `button / toggle` "Unfollow".
15
+ 3. **Followed Channels** — "Recent Visits" header, per-row `button / toggle` "Unfollow" with member counts.
16
+ 4. **Explore** — Trending Channels rows with `button / toggle` "Follow", "New Channels" media `nav-card`s, `tab-bar`.
17
+ 5. **Search no-results** — empty state + "Popular Channels" / "Popular Companies" suggestion lists with "View All".
18
+ 6. **Company feed** — company `tabs` + recruiter `banner` + POPULAR `feed / post` items.
19
+
20
+ ## Layout
21
+
22
+ - **Entity row anatomy** — leading avatar/logo `thumbnail`, name (bold) + one-line meta (member count / blurb), trailing `button / toggle` commit.
23
+ - **Grouping by relationship** — sections ("Favorites", "Following", "Recent Visits") separate the same row shape by scope; keep as discrete sections with their own headers.
24
+ - **Discovery rails** — Explore mixes vertical channel rows with media-card previews; section headers carry a trailing "View All / See all" `button / text`.
25
+ - **Empty state** — "No results" headline then suggestion lists, so the surface never dead-ends.
26
+
27
+ ## Tokens in use
28
+
29
+ - **color**: `sys.color.surface` page, `sys.color.onSurface` names, `sys.color.onSurfaceVariant` meta; Follow = brand-blue solid (`sys.color.primary` / `onPrimary`), Unfollow = outlined neutral; accent blue on "See all" links.
30
+ - **spacing**: `sys.layout.stack.md` between rows, `sys.layout.stack.lg` between sections; one shared gutter (`sys.layout.page.md` once).
31
+ - **typography**: channel name `sys.typo.heading.sm` bold; meta `sys.typo.label.sm`; section headers `sys.typo.heading.md` bold.
32
+ - **radius**: avatars circular; media cards `sys.radius.md`; toggle pills fully rounded.
33
+
34
+ ## Components
35
+
36
+ - [[nav-list]] — channel drawer / side navigation, grouped.
37
+ - [[list/standard]] — avatar-anchored channel rows.
38
+ - [[directory-list]] — dense followed/all-channel directories.
39
+ - [[button/toggle]] — Follow / Unfollow, the per-row commit (parity in both states).
40
+ - [[nav-card]] — "New Channels" media preview cards.
41
+ - [[avatar-rail]] — horizontal entity quick-nav where used.
42
+ - [[navigation-bar/sub]] · [[tab-bar]] — chrome.
43
+ - [[feed/post]] — company-feed stream.
44
+ - [[button/text]] — "View All / See all" section actions.
45
+
46
+ ## Notes
47
+
48
+ - **Rows are the click target** (AGENTS.md): the leading avatar and the trailing toggle are *not* separate hit areas — tapping the row opens the channel, the toggle only flips follow state.
49
+ - **Follow / Unfollow is one `button / toggle`** shown in both states — this board is the parity reference; don't model them as two different buttons.
50
+ - **AvatarRail ≠ SuggestionList ≠ nav-list**: horizontal quick-nav vs vertical follow-suggestion block vs grouped menu. Pick by the cell's job, don't interchange.
51
+ - "View All / See all" link-affordance text buttons use `appearance="accent"` (AGENTS.md rule 8).
52
+ - Source mixes EN/KR; demo strings render English per AGENTS.md rule 7.
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: communications
3
+ image: ./communications.png
4
+ status: canonical
5
+ ---
6
+
7
+ ## Intent
8
+
9
+ Authored content and engagement — posts, embedded polls, gated previews, comment threads, the channel picker for posting, and offer/poll compose. `feed` is the spine for content streams; `list / radio` carries poll options; compose surfaces commit via a Cancel/Post action bar. Reach here for "read, react to, or author a post or comment".
10
+
11
+ ## Board cells
12
+
13
+ 1. **Poll post** — `feed / post` body + `list / radio` poll with result bars ("128 Participants") + Like / Comment / Message footer.
14
+ 2. **Gated preview** — blurred body behind a "Tap to View" reveal CTA; "Prevent preview" affordance.
15
+ 3. **Post + reply** — long-form post body, inline "Register" CTA, comment compose with keyboard up.
16
+ 4. **Comment thread** — `feed` comment stream, nested replies on a sunken surface, persistent bottom compose bar.
17
+ 5. **Select Channel sheet** — `bottom-sheet` channel picker with `form-field / search` and grouped channel rows (where to post).
18
+ 6. **Offer/Poll compose** — Cancel/Post action bar, title input, "Create an Offer or Poll" pill, "Popular Tags" chip row, keyboard up.
19
+
20
+ ## Layout
21
+
22
+ - **Post anatomy** — author block (avatar, channel + time, workplace · role · alias), title, body, then a footer action row (heart / comment / message / view). Two meta lines on the author block are load-bearing.
23
+ - **Poll** — a soft container quoting `banner` shape: leading Poll icon, participant header, helper, then `list / radio` option rows with result bars.
24
+ - **Comment thread** — sort/jump `button / text` controls on top; nested replies use a `sys.color.surfaceContainerLow` block (not a left rule); compose bar pinned to the bottom safe area.
25
+ - **Compose** — top bar is a modal action bar (Cancel / Post), *not* a `navigation-bar`; channel + identity rows, title/body inputs, an offer/poll FAB, and a Popular Tags chip rail.
26
+
27
+ ## Tokens in use
28
+
29
+ - **color**: active heart + Poll icon in coral (`sys.color.brand`); links / Cancel-Post-valid / jump controls in accent blue (`sys.color.primary`); verified badge accent blue; nested-reply block `sys.color.surfaceContainerLow`; Popular Tags chips `chip / tag` `accent` (primaryContainer + primary label).
30
+ - **spacing**: `sys.layout.stack.lg` between title/body/poll and between comments; footer row gap `sys.layout.stack.md`.
31
+ - **typography**: post title `sys.typo.heading.md`–`display.md` bold; body `sys.typo.body.lg`; comment body `sys.typo.body.md`; meta `sys.typo.label.sm`.
32
+ - **radius**: poll container `sys.radius.md`, option rows `sys.radius.sm`; offer pill + chips fully rounded.
33
+
34
+ ## Components
35
+
36
+ - [[feed/post]] — post and comment streams.
37
+ - [[list/radio]] — poll options.
38
+ - [[form-field/input]] — title / body / comment compose bar.
39
+ - [[bottom-sheet]] — Select Channel picker (paired trigger from compose).
40
+ - [[form-field/search]] — channel search inside the picker.
41
+ - [[list/standard]] — channel rows in the picker.
42
+ - [[button/text]] — Cancel / Post, sort, jump-to-latest, load-earlier.
43
+ - [[button/fab]] — "Create an Offer or Poll" pill (`appearance: secondary`).
44
+ - [[chip/tag]] — Popular Tags (clickable suggestions, `accent`).
45
+ - [[badge]] — verified checkmark on author handles.
46
+ - [[button/toolbar]] — post-level action cluster (share / subscribe / bookmark / overflow).
47
+
48
+ ## Notes
49
+
50
+ - **Compose top bar is an action bar** (Cancel / Post), not `navigation-bar/*`. Don't retrofit a sub bar.
51
+ - **The offer/poll pill IS `button / fab` `secondary`** — the canonical commit for compose; "Post" in the top bar is finalize chrome (`button / text`), so the ≤1-FAB rule holds.
52
+ - **Comment compose bar is persistent**, docked while scrolling — model as a pinned `form-field/input`, never a `bottom-sheet`.
53
+ - **Popular Tags are `chip / tag` `accent`, not `chip / filter`** — taps insert a tag, they don't toggle filter state.
54
+ - **Poll options are `list / radio`** inside a banner-shaped container; don't bake results into the title string.
55
+ - "Tap to View" gated reveal is an overlay affordance over blurred media — out-of-system chrome; don't synthesize a component.
56
+ - Source mixes EN/KR; demo strings render English per AGENTS.md rule 7.
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: layout
3
+ image: ./layout.png
4
+ status: canonical
5
+ ---
6
+
7
+ ## Intent
8
+
9
+ Page-level scaffolding and navigation chrome across the five main tab destinations — **Home, Company, Explore, Jobs, Notifications** — plus a channel profile. This board is the reference for *frame*, not content: how a screen seats its top bar, in-page tabs, filter rail, scrolling body, and bottom tab bar so every destination reads as one geometry. When composing any full screen, match the scaffold here before filling slots.
10
+
11
+ ## Board cells
12
+
13
+ 1. **Home** — `navigation-bar / main` (hamburger · wordmark · icon cluster), `avatar-rail` feed-mode chips (For You / Recent / Popular), recruiter + keyword banners, `feed / post` stream, `tab-bar`.
14
+ 2. **Company** — `navigation-bar / sub` "Company", `tabs / underline` (MY COMPANY / FOLLOWING), `chip / filter` company-scope rail, collapsible "Overview" `banner`, `feed / post`.
15
+ 3. **Explore** — `tabs / underline` (Explore / My Channels), "Recommended for you" preview cards, "Hot Posts" section, `tab-bar`.
16
+ 4. **Jobs** — `navigation-bar / search`, `tabs / underline` with inline counts (Jobs 3,401 / Saved 4), `chip / filter` facet rail, bookmarkable job list, "Job saved" `toast`.
17
+ 5. **Notifications** — `navigation-bar / sub`, settings-off `banner` ("Open iOS Settings"), recruiter banner, bell-leading notification `list`.
18
+ 6. **Channel profile (dark)** — `profile-header` hero band, `tabs`, branded `feed` — dark-theme parity reference.
19
+
20
+ ## Layout
21
+
22
+ - **One bar at top, one bar at bottom.** Exactly one `navigation-bar` (`home` for landing, `page` for drill-ins, `search` when the input is the screen) and one `tab-bar` (6 items, Create as the coral commit). Never stack two top bars.
23
+ - **In-page modes are `tabs / underline`** with a sliding indicator (Company, Explore, Jobs). These are *page* tabs — distinct from Home's `avatar-rail` feed-mode chips, which are the rail primitive, not `tabs`.
24
+ - **Filter rails** sit directly under the tabs as a horizontally scrollable `chip / filter` row (company scope, job facets).
25
+ - **Body** scrolls under fixed chrome: `feed / post` for authored streams, `list` for notification/job rows.
26
+
27
+ ### Fixed-chrome scroll contract (the bars must NOT scroll)
28
+
29
+ `NavigationBar` and `TabBar` render **in flow** — the components do not pin themselves (they only pay their own `env(safe-area-inset-*)`). Pinning is the **page shell's** job. Without it the whole page scrolls and both bars drift off-screen with the content. **Use `<PageShell nav={…} tabBar={…}>` — it ships this contract** (the `.chorus-page-shell` / `.chorus-page-shell__body` classes), so you don't hand-author it; a dev-only `usePinnedBarGuard` warns when a bar renders inside a scrolling region instead. The shell is a full-height flex column where only `<main>` scrolls:
30
+
31
+ ```css
32
+ /* What <PageShell> / .chorus-page-shell ships — the §A.4 skeleton. */
33
+ .page-shell {
34
+ display: flex;
35
+ flex-direction: column;
36
+ height: 100dvh; /* dynamic viewport — survives mobile URL-bar show/hide */
37
+ }
38
+ .page-shell > main {
39
+ flex: 1 1 auto;
40
+ min-height: 0; /* lets the flex child shrink so it — not the shell — scrolls */
41
+ overflow-y: auto;
42
+ }
43
+ ```
44
+
45
+ `NavigationBar` (first child) and `TabBar` (last child) are flex items at their natural height; `<main>` takes the remaining space and is the **only** scroll region — so the bars stay put. `position: sticky`/`fixed` on the bars is neither needed nor wanted here (it would re-introduce double safe-area insets). `min-height: 0` on `<main>` is the load-bearing line: omit it and the flex child refuses to shrink, the shell overflows, and the page scrolls as one piece again.
46
+
47
+ ## Tokens in use
48
+
49
+ - **color**: `sys.color.surface` page, `sys.color.onSurface` titles, `sys.color.onSurfaceVariant` meta; coral accent on the Create tab + active heart + Poll; accent blue on inline/link affordances. Dark cell renders the same roles from `resolved.dark.json`.
50
+ - **spacing**: one shared horizontal gutter — page shell pays `sys.layout.page.md` once; full-bleed chrome (bars, rails, feed, banners) stretches edge-to-edge inside it. `sys.layout.stack.lg` between feed cards.
51
+ - **typography**: feed/section titles `sys.typo.heading.md` bold; meta `sys.typo.label.sm`.
52
+ - **radius**: chips fully rounded; cards/banners `sys.radius.md`.
53
+
54
+ ## Components
55
+
56
+ - [[navigation-bar/main]] · [[navigation-bar/sub]] · [[navigation-bar/search]] — top chrome, one per screen.
57
+ - [[tab-bar]] — bottom nav, Create as accent.
58
+ - [[tabs/underline]] — in-page modes with sliding indicator.
59
+ - [[chip/filter]] — company-scope + job-facet rails.
60
+ - [[avatar-rail]] — channel + feed-mode rail on Home.
61
+ - [[banner]] — promo / overview / settings-off rows.
62
+ - [[feed/post]] — authored stream.
63
+ - [[toast]] — "Job saved" with Undo.
64
+ - [[profile-header]] — channel hero (dark cell).
65
+
66
+ ## Notes
67
+
68
+ - **One geometry across the flow** (AGENTS.md hard rule 6): all three `navigation-bar` subs share `56px` min-height and `8/8` padding; bar/row heights stay stable as you move between destinations.
69
+ - **Pay the gutter once.** Do not wrap full-bleed components in a second `layout.container.md`; rails inside a `Section`/`Feed` enter `embedded` mode so the gutter isn't double-paid.
70
+ - "For You / Recent / Popular" are `avatar-rail` chips, **not** `tabs / underline` — don't swap the primitive.
71
+ - Dark channel-profile cell is a parity check: every composition must render under both `resolved.light` and `resolved.dark`.
72
+ - Source mixes EN/KR; demo strings render English per AGENTS.md rule 7.
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: modals
3
+ image: ./modals.png
4
+ status: canonical
5
+ ---
6
+
7
+ ## Intent
8
+
9
+ Overlay surfaces that interrupt the flow for a commit or disclosure — confirmation Dialogs, info Dialogs, a full-screen disclosure panel, bottom-anchored confirmation CTAs, and BottomSheet purchase flows. These are the interaction-contract primitives: `dialog` and `bottom-sheet` are `visualReuse: "locked"` — chosen for behavior (scrim, focus trap, ARIA), never borrowed for shape. Reach here for "block the screen until the user decides".
10
+
11
+ ## Board cells
12
+
13
+ 1. **Confirmation Dialog** — centered modal confirm ("I've checked it!") with scrim over a profile.
14
+ 2. **"About the Scores" info Dialog** — scrim over recommended companies, bulleted explainer + `button / icon` X close.
15
+ 3. **"Overview" disclosure panel** — full-screen scrollable info (Workplace / Employees / Contractors) with linked references and a single "Got It" primary dismiss.
16
+ 4. **Channel-profile bottom CTA** — bottom-anchored confirm button (DM guidance) over a channel surface.
17
+ 5. **Story purchase Dialog** — centered dialog over Story media, primary action + X close.
18
+ 6. **Ad-hide promotion BottomSheet** — pricing rows ($9.90/month, 7-day free trial) + commit / "Do it later" stack.
19
+
20
+ ## Layout
21
+
22
+ - **Dialog** — centered card, scrim behind, title + body + 1–2 actions; a trailing `button / icon` X for dismissable info dialogs.
23
+ - **BottomSheet** — drag handle, rounded top corners, scrim over the parent; content scrolls while the primary CTA stays pinned to the safe area.
24
+ - **Disclosure panel** ("Overview") — near-full-screen scrollable info with a single bottom dismiss ("Got It") — a maximal dialog, not a new destination.
25
+ - **Action stack** — purchase/confirm sheets pin a full-width primary commit with a neutral secondary ("Do it later" / Cancel) directly beneath.
26
+
27
+ ## Tokens in use
28
+
29
+ - **color**: dialog/sheet surface `sys.color.surface` + `sys.color.onSurface`; scrim is the tokenized `scrim` ref over the parent; primary commit `sys.color.primary` / `onPrimary`; neutral dismiss on `sys.color.surface` with `sys.color.onSurface`; destructive commits carry `sys.color.error`.
30
+ - **spacing**: row/section gaps `sys.layout.stack.md`–`lg`; action stack hugs the bottom safe area.
31
+ - **typography**: dialog/sheet title `sys.typo.heading.sm`–`lg` bold; body `sys.typo.body.md`; CTA `sys.typo.label.lg`.
32
+ - **radius**: dialog card `sys.radius.lg`; sheet top corners `sys.radius.lg`; buttons `sys.radius.md`.
33
+
34
+ ## Components
35
+
36
+ - [[dialog]] — confirmation + info modals (About the Scores, Story purchase).
37
+ - [[bottom-sheet]] — purchase / committed-sheet flows (ad-hide, channel-context CTAs).
38
+ - [[button/standard]] — primary commit + neutral dismiss; `flavor: "destructive"` for irreversible commits.
39
+ - [[button/icon]] — X close (renders `XIcon`).
40
+ - [[banner]] — in-sheet description / teaching blocks.
41
+
42
+ ## Notes
43
+
44
+ - **Dialog vs BottomSheet, both locked, never inline** (AGENTS.md principles 2 & 5): Dialog for modal commits, BottomSheet for committed-sheet flows. Pick by interaction, not visual fit.
45
+ - **Every dialog / bottom-sheet needs a paired trigger** — the screen validator fails an orphaned overlay; document the triggering action.
46
+ - **Destructive primary commits escalate to a Dialog/Sheet, never a FAB** and never fire inline — the *overlay* owns the irreversible action.
47
+ - **Bounded surface hosting a full-bleed child** (a `list` inside a sheet) must break padding with the negative-margin opt-out so row content aligns to the sheet's content box.
48
+ - X close is a `button / icon` with `XIcon`, never a typed `×` character (AGENTS.md rule 10).
49
+ - Purchase sheets use a **stacked full-width action pair** when the commit is the surface's whole purpose (not the sheet's inline `primaryAction`/`secondaryAction`).
50
+ - Source mixes EN/KR; demo strings render English per AGENTS.md rule 7.
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: visual
3
+ image: ./visual.png
4
+ status: canonical
5
+ ---
6
+
7
+ ## Intent
8
+
9
+ Image-bearing and expressive surfaces — empty-state illustrations, photo grids, media-rich cover cards, colorful category tiles, illustration banners, and gated-channel covers. This board is the reference for the **image-area contract**: every `assetType: "image"` slot fills with the universal `/placeholder.png` and swaps to a context image by changing only the `src`, never the slot's footprint. Reach here for "this surface carries pictures, not just text".
10
+
11
+ ## Board cells
12
+
13
+ 1. **"Push notifications are off" empty state** — mascot illustration, numbered enable steps, primary "Open iOS Settings", "Not now".
14
+ 2. **Recruiting list** — company-logo `thumbnail` rows + pricing tags + "Set up your team" CTA banner.
15
+ 3. **Profile photo grid** — `profile-header` + a 3-column photo gallery.
16
+ 4. **Profile mini-card** — author card (Block / Join Team / Action Tag) + illustration recruiter banner.
17
+ 5. **Recommended Industries** — colorful category tiles (Facilities Services, Real Estate, …) + "Trending now".
18
+ 6. **Gated private channel** — `profile-header` with a full-bleed brand-tonal cover + overlay `badge` (NSFW), a centered lock-icon empty state, and a primary "Follow and request approval" CTA beside a Follow `button / toggle`.
19
+
20
+ ## Layout
21
+
22
+ - **Empty state** — centered illustration + headline + (optional numbered steps) + a single primary CTA. Never a bare blank surface.
23
+ - **Media rows / cards** — leading logo/cover `thumbnail` at a fixed footprint, text beside or below; covers are full-bleed within their card.
24
+ - **Photo grid** — uniform-cell gallery under the profile header; cells crop to fill (`object-fit: cover`).
25
+ - **Category tiles** — image/color-filled tiles with an overlaid label; the label must clear contrast against the tile (scrim if needed).
26
+ - **Cover + gated empty state** — a full-bleed `profile-header` cover (brand-tonal, with an overlay `badge`) seats above a centered lock-icon empty state; the empty state pairs a short explainer with a single primary "request access" CTA — never a bare blank panel.
27
+
28
+ ## Tokens in use
29
+
30
+ - **color**: `sys.color.surface` page; text over imagery resolves to the pre-paired `on*` for its host (or a scrim + `inverseOnSurface`); empty-state CTA `sys.color.primary` / `onPrimary`. Category-tile fills must pass AA against their label in both themes.
31
+ - **spacing**: grid gutters and card gaps `sys.layout.stack.md`; one shared page gutter `sys.layout.page.md`.
32
+ - **typography**: empty-state headline `sys.typo.heading.md` bold; card titles `sys.typo.heading.sm`; meta `sys.typo.label.sm`.
33
+ - **radius**: cards / tiles `sys.radius.md`; avatars circular; grid cells `sys.radius.sm`.
34
+
35
+ ## Components
36
+
37
+ - [[thumbnail]] — logo / cover image slots (`assetType: image`).
38
+ - [[feed/ad]] — media-rich promotional cards.
39
+ - [[carousel/post]] · [[carousel/profile]] — horizontally scrollable media previews.
40
+ - [[profile-header]] — hero/cover band above the photo grid.
41
+ - [[nav-card]] — cover-bearing category / channel tiles.
42
+ - [[banner]] — illustration recruiter banner.
43
+ - [[badge]] — logo / status overlay on a thumbnail.
44
+
45
+ ## Notes
46
+
47
+ - **`/placeholder.png` is the single universal image-area placeholder** (AGENTS.md rule 9): fill every `assetType: image` slot with it while scaffolding; swap to a context image only by changing `src`, never the footprint or chrome. When no context image can be inferred, fall back to `/placeholder.png` — not a glyph-in-circle or invented stock URL.
48
+ - **Full-bleed covers promote the outline stroke to the `::after` overlay layer** (AGENTS.md rule 3) — an inset shadow paints *below* an opaque cover child and gets masked at the edges it touches.
49
+ - **Illustrations are imagery; icons are SVG glyph components** (rule 10) — the mascot is an image asset, but the numbered-step glyphs and CTAs use icon components, never typed characters.
50
+ - **Brand/vendor marks render at authentic colors** (company / vendor logos) — they are not theme tokens; don't recolor.
51
+ - **Gated / empty channel states stay token-true** — a centered lock-`icon` + explainer + single primary "request access" CTA; the lock is an SVG icon component (rule 10), never a typed glyph.
52
+ - **Category tiles must pair contrast, not compose it** (rule 11): `onSurface` text over a colored/image tile usually fails AA — add a scrim or pick a pre-paired fill, don't hand-tune the foreground.
53
+ - **Empty states are still token-true surfaces** — illustration + token-driven CTA; no off-scale spacing for the artwork.
54
+ - Dark parity applies to every media surface — verify covers and tiles under `resolved.dark.json` too.
55
+ - Source mixes EN/KR; demo strings render English per AGENTS.md rule 7.
@@ -0,0 +1,55 @@
1
+ # Chorus reconstruction prompt
2
+
3
+ Paste the block below into a Chorus-aware agent (Lovable etc.) to rebuild ad-hoc,
4
+ agent-invented UI as pure Chorus. Strongest with `LOVABLE.md` loaded as the system
5
+ prompt — this block is a thin **driver**; the full rules live in `LOVABLE.md` and the
6
+ guides it tells the agent to read, so the prompt stays short (lower token cost).
7
+
8
+ ---
9
+
10
+ ```text
11
+ Chorus reconstruction pass — rebuild every ad-hoc component as pure Chorus.
12
+
13
+ GOAL: every hand-built component (raw div/button, Tailwind, shadcn, inline styles, hex)
14
+ becomes pure Chorus. No mixed renders — a screen is 100% Chorus or not done. The old UI
15
+ is the source of a migration to @teamblind-chorus/ui + @teamblind-chorus/tokens; don't preserve or
16
+ "match" it — the old style is the bug.
17
+
18
+ FIRST read node_modules/@teamblind-chorus/ui/agents/: catalog.md, manifest.json, compose.md,
19
+ anti-patterns.md. The component name is not the contract — bindings are in
20
+ components/<family>/<sub>.spec.json.
21
+
22
+ DO, in order:
23
+ 1. INVENTORY every non-Chorus component in src/ (one line each: file → what it is).
24
+ 2. MAP each intent via catalog.md to a family + sub. If none fits, climb the ladder
25
+ (recompose slots → LEGO-combine → new primitive with every value a var(--sys-*)),
26
+ then flag a one-line "Chorus gap". Never fall back to raw HTML/Tailwind/shadcn.
27
+ 3. REBUILD with the real import from @teamblind-chorus/ui (icons from @teamblind-chorus/ui/icons),
28
+ honoring the spec; then delete the old component + its CSS.
29
+
30
+ NON-NEGOTIABLE (else discard + regenerate):
31
+ - Tokens only: no hex, no Tailwind colors, no off-scale px; card edge = inset shadow,
32
+ never border:.
33
+ - Typography = className="sys-typo-<role>-<rung>" (NO `font: var(--sys-typo-*)` token).
34
+ - Compound children: <Tabs> needs <Tab>; List/SuggestionList/AvatarRail take an items
35
+ array — never bare text.
36
+ - One gutter at the shell; full-bleed children (Tabs/Feed/List/Carousel/bars) never
37
+ wrapped in a padded div. Floating action = <Button variant="fab">, not a pinned
38
+ standard button. Fixed bars via .page-shell (height:100dvh + <main> overflow-y:auto).
39
+
40
+ VERIFY: eslint.config.js extends @teamblind-chorus/ui/eslint — fix every chorus/* error (never
41
+ suppress); run the §E pre-flight checklist + rail self-diagnostic (anti-patterns.md).
42
+
43
+ SCOPE (optional): "SCOPE: <area/route globs>" limits the pass to that boundary —
44
+ inventory, rebuild, and lint only inside it; outside files are reported, never edited
45
+ (boundary rules: agents/scoped-adoption.md).
46
+
47
+ DELIVER: a before→after table (component → Chorus family/sub, or "gap") and screens with
48
+ zero mixed renders. If scope is large, reconstruct the entry screen first, list the rest.
49
+ ```
50
+
51
+ ---
52
+
53
+ The ESLint preset (`@teamblind-chorus/ui/eslint`, wired in the VERIFY step) is the safety net:
54
+ even if the model drops a rule mid-session, `chorus/*` errors fail the build and its own
55
+ fix loop catches the drift.