@teamblind-chorus/ui 1.0.0 → 1.1.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 (105) hide show
  1. package/agents/AGENTS.md +4 -6
  2. package/agents/DESIGN.md +2 -0
  3. package/agents/LOVABLE.md +167 -373
  4. package/agents/anti-patterns.md +2 -2
  5. package/agents/catalog.md +7 -3
  6. package/agents/components/avatar-rail/avatar-rail.md +2 -0
  7. package/agents/components/badge/badge.md +2 -0
  8. package/agents/components/badge/role.md +2 -0
  9. package/agents/components/badge/update.md +2 -0
  10. package/agents/components/banner/banner.md +72 -9
  11. package/agents/components/banner/banner.spec.json +40 -2
  12. package/agents/components/bottom-sheet/bottom-sheet.md +2 -0
  13. package/agents/components/bubble/bubble.md +2 -0
  14. package/agents/components/button/button.family.json +8 -2
  15. package/agents/components/button/button.md +2 -0
  16. package/agents/components/button/check.md +2 -0
  17. package/agents/components/button/fab.md +2 -0
  18. package/agents/components/button/group.spec.json +65 -0
  19. package/agents/components/button/icon.md +2 -0
  20. package/agents/components/button/standard.md +45 -19
  21. package/agents/components/button/text.md +2 -0
  22. package/agents/components/button/toggle.md +2 -0
  23. package/agents/components/button/toolbar.md +2 -0
  24. package/agents/components/carousel/carousel.md +2 -0
  25. package/agents/components/carousel/post.md +5 -3
  26. package/agents/components/carousel/post.spec.json +4 -6
  27. package/agents/components/carousel/profile.md +4 -2
  28. package/agents/components/carousel/profile.spec.json +4 -6
  29. package/agents/components/chip/chip.md +2 -0
  30. package/agents/components/chip/filter.md +2 -0
  31. package/agents/components/chip/tag.md +2 -0
  32. package/agents/components/dialog/dialog.md +2 -0
  33. package/agents/components/directory-list/directory-list.md +2 -0
  34. package/agents/components/divider/divider.md +2 -0
  35. package/agents/components/feed/ad.md +2 -0
  36. package/agents/components/feed/feed.md +2 -0
  37. package/agents/components/feed/post.md +2 -0
  38. package/agents/components/form-field/form-field.md +3 -1
  39. package/agents/components/form-field/input.md +2 -0
  40. package/agents/components/form-field/input.spec.json +2 -1
  41. package/agents/components/form-field/search.md +2 -0
  42. package/agents/components/form-field/search.spec.json +2 -1
  43. package/agents/components/form-field/select.md +2 -0
  44. package/agents/components/form-field/textarea.md +2 -0
  45. package/agents/components/form-field/textarea.spec.json +2 -1
  46. package/agents/components/header/header.md +2 -0
  47. package/agents/components/header/main.md +2 -0
  48. package/agents/components/header/sub.md +2 -0
  49. package/agents/components/list/accordion.md +2 -0
  50. package/agents/components/list/entry.md +2 -0
  51. package/agents/components/list/entry.spec.json +2 -1
  52. package/agents/components/list/list.md +3 -1
  53. package/agents/components/list/radio.md +2 -0
  54. package/agents/components/list/standard.md +2 -0
  55. package/agents/components/list/standard.spec.json +2 -1
  56. package/agents/components/metadata/compact.md +13 -7
  57. package/agents/components/metadata/compact.spec.json +19 -6
  58. package/agents/components/metadata/metadata.family.json +3 -3
  59. package/agents/components/metadata/metadata.md +4 -2
  60. package/agents/components/metadata/standard.md +24 -0
  61. package/agents/components/nav-card/nav-card.md +2 -0
  62. package/agents/components/nav-list/nav-list.md +2 -0
  63. package/agents/components/navigation-bar/main.md +2 -0
  64. package/agents/components/navigation-bar/navigation-bar.md +2 -0
  65. package/agents/components/navigation-bar/search.md +2 -0
  66. package/agents/components/navigation-bar/sub.md +2 -0
  67. package/agents/components/page-shell/page-shell.md +2 -0
  68. package/agents/components/pagination/pagination.family.json +26 -0
  69. package/agents/components/pagination/pagination.md +40 -0
  70. package/agents/components/pagination/pagination.spec.json +54 -0
  71. package/agents/components/profile-header/profile-header.md +2 -0
  72. package/agents/components/progress/progress.md +2 -0
  73. package/agents/components/side-sheet/side-sheet.md +2 -0
  74. package/agents/components/skeleton/skeleton.md +2 -0
  75. package/agents/components/status-tag/status-tag.md +2 -0
  76. package/agents/components/suggestion-list/suggestion-list.md +2 -0
  77. package/agents/components/switch/switch.md +2 -0
  78. package/agents/components/tab-bar/tab-bar.md +2 -0
  79. package/agents/components/tabs/rounded.md +2 -0
  80. package/agents/components/tabs/segmented.md +2 -0
  81. package/agents/components/tabs/tabs.md +2 -0
  82. package/agents/components/tabs/underline.md +2 -0
  83. package/agents/components/thumbnail/thumbnail.md +2 -0
  84. package/agents/components/toast/toast.md +2 -0
  85. package/agents/components/tooltip/tooltip.md +2 -0
  86. package/agents/compose.md +3 -3
  87. package/agents/manifest.json +1 -0
  88. package/agents/patterns/README.md +2 -0
  89. package/agents/patterns/actions.md +2 -0
  90. package/agents/patterns/browsing.md +2 -0
  91. package/agents/patterns/communications.md +2 -0
  92. package/agents/patterns/layout.md +2 -0
  93. package/agents/patterns/modals.md +2 -0
  94. package/agents/patterns/visual.md +2 -0
  95. package/agents/usage.json +15 -3
  96. package/dist/index.cjs +95 -39
  97. package/dist/index.cjs.map +1 -1
  98. package/dist/index.d.cts +28 -1
  99. package/dist/index.d.ts +28 -1
  100. package/dist/index.js +94 -40
  101. package/dist/index.js.map +1 -1
  102. package/dist/styles.css +183 -41
  103. package/package.json +2 -3
  104. package/agents/reconstruct.md +0 -55
  105. package/agents/scoped-adoption.md +0 -111
package/dist/styles.css CHANGED
@@ -222,6 +222,65 @@ textarea {
222
222
  opacity: 0;
223
223
  }
224
224
 
225
+ /* ------------------------------------------------------------
226
+ ButtonGroup — Buttons composed as one block. The row carries the
227
+ family's 8px (`sys.layout.inline.md`) gap; an optional label stacks
228
+ above it. The docked form adds the footer-bar chrome (surface fill,
229
+ 16px inset, upward `sys.elevation.sheet` shadow). See ButtonGroup.jsx
230
+ and schema/components/button/standard.md.
231
+ ------------------------------------------------------------ */
232
+ .chorus-button-group {
233
+ display: flex;
234
+ flex-direction: column;
235
+ }
236
+
237
+ .chorus-button-group__row {
238
+ display: flex;
239
+ gap: var(--sys-layout-inline-md);
240
+ }
241
+
242
+ /* Vertical inline group — full-width Buttons stacked (primary over
243
+ secondary). Same 8px gap, now on the block axis. */
244
+ .chorus-button-group--vertical .chorus-button-group__row {
245
+ flex-direction: column;
246
+ align-items: stretch;
247
+ }
248
+
249
+ /* Optional caption above the row — `sys.typo.body.md` (16px) in the muted
250
+ on-surface tone, centered, 16px (`sys.layout.stack.md`) above the row. An
251
+ inline <strong> reads as the emphasized value in the full-strength tone. */
252
+ .chorus-button-group__label {
253
+ text-align: center;
254
+ margin-bottom: var(--sys-layout-stack-md);
255
+ color: var(--sys-color-onSurfaceVariant);
256
+ font-size: var(--sys-typo-body-md-size);
257
+ font-weight: var(--sys-typo-body-md-weight);
258
+ line-height: var(--sys-typo-body-md-line);
259
+ letter-spacing: var(--sys-typo-body-md-tracking);
260
+ }
261
+
262
+ .chorus-button-group__label strong {
263
+ color: var(--sys-color-onSurface);
264
+ }
265
+
266
+ /* Docked footer bar — full-bleed surface pinned to the bottom of the app.
267
+ No top stroke; the upward `sys.elevation.sheet` shadow (same one
268
+ BottomSheet / SideSheet cast) lifts it off the scrolling body so content
269
+ passing behind reads as a separate region. Renders in flow — PageShell
270
+ owns the pin, so the bar must NOT set position: sticky/fixed itself. */
271
+ .chorus-button-group--docked {
272
+ background: var(--sys-color-surface);
273
+ padding: var(--sys-layout-container-md);
274
+ box-shadow: var(--sys-elevation-sheet);
275
+ }
276
+
277
+ /* In the docked bar the two Buttons split the row equally, so the consumer
278
+ drops in two `<Button size="large">` without wiring `fullWidth`. */
279
+ .chorus-button-group--docked .chorus-button-group__row > * {
280
+ flex: 1 1 0;
281
+ min-width: 0;
282
+ }
283
+
225
284
  /* ============================================================
226
285
  FAB
227
286
  ============================================================ */
@@ -1057,6 +1116,8 @@ a.chorus-metadata__name:focus-visible {
1057
1116
  timestamp. Single text line — the row never wraps; identity links
1058
1117
  truncate before the timestamp gives way. */
1059
1118
  .chorus-metadata--compact .chorus-metadata__meta {
1119
+ flex: 1 1 auto;
1120
+ min-width: 0;
1060
1121
  flex-wrap: nowrap;
1061
1122
  white-space: nowrap;
1062
1123
  }
@@ -1074,6 +1135,9 @@ a.chorus-metadata__name:focus-visible {
1074
1135
 
1075
1136
  .chorus-metadata--compact .chorus-metadata__timestamp {
1076
1137
  flex: 0 0 auto;
1138
+ /* No leading middot — pay the name↔time gap directly (matches the
1139
+ Standard head's 8px primary-line gap). */
1140
+ margin-inline-start: var(--sys-layout-inline-md);
1077
1141
  }
1078
1142
 
1079
1143
  /* ============================================================
@@ -1248,7 +1312,7 @@ a.chorus-metadata__name:focus-visible {
1248
1312
  gap: var(--sys-layout-inline-sm);
1249
1313
  }
1250
1314
  .chorus-feed__poll-glyph { color: var(--sys-color-brand); display: inline-flex; }
1251
- .chorus-feed__poll-label { color: var(--sys-color-brand); font-weight: 700; }
1315
+ .chorus-feed__poll-label { color: var(--sys-color-brand); font-weight: var(--ref-fontWeight-bold); }
1252
1316
 
1253
1317
  /* Offer-evaluation tone — same chrome as the poll banner, but the
1254
1318
  leading glyph and label paint in `sys.color.success` (resolves to
@@ -1266,7 +1330,7 @@ a.chorus-metadata__name:focus-visible {
1266
1330
  background: var(--sys-color-outlineVariant);
1267
1331
  }
1268
1332
  .chorus-feed__poll-participants { color: var(--sys-color-onSurface); }
1269
- .chorus-feed__poll-participants strong { font-weight: 700; }
1333
+ .chorus-feed__poll-participants strong { font-weight: var(--ref-fontWeight-bold); }
1270
1334
 
1271
1335
  .chorus-feed__citation {
1272
1336
  /* Citation is one bordered surface: the hero and the text column sit
@@ -1831,23 +1895,12 @@ a.chorus-metadata__name:focus-visible {
1831
1895
  color: currentColor;
1832
1896
  }
1833
1897
 
1834
- .chorus-post-carousel__pagination {
1835
- display: flex;
1836
- align-items: center;
1837
- justify-content: center;
1838
- gap: var(--sys-layout-inline-sm);
1839
- }
1840
-
1841
- .chorus-post-carousel__dot {
1842
- width: var(--ref-space-75);
1843
- height: var(--ref-space-75);
1844
- border-radius: var(--sys-radius-full);
1845
- background: var(--sys-color-outlineVariant);
1846
- display: block;
1847
- }
1848
-
1849
- .chorus-post-carousel__dot--active {
1850
- background: var(--sys-color-onSurface);
1898
+ /* Pagination dots below the pager are the shared Pagination component
1899
+ (.chorus-pagination) — no carousel-local dot rules. The component is
1900
+ an intrinsic-width inline element, so the carousel (column flex,
1901
+ align-items stretch) centers it itself. */
1902
+ .chorus-post-carousel > .chorus-pagination {
1903
+ align-self: center;
1851
1904
  }
1852
1905
 
1853
1906
  /* ============================================================
@@ -2078,23 +2131,12 @@ a.chorus-metadata__name:focus-visible {
2078
2131
  justify-content: center;
2079
2132
  }
2080
2133
 
2081
- .chorus-profile-carousel__pagination {
2082
- display: flex;
2083
- align-items: center;
2084
- justify-content: center;
2085
- gap: var(--sys-layout-inline-sm);
2086
- }
2087
-
2088
- .chorus-profile-carousel__dot {
2089
- width: var(--ref-space-75);
2090
- height: var(--ref-space-75);
2091
- border-radius: var(--sys-radius-full);
2092
- background: var(--sys-color-outlineVariant);
2093
- display: block;
2094
- }
2095
-
2096
- .chorus-profile-carousel__dot--active {
2097
- background: var(--sys-color-onSurface);
2134
+ /* Pagination dots below the pager are the shared Pagination component
2135
+ (.chorus-pagination) — no carousel-local dot rules. The component is
2136
+ an intrinsic-width inline element, so the carousel (column flex,
2137
+ align-items stretch) centers it itself. */
2138
+ .chorus-profile-carousel > .chorus-pagination {
2139
+ align-self: center;
2098
2140
  }
2099
2141
 
2100
2142
  /* ============================================================
@@ -2437,11 +2479,20 @@ a.chorus-metadata__name:focus-visible {
2437
2479
  pointer-events: none;
2438
2480
  }
2439
2481
 
2440
- .chorus-list__row:hover {
2482
+ /* Nested-action scope — a trailing slot carrying an independent action
2483
+ (a Follow / mute / favorite button, an overflow control) is marked
2484
+ `data-nested-action` and already stops click/key propagation so it never
2485
+ commits the row's primary action. The row's hover / press overlay is
2486
+ suppressed while the pointer sits on that action so the large row does
2487
+ NOT read as hovered / pressed at the same time the small control does —
2488
+ the visual state boundary matches the event boundary. The decorative
2489
+ nav chevron carries no `data-nested-action`, so hovering it still lights
2490
+ the row (it IS the row's drill-in affordance). */
2491
+ .chorus-list__row:hover:not(:has([data-nested-action]:hover)) {
2441
2492
  background: color-mix(in srgb, var(--sys-color-onSurface) calc(var(--sys-state-hover) * 100%), transparent);
2442
2493
  }
2443
2494
 
2444
- .chorus-list__row:active {
2495
+ .chorus-list__row:active:not(:has([data-nested-action]:active)) {
2445
2496
  background: color-mix(in srgb, var(--sys-color-onSurface) calc(var(--sys-state-pressed) * 100%), transparent);
2446
2497
  }
2447
2498
 
@@ -3241,6 +3292,7 @@ a.chorus-metadata__name:focus-visible {
3241
3292
  .chorus-banner--accent {
3242
3293
  background: var(--sys-color-primaryContainer);
3243
3294
  color: var(--sys-color-onPrimaryContainer);
3295
+ --banner-outline-color: color-mix(in srgb, var(--sys-color-primary) 40%, transparent);
3244
3296
  }
3245
3297
 
3246
3298
  /* Destructive — error-tinted banner. Reach for it when the aside is a
@@ -3251,6 +3303,7 @@ a.chorus-metadata__name:focus-visible {
3251
3303
  .chorus-banner--destructive {
3252
3304
  background: var(--sys-color-errorContainer);
3253
3305
  color: var(--sys-color-onErrorContainer);
3306
+ --banner-outline-color: color-mix(in srgb, var(--sys-color-error) 40%, transparent);
3254
3307
  }
3255
3308
 
3256
3309
  /* Default uses `sys.color.scrimSubtle` (~8% inverse-tone overlay —
@@ -3264,6 +3317,21 @@ a.chorus-metadata__name:focus-visible {
3264
3317
  .chorus-banner--default {
3265
3318
  background: var(--sys-color-scrimSubtle);
3266
3319
  color: var(--sys-color-onSurface);
3320
+ --banner-outline-color: var(--sys-color-outlineVariant);
3321
+ }
3322
+
3323
+ /* Optional outline — a hairline inset stroke toned to the appearance's
3324
+ color family (each appearance block above sets
3325
+ `--banner-outline-color`), kept deliberately faint so the edge reads
3326
+ as a soft boundary of the same tint, not a frame: the subtle gray
3327
+ hairline (`sys.color.outlineVariant`) on default's gray-tinted
3328
+ scrim; `primary` / `error` at 40% over transparent (the Skeleton
3329
+ fill recipe) on accent / destructive. Painted as an inset
3330
+ box-shadow, never a `border`, so toggling it can't change the
3331
+ banner's footprint — same idiom as Chip (see DESIGN.md → Border &
3332
+ Stroke). */
3333
+ .chorus-banner--outlined {
3334
+ box-shadow: inset 0 0 0 var(--sys-borderWidth-hairline) var(--banner-outline-color);
3267
3335
  }
3268
3336
 
3269
3337
  /* Leading icon slot — a 16 × 16 glyph (`sys.icon.md`) that paints in
@@ -3306,6 +3374,23 @@ a.chorus-metadata__name:focus-visible {
3306
3374
  gap: var(--sys-layout-stack-xs);
3307
3375
  }
3308
3376
 
3377
+ /* Optional heading line above the body — label.md (14 / Semibold) in
3378
+ the container's foreground. Title↔body wants the tighter
3379
+ `sys.layout.stack.2xs` (4) so the pair reads as one passage, while
3380
+ the content column's gap stays `stack.xs` (8) for body↔action; the
3381
+ negative margin nets the column gap down to 4 for this pair only. */
3382
+ .chorus-banner__title {
3383
+ margin: 0;
3384
+ font-size: var(--sys-typo-label-md-size);
3385
+ line-height: var(--sys-typo-label-md-line);
3386
+ font-weight: var(--sys-typo-label-md-weight);
3387
+ color: inherit;
3388
+ }
3389
+
3390
+ .chorus-banner__title + .chorus-banner__body {
3391
+ margin-top: calc(var(--sys-layout-stack-2xs) - var(--sys-layout-stack-xs));
3392
+ }
3393
+
3309
3394
  .chorus-banner__body {
3310
3395
  margin: 0;
3311
3396
  font-size: var(--sys-typo-body-sm-size);
@@ -3332,6 +3417,28 @@ a.chorus-metadata__name:focus-visible {
3332
3417
  color: var(--sys-color-primary);
3333
3418
  }
3334
3419
 
3420
+ /* Trailing icon slot — a 16 × 16 glyph (`sys.icon.md`) at the trailing
3421
+ edge, vertically centered against the whole block (`align-self:
3422
+ center` overrides the container's flex-start). Paints in the
3423
+ banner's foreground (`currentColor`); typically a forward affordance
3424
+ (e.g. ForwardCircleFillIcon) signaling the aside leads somewhere. */
3425
+ .chorus-banner__trailing-icon {
3426
+ flex: 0 0 var(--sys-icon-md);
3427
+ align-self: center;
3428
+ display: inline-flex;
3429
+ align-items: center;
3430
+ justify-content: center;
3431
+ color: currentColor;
3432
+ }
3433
+
3434
+ .chorus-banner__trailing-icon img,
3435
+ .chorus-banner__trailing-icon svg {
3436
+ width: var(--sys-icon-md);
3437
+ height: var(--sys-icon-md);
3438
+ display: block;
3439
+ color: currentColor;
3440
+ }
3441
+
3335
3442
  /* ============================================================
3336
3443
  Divider — section-break band between adjacent regions that
3337
3444
  don't share an enclosing container. Single full-bleed block
@@ -3715,14 +3822,21 @@ a.chorus-metadata__name:focus-visible {
3715
3822
  /* Hover / pressed re-tone the 1px stroke to `borderHover` (`outline`,
3716
3823
  or `error` on the error appearance). `:active` also lights the
3717
3824
  state-overlay `::before`. */
3718
- .chorus-field:hover,
3825
+ /* The trailing clear button is an independent nested action (it wipes the
3826
+ value, distinct from focusing / pressing the field) — marked
3827
+ `data-nested-action`. While the pointer hovers / presses it, the field's
3828
+ own hover stroke and pressed overlay are suppressed so the large field
3829
+ does NOT read as pressed at the same time the small "×" does. The Select
3830
+ chevron is NOT marked: it fires the same action as clicking the field
3831
+ (open), so it correctly lights the field. */
3719
3832
  .chorus-field[data-force-state="hovered"],
3720
3833
  .chorus-field[data-force-state="pressed"],
3721
- .chorus-field:not(.is-disabled):active {
3834
+ .chorus-field:hover:not(:has([data-nested-action]:hover)),
3835
+ .chorus-field:not(.is-disabled):active:not(:has([data-nested-action]:active)) {
3722
3836
  box-shadow: inset 0 0 0 var(--field-border-width) var(--field-border-hover);
3723
3837
  }
3724
3838
 
3725
- .chorus-field:not(.is-disabled):active::before,
3839
+ .chorus-field:not(.is-disabled):active:not(:has([data-nested-action]:active))::before,
3726
3840
  .chorus-field[data-force-state="pressed"]::before {
3727
3841
  opacity: var(--field-overlay-pressed);
3728
3842
  }
@@ -5225,6 +5339,34 @@ a.chorus-metadata__name:focus-visible {
5225
5339
  transform: rotate(45deg);
5226
5340
  }
5227
5341
 
5342
+ /* ============================================================
5343
+ Pagination — decorative dot-position indicator
5344
+ ============================================================ */
5345
+ /* One 6px dot per page in an inline.sm row. An inline element —
5346
+ inline-flex, intrinsic width (no stretch, no self-centering); the
5347
+ host owns horizontal placement. Active dot paints `onSurface`, the
5348
+ rest `outlineVariant`. Non-interactive (`aria-hidden` on the root) —
5349
+ the host pager owns the active index and keyboard reach.
5350
+ See schema/components/pagination/pagination.md. */
5351
+
5352
+ .chorus-pagination {
5353
+ display: inline-flex;
5354
+ align-items: center;
5355
+ gap: var(--sys-layout-inline-sm);
5356
+ }
5357
+
5358
+ .chorus-pagination__dot {
5359
+ width: var(--ref-space-75);
5360
+ height: var(--ref-space-75);
5361
+ border-radius: var(--sys-radius-full);
5362
+ background: var(--sys-color-outlineVariant);
5363
+ display: block;
5364
+ }
5365
+
5366
+ .chorus-pagination__dot--active {
5367
+ background: var(--sys-color-onSurface);
5368
+ }
5369
+
5228
5370
  /* ============================================================
5229
5371
  Progress — linear progress bar (determinate)
5230
5372
  ============================================================ */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamblind-chorus/ui",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Chorus React components. Ships prebuilt ESM + CJS bundles (`dist/`) and a single `styles.css`; import `@teamblind-chorus/tokens/tokens.css` + `@teamblind-chorus/ui/styles.css` once at the app entry. The contract every component honors lives in schema/components/<family>/<sub>.spec.json; see schema/manifest.json for the inventory.",
5
5
  "license": "MIT",
6
6
  "author": "Teamblind, Inc.",
@@ -54,7 +54,6 @@
54
54
  "./agents/usage.json": "./agents/usage.json",
55
55
  "./agents/DESIGN.md": "./agents/DESIGN.md",
56
56
  "./agents/LOVABLE.md": "./agents/LOVABLE.md",
57
- "./agents/reconstruct.md": "./agents/reconstruct.md",
58
57
  "./agents/images.md": "./agents/images.md",
59
58
  "./agents/patterns/": "./agents/patterns/",
60
59
  "./agents/components/": "./agents/components/",
@@ -83,7 +82,7 @@
83
82
  "react-dom": ">=18"
84
83
  },
85
84
  "dependencies": {
86
- "@teamblind-chorus/tokens": "^1.0.0"
85
+ "@teamblind-chorus/tokens": "^1.0.1"
87
86
  },
88
87
  "publishConfig": {
89
88
  "access": "public"
@@ -1,55 +0,0 @@
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.
@@ -1,111 +0,0 @@
1
- # Scoped adoption — Chorus in a designated area of an existing app
2
-
3
- Protocol for the third first-turn branch in `LOVABLE.md`: the user wants Chorus in a
4
- **specific area** of an in-progress project — not a full migration (§D), not a blank
5
- scaffold (greenfield). Triggers: the first message names a target area / route /
6
- feature ("apply Chorus to the settings tab", "the community feed uses Chorus"), or
7
- says to initialize and **stand by** for an area they'll designate next.
8
-
9
- Everything in `LOVABLE.md` still applies *inside* the designated area. This file only
10
- defines what changes **at and outside the boundary**.
11
-
12
- ## First-turn behaviour
13
-
14
- 1. Run §A.0 end-to-end (install, stylesheets, placeholder, lint preset — scoped, see
15
- below) and post the readiness line.
16
- 2. Post a drift report **scoped to the designated area** (same shape as §D.1 — counts
17
- + worst offenders — but only for files the area renders). If no area is named yet,
18
- skip the report and say so in one line.
19
- 3. **Stand by.** Do NOT reconstruct the entry screen, do NOT post an app-wide
20
- migration plan, do NOT touch files outside the area. The §D "reconstruct the
21
- representative screen immediately" step is **suspended** in this mode — the
22
- reconstruction target is the designated area, on the user's brief.
23
-
24
- ## The boundary — declare it, then respect it
25
-
26
- * **Declare the Chorus boundary at route / screen granularity** whenever possible.
27
- State it explicitly in your first scoped reply: *"Chorus boundary: `/community/*`
28
- (CommunityPage + children)."* Everything inside renders 100% Chorus; everything
29
- outside stays untouched — visually and in code.
30
- * **The zero-mixed-render rule applies per screen *inside* the boundary.** A screen
31
- inside the boundary is 100% Chorus or not done. Screens outside the boundary are
32
- out of scope — their drift is *reported, never edited*.
33
- * **Sub-screen areas.** If the designated area is a region within a screen that also
34
- has legacy UI (e.g. "only the comment section"), first propose widening the boundary
35
- to the whole screen — that's the only way to honor zero-mixed-render literally. If
36
- the user declines, the area's **container element is the declared boundary**: inside
37
- it 100% Chorus, outside it untouched, and you state the exception in one line
38
- ("mixed render sanctioned at `<CommentSection>` per scoped boundary"). Never
39
- restyle legacy siblings "for consistency".
40
- * **§D.4 neighbor migration is capped at the boundary.** "Touched files AND immediate
41
- visual neighbors" never drags in files outside the declared area. Out-of-boundary
42
- drift goes on the next-PR shopping list (§D.5), nothing more.
43
- * **Widening is the user's call.** They can grow the boundary ("now also the profile
44
- page") or switch to full §D at any time. You may *suggest* widening when the
45
- boundary forces an awkward seam; you never widen unilaterally.
46
-
47
- ## Embedded host shell — who pays the gutter
48
-
49
- The §A.4 PageShell contract assumes Chorus owns the viewport. In scoped adoption it
50
- usually doesn't:
51
-
52
- * **Area = whole route/screen** → use `<PageShell>` normally inside that route.
53
- * **Area = region embedded in a legacy frame** → do NOT install `<PageShell>` (a
54
- 100dvh flex column inside a host layout breaks the host's scroll). Instead, treat
55
- the **legacy container as the shell**: it pays the horizontal gutter **once**, and
56
- Chorus full-bleed children (`List`, `Feed`, `Carousel`, `Tabs`, …) are its direct
57
- children with no extra `padding-inline` / `px-*` wrapper.
58
- * If the legacy container already pays its own padding, that *is* the single
59
- gutter — do not re-pay it on a Chorus wrapper.
60
- * If it pays none, add it once at the area root:
61
- `style={{ paddingInline: 'var(--sys-layout-page-md)' }}`.
62
- * The rail self-diagnostic (LOVABLE.md § rail) still applies *within the area*: every
63
- full-bleed child's left edge must align to the area's single gutter.
64
-
65
- ## ESLint preset — scope it to the area
66
-
67
- `export default [ ...chorus ]` repo-wide would flood an unmigrated codebase with
68
- `chorus/*` errors and push you toward either suppressing (forbidden) or migrating
69
- everything (scope violation). Scope the preset with flat-config `files`:
70
-
71
- ```js
72
- // eslint.config.js
73
- import chorus from "@teamblind-chorus/ui/eslint";
74
- export default [
75
- ...chorus.map((cfg) => ({
76
- ...cfg,
77
- files: ["src/pages/community/**", "src/components/community/**"], // = the declared boundary
78
- })),
79
- ];
80
- ```
81
-
82
- * "Chorus lint green" (§E checklist) means green **within the scoped `files` globs**.
83
- * When the user widens the boundary, widen the globs in the same change — the lint
84
- scope and the declared boundary must never disagree.
85
- * Never silence a `chorus/*` error inside the boundary; outside the boundary the
86
- rules simply don't run.
87
-
88
- ## CSS coexistence — safe by construction
89
-
90
- Importing `@teamblind-chorus/tokens/tokens.css` + `@teamblind-chorus/ui/styles.css` app-wide is
91
- safe for the untouched legacy area. The only global effects are:
92
-
93
- * a `button/input/select/textarea { font-family: inherit }` reset (form controls
94
- inherit the body font — benign, usually invisible);
95
- * `:root` custom properties (`--sys-*`, `--ref-*`, `--chorus-placeholder-image`) —
96
- inert until something consumes them;
97
- * `.chorus-*` / `.sys-typo-*` classes — inert on legacy markup.
98
-
99
- The Pretendard `<link>` loads a font; it restyles nothing until a rule asks for it.
100
- So: import the stylesheets once at app entry as §A.0 says — do NOT try to
101
- conditionally load CSS per route, and do not warn the user about "global conflicts".
102
-
103
- ## Exit paths
104
-
105
- * **Widen** — user names more areas; repeat the boundary declaration, widen the lint
106
- globs, migrate the new area.
107
- * **Graduate to §D** — user asks for the full conversion; run §D from step 1 with the
108
- already-migrated areas as the reference target (skip §D.3's proactive
109
- reconstruction — it exists).
110
- * **Escape hatch (§D.7)** still works as documented: "just add the feature" demotes
111
- everything to a drift note; new code stays pure Chorus.