@nanisoft/prism-llms 0.3.0 → 0.5.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 (175) hide show
  1. package/README.md +88 -0
  2. package/THIRD-PARTY-NOTICES.md +48 -0
  3. package/dist/data.json +3998 -375
  4. package/dist/demo-graph.d.ts +21 -12
  5. package/dist/demo-graph.js +37 -21
  6. package/dist/extractor.d.ts +24 -17
  7. package/dist/extractor.js +269 -153
  8. package/dist/index.d.ts +12 -7
  9. package/dist/index.js +9 -7
  10. package/dist/llms-full.txt +7988 -1989
  11. package/dist/llms.txt +97 -91
  12. package/dist/markdown.d.ts +69 -23
  13. package/dist/markdown.js +131 -72
  14. package/dist/md/blocks/app-shell-01.md +135 -0
  15. package/dist/md/blocks/auth-form-01.md +135 -0
  16. package/dist/md/blocks/cta-01.md +88 -0
  17. package/dist/md/blocks/data-table-01.md +196 -0
  18. package/dist/md/blocks/feature-grid-01.md +92 -0
  19. package/dist/md/blocks/hero-01.md +90 -0
  20. package/dist/md/blocks/instrument-panel-01.md +109 -0
  21. package/dist/md/blocks/logo-strip-01.md +97 -0
  22. package/dist/md/blocks/note-grid-01.md +102 -0
  23. package/dist/md/blocks/page-header-01.md +97 -0
  24. package/dist/md/blocks/pricing-01.md +91 -0
  25. package/dist/md/blocks/process-rail-01.md +106 -0
  26. package/dist/md/blocks/product-grid-01.md +113 -0
  27. package/dist/md/blocks/settings-panel-01.md +148 -0
  28. package/dist/md/blocks/site-footer.md +111 -0
  29. package/dist/md/blocks/site-header.md +119 -0
  30. package/dist/md/blocks/stack-grid-01.md +108 -0
  31. package/dist/md/blocks/stats-01.md +89 -0
  32. package/dist/md/blocks/status-ledger-01.md +121 -0
  33. package/dist/md/changelogs/prism-llms.md +206 -0
  34. package/dist/md/changelogs/prism-mcp-server.md +88 -0
  35. package/dist/md/changelogs/prism-tokens.md +126 -0
  36. package/dist/md/changelogs/prism-ui.md +429 -0
  37. package/dist/md/components/accordion.md +108 -0
  38. package/dist/md/components/alert.md +90 -7
  39. package/dist/md/components/avatar.md +89 -7
  40. package/dist/md/components/badge.md +74 -7
  41. package/dist/md/components/breadcrumb.md +84 -7
  42. package/dist/md/components/button.md +90 -7
  43. package/dist/md/components/card.md +90 -7
  44. package/dist/md/components/checkbox.md +101 -7
  45. package/dist/md/components/cta-link.md +98 -0
  46. package/dist/md/components/diagram.md +124 -0
  47. package/dist/md/components/dialog.md +128 -0
  48. package/dist/md/components/dropdown-menu.md +125 -0
  49. package/dist/md/components/fact-list.md +85 -0
  50. package/dist/md/components/field.md +116 -0
  51. package/dist/md/components/input.md +86 -7
  52. package/dist/md/components/kbd.md +77 -0
  53. package/dist/md/components/pagination.md +95 -7
  54. package/dist/md/components/popover.md +95 -7
  55. package/dist/md/components/product-mark.md +123 -0
  56. package/dist/md/components/product-switcher.md +88 -0
  57. package/dist/md/components/progress.md +88 -7
  58. package/dist/md/components/prose.md +91 -0
  59. package/dist/md/components/radio-group.md +111 -0
  60. package/dist/md/components/section.md +91 -0
  61. package/dist/md/components/select.md +143 -7
  62. package/dist/md/components/separator.md +77 -0
  63. package/dist/md/components/skeleton.md +71 -7
  64. package/dist/md/components/slider.md +90 -7
  65. package/dist/md/components/switch.md +104 -7
  66. package/dist/md/components/table.md +104 -7
  67. package/dist/md/components/tabs.md +105 -7
  68. package/dist/md/components/textarea.md +101 -0
  69. package/dist/md/components/tooltip.md +95 -7
  70. package/dist/md/components/typography.md +82 -7
  71. package/dist/md/content/about-content.md +36 -0
  72. package/dist/md/content/audience.md +43 -0
  73. package/dist/md/content/glossary.md +69 -0
  74. package/dist/md/content/product-vocabulary.md +53 -0
  75. package/dist/md/content/voice.md +52 -0
  76. package/dist/md/content/writing-mechanics.md +58 -0
  77. package/dist/md/foundation/colors.md +10 -0
  78. package/dist/md/foundation/iconography.md +14 -0
  79. package/dist/md/foundation/motion.md +10 -0
  80. package/dist/md/foundation/radii.md +9 -0
  81. package/dist/md/foundation/shadows.md +9 -0
  82. package/dist/md/foundation/spacings.md +8 -0
  83. package/dist/md/foundation/typography.md +9 -0
  84. package/dist/md/foundation/variables.md +9 -0
  85. package/dist/md/overview/architecture.md +76 -0
  86. package/dist/md/overview/brand.md +41 -0
  87. package/dist/md/overview/composition.md +77 -0
  88. package/dist/md/overview/quickstart.md +97 -0
  89. package/dist/md/overview/theming.md +89 -0
  90. package/dist/md/overview/using-llms.md +83 -0
  91. package/dist/md/pages/auth-page.md +149 -0
  92. package/dist/md/pages/blog-post-page.md +136 -0
  93. package/dist/md/pages/dashboard-page.md +241 -0
  94. package/dist/md/pages/marketing-page.md +199 -0
  95. package/dist/md/pages/not-found-page.md +105 -0
  96. package/dist/md/pages/settings-page.md +206 -0
  97. package/dist/prism-skill.md +50 -0
  98. package/dist/store.d.ts +203 -0
  99. package/dist/store.js +317 -0
  100. package/package.json +29 -26
  101. package/dist/demo-graph.d.ts.map +0 -1
  102. package/dist/demo-graph.js.map +0 -1
  103. package/dist/extractor.d.ts.map +0 -1
  104. package/dist/extractor.js.map +0 -1
  105. package/dist/index.d.ts.map +0 -1
  106. package/dist/index.js.map +0 -1
  107. package/dist/markdown.d.ts.map +0 -1
  108. package/dist/markdown.js.map +0 -1
  109. package/dist/md/blocks/component-demo.md +0 -19
  110. package/dist/md/blocks/page-header.md +0 -19
  111. package/dist/md/components/affix.md +0 -14
  112. package/dist/md/components/anchor.md +0 -14
  113. package/dist/md/components/auto-complete.md +0 -14
  114. package/dist/md/components/back-top.md +0 -14
  115. package/dist/md/components/border-beam.md +0 -14
  116. package/dist/md/components/calendar.md +0 -14
  117. package/dist/md/components/carousel.md +0 -14
  118. package/dist/md/components/cascader.md +0 -14
  119. package/dist/md/components/col.md +0 -14
  120. package/dist/md/components/collapse.md +0 -14
  121. package/dist/md/components/color-picker.md +0 -14
  122. package/dist/md/components/date-picker.md +0 -14
  123. package/dist/md/components/descriptions.md +0 -14
  124. package/dist/md/components/display-title.md +0 -20
  125. package/dist/md/components/divider.md +0 -14
  126. package/dist/md/components/drawer.md +0 -14
  127. package/dist/md/components/dropdown.md +0 -14
  128. package/dist/md/components/empty.md +0 -14
  129. package/dist/md/components/flex.md +0 -14
  130. package/dist/md/components/float-button.md +0 -14
  131. package/dist/md/components/form.md +0 -14
  132. package/dist/md/components/grid.md +0 -14
  133. package/dist/md/components/image.md +0 -14
  134. package/dist/md/components/input-number.md +0 -14
  135. package/dist/md/components/layout.md +0 -14
  136. package/dist/md/components/list.md +0 -14
  137. package/dist/md/components/listy.md +0 -14
  138. package/dist/md/components/masonry.md +0 -14
  139. package/dist/md/components/mentions.md +0 -14
  140. package/dist/md/components/menu.md +0 -14
  141. package/dist/md/components/modal.md +0 -14
  142. package/dist/md/components/popconfirm.md +0 -14
  143. package/dist/md/components/qrcode.md +0 -14
  144. package/dist/md/components/radio.md +0 -14
  145. package/dist/md/components/rate.md +0 -14
  146. package/dist/md/components/result.md +0 -14
  147. package/dist/md/components/row.md +0 -14
  148. package/dist/md/components/segmented.md +0 -14
  149. package/dist/md/components/space.md +0 -14
  150. package/dist/md/components/spin.md +0 -14
  151. package/dist/md/components/splitter.md +0 -14
  152. package/dist/md/components/statistic.md +0 -14
  153. package/dist/md/components/steps.md +0 -14
  154. package/dist/md/components/tag.md +0 -14
  155. package/dist/md/components/time-picker.md +0 -14
  156. package/dist/md/components/timeline.md +0 -14
  157. package/dist/md/components/tour.md +0 -14
  158. package/dist/md/components/transfer.md +0 -14
  159. package/dist/md/components/tree-select.md +0 -14
  160. package/dist/md/components/tree.md +0 -14
  161. package/dist/md/components/upload.md +0 -14
  162. package/dist/md/components/watermark.md +0 -14
  163. package/dist/md/docs/brand.md +0 -41
  164. package/dist/md/pages/blog-layout.md +0 -19
  165. package/dist/md/pages/docs-shell.md +0 -23
  166. package/dist/md/theme/blue-dark.md +0 -127
  167. package/dist/md/theme/blue-light.md +0 -129
  168. package/dist/md/theme/green-dark.md +0 -127
  169. package/dist/md/theme/green-light.md +0 -129
  170. package/dist/md/theme/lavender-dark.md +0 -127
  171. package/dist/md/theme/lavender-light.md +0 -129
  172. package/dist/md/theme/peach-dark.md +0 -127
  173. package/dist/md/theme/peach-light.md +0 -129
  174. package/dist/md/theme/rose-dark.md +0 -127
  175. package/dist/md/theme/rose-light.md +0 -129
@@ -0,0 +1,126 @@
1
+ # @nanisoft/prism-tokens
2
+
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 4f9ce77: A server-rendered pack boundary can now be mode-correct
8
+
9
+ The only dark selector for a pack was `[data-pack="<id>"].dark`, which requires
10
+ the mode class on the same element. A server cannot know the reader's mode, so a
11
+ subtree carrying a second pack rendered in that pack's light values on a dark
12
+ page. The token build now emits the two-member list
13
+
14
+ ```
15
+ [data-pack="<id>"].dark, .dark [data-pack="<id>"]
16
+ ```
17
+
18
+ as one rule with one declaration block, for all five packs. The compound half is
19
+ retained, because it is what a dark surface on a light page is and it is a
20
+ capability the package already published.
21
+
22
+ Consumers get a second change alongside it. `themeAttributes` now takes `mode` as
23
+ optional, because a required one is that same impossibility written into the type:
24
+ the only way to ask for a boundary with no mode class of its own was to pass
25
+ `'light'` as a guess, and the guess is wrong for half of readers. Omitting `mode`
26
+ is the honest spelling of "wear the mode of the element carrying `.dark`". The
27
+ change is a widening, so every existing caller still compiles.
28
+
29
+ The theme-boundary law in `DESIGN.md` is rewritten to match. It previously
30
+ instructed an implementer to put `data-pack` and `class="dark"` on a boundary
31
+ together, which works for a client-applied boundary and cannot work for a
32
+ server-rendered one.
33
+ - 5dcb347: Every colour role now has a contrast row or a stated reason, and there is a brand ink
34
+
35
+ The contrast gate decided what to measure by a naming convention: any root token ending in
36
+ `-foreground` had to be the first element of a pair. Six of the seven roles that were in no row at
37
+ all could not be reported by that rule, because `chart-1` through `chart-5` and `sidebar-border`
38
+ do not end in `-foreground`, and `sidebar-ring` does not either. A new role therefore defaulted to
39
+ silence. The gate now walks the token source instead of the table, so a role nobody has written
40
+ down is a failure rather than an absence, and the role set is read from `src/semantic/` rather than
41
+ from the pair table, or the walk would be checking the table against itself.
42
+
43
+ `sidebar-ring` is what the walk immediately found. It had no row at all, and its shipped values
44
+ measured 2.42:1 in the base pack's light mode, 1.73:1 in its dark mode, 2.86:1 in Mint and 2.97:1
45
+ in Sky against the sidebar surface: a focus indicator four of twelve pack and mode combinations
46
+ could not be seen against, reported as compliant because it was not measured. The values moved, not
47
+ the row. The base pack goes from neutral 400 and neutral 700 to neutral 500 in both modes, because
48
+ the base pack's ring is one ramp rather than two; the pastels go from brand 500 to brand 600 in
49
+ light, because the sidebar is neutral 50 rather than neutral 0 and brand 500 is one step short of
50
+ 3:1 in two packs. The dark pastels keep brand 300 at 8.06:1 or better. `sidebar-border` gains an
51
+ advisory row on the sidebar surface with the same judgement `border` and `input` already carry,
52
+ which is how its 1.18:1 to 1.37:1 became a number on the record rather than an unmeasured value.
53
+
54
+ `chart-1` through `chart-5` are exempted with a reason rather than measured against a ground. A
55
+ series is a graphic object: shadcn consumes the five as the stroke and fill of a series and reads
56
+ its tooltip and legend text from `foreground` and `muted-foreground`, which are each gated against
57
+ their own ground. What binds a five-way set is that no two series resolve to the same value, so
58
+ that is what the gate asserts instead, in every pack and both modes. The measured tightest pair is
59
+ 37/255 in a channel, Peach in dark mode between `chart-1` and `chart-5`. No minimum separation is
60
+ asserted, because a threshold with no standard behind it is a number that gets bent. `radius` is
61
+ exempted as a length rather than a colour, and the exclusion is a declared entry with a reason
62
+ instead of a name filter buried in a loop. Every exemption is printed on every run with the roles
63
+ that share it.
64
+
65
+ **`brand-ink` is a new semantic role: a brand hue read as text.** Three sites want a brand colour
66
+ they can read on a surface and `primary` cannot serve it, because `primary` is a fill: a pastel
67
+ brand 400 measures 2.20:1 to 2.44:1 on its own pack's page and 1.90:1 to 2.05:1 on the accent
68
+ surface. It is brand 700 in light and brand 200 in dark, in all five packs and the base, and it is
69
+ gated at 4.5:1 against three grounds rather than one: the page, a card, and the accent surface.
70
+ The accent is the ground that decides the step in both directions, because a light ink has to
71
+ survive the pack's own brand 100 and a dark ink has to survive brand 800, and brand 600 and brand
72
+ 400 respectively are the steps that fail each. The measured worst case across all six sources, both
73
+ modes and all three grounds is 5.57:1 in Mint's light mode. The dark step is brand 200 rather than
74
+ the brand 300 the accent ground would have permitted, because brand 300 is `primary` in every
75
+ pack's dark mode and a brand ink that is the brand fill is the confusion the role exists to end. A
76
+ test asserts that `brand-ink` never resolves to the same value as `primary`, `primary-foreground`
77
+ or `foreground` in any source or mode.
78
+
79
+ **The two values that were already in the contract are the two answers this gate rules out, and
80
+ both are written into `DESIGN.md` as prohibitions with a number attached.** `primary-foreground` is
81
+ the pack's brand 950 step, in both modes and all five packs, published under three further names in
82
+ light mode, and it is the value a reader of the contract reaches for when they want the brand colour
83
+ as text. It clears its own row by 7.39:1 to 11.06:1 and it measures 1.00:1 to 1.82:1 on the dark
84
+ page ground, the dark card and the dark accent surface. In light mode it reads 15.05:1 to 18.05:1
85
+ on the page, so half the modes it looks right and half it disappears, and no row in the contract
86
+ could see the difference. `ring` is the other: the pack's brand hue as a non-text boundary, gated
87
+ at 3:1 against the page ground alone, and it measures 2.66:1 to 4.35:1 on the accent surface in all
88
+ six light-mode sources. Neither was wrong arithmetically. Both are invisible on the ground a brand
89
+ ink lands on, and both now have a role that is measured there.
90
+
91
+ **A role is measured in both modes or the run fails.** Every colour role, exempt ones included, has
92
+ to resolve to an sRGB value in light and in dark in every pack, so an exemption buys a role freedom
93
+ from a ground and never from a mode. This is what a role present in one mode and absent from the
94
+ other looks like, and the build's own key-set guard does not reach it because it compares light
95
+ against light.
96
+
97
+ The old `-foreground` name test is gone rather than kept beside the walk. It was never a second
98
+ opinion on coverage, which the walk settles, and its only additional content is that a role named
99
+ `*-foreground` must be the foreground of a row rather than the background of one, which is a claim
100
+ about the shadcn naming convention and belongs to the Contract Rule.
101
+
102
+ No consumer action is required. `brand-ink` is additive: it is a new custom property, the stylesheet
103
+ gains a `text-brand-ink` utility, and no existing token changed except `sidebar-ring`, whose value
104
+ moved in the base pack and in the light mode of every pastel. A consumer that styled its own sidebar
105
+ focus ring from `--sidebar-ring` will see a more visible ring, which is the point.
106
+
107
+ ## 0.5.1
108
+
109
+ ### Patch Changes
110
+
111
+ - 2fc6a0a: Retune `muted-foreground` so muted text clears 4.5:1 on the muted surface
112
+
113
+ The base pack's `muted-foreground` moves from neutral 500 to neutral 600. It
114
+ cleared 4.5:1 on the page background but fell to 4.34:1 on `muted`, where the
115
+ pill, avatar fallback, kbd and tab-list pattern place it. The contrast gate now
116
+ also checks `muted-foreground` on `muted`. The pastel packs were already pinned
117
+ to their neutral 700 and are unchanged.
118
+
119
+ ## 0.5.0
120
+
121
+ Prism was rebuilt from a fresh repository on a clean break. The token pipeline is
122
+ DTCG 2025.10 compiled with Style Dictionary, the semantic names keep the shadcn
123
+ contract verbatim, and motion, typography, spacing, shadows, breakpoints and
124
+ containers all reach CSS through a single `@theme static` block.
125
+
126
+ Changesets are appended above this entry for every published change.
@@ -0,0 +1,429 @@
1
+ # @nanisoft/prism-ui
2
+
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 956fc8f: A call to action with a destination renders a link, and an icon is required only where a tile is
8
+
9
+ Every primary call to action in the Block family declared an `href` its type then
10
+ never rendered. The control was a `<button>` with no handler: the type said the
11
+ component could navigate, the documentation implied it, and the markup could not
12
+ do either. Sixteen of them across the family, five of them to another origin.
13
+
14
+ **`CtaLink` is a new Component.** It renders a native `<a>` and carries the
15
+ visual weight of a call to action, so assistive technology announces a link and
16
+ the browser's own affordances all work: a status bar showing the destination, a
17
+ context menu to copy it, middle-click to open a new tab. It is a Component in its
18
+ own right rather than an `as` or `href` prop on `Button`, which is the answer
19
+ `BreadcrumbLink` and `PaginationLink` already give twice in this package. It is a
20
+ server component, so the swap costs no client JavaScript.
21
+
22
+ `href` is required on `CtaLink`: an anchor without one is not a link. `newTab` is
23
+ a declared prop and the only way to open a new browsing context, because `target`
24
+ is not accepted, so the relationship cannot be opted out of by accident. When it
25
+ is set, `rel` defaults to `noopener noreferrer`; a consumer that needs a
26
+ different relationship passes `rel` and it wins. Prism does not parse the
27
+ destination and does not decide what counts as external.
28
+
29
+ **`Cta01` no longer offers an action that navigates nothing.** `Cta01Action`'s
30
+ `href` is required, there is no optional arm and no dead-button arm, and a new
31
+ `actionSlot` takes a control the Block does not own, such as a client router's
32
+ link. The Block makes no guess about cross-origin. The one rendered change in
33
+ this release is a button becoming the link its type always said it was: the link
34
+ is styled from the same recipe as the button it replaces, at every shared variant
35
+ and size, and a test asserts that class-for-class.
36
+
37
+ **`FeatureGrid01`'s icon is required only where a tile is rendered.** The
38
+ requirement is a union discriminated on `variant` rather than an optional prop,
39
+ because an optional prop turns a safe-at-render condition into an unsafe one and
40
+ permits an empty accent tile per feature. The `icon` variant is the one an
41
+ omitted `variant` selects, so the default and the required icon live on the same
42
+ arm; the `bare` variant renders no tile and requires no icon, and the type says
43
+ so rather than the renderer finding out. `IconFeature` and `BareFeature` are
44
+ exported, and the union is the type the site's API table is extracted from.
45
+
46
+ **The version the four downstream site plans pin is `0.6.0`.** It is
47
+ `@nanisoft/prism-ui@0.6.0`, and `@nanisoft/prism-tokens@0.6.0` with it, because the
48
+ two are linked in `.changeset/config.json` and the pending
49
+ `a-server-rendered-pack-boundary-can-be-mode-correct` changeset already declares a
50
+ minor for both from `0.5.1`. This one declares a minor as well and so does not
51
+ raise it.
52
+
53
+ Migration: a `Cta01` action needs a destination. Where the action was a label
54
+ alone, give it the `href` it was always declared with, or move the control into
55
+ the new `actionSlot`. A `FeatureGrid01` in the `bare` variant can now drop the
56
+ `icon` its type used to demand; the `icon` variant still demands one per feature.
57
+ - d001983: A diagram is a Component, and a product's mark is one item rather than two copies
58
+
59
+ Three of the four product sites draw something in the position where an empty box
60
+ is most visible, and all of it was a canvas. A canvas reads computed style from
61
+ one element while a pack is an attribute on an ancestor, so a scoped `data-pack`
62
+ boundary hands it the pack's light values on a dark page, and no token gate can
63
+ see it: the value the drawing holds is a legal token value, applied to the wrong
64
+ pack. Cutting the canvases without a replacement is the alternative, and it
65
+ leaves three heroes with an empty slot.
66
+
67
+ `Diagram` draws named things and the labelled relations between them as inline
68
+ vector markup. Every stroke and every fill is a semantic utility, never a
69
+ resolved value, so a boundary restyles the drawing through the cascade exactly as
70
+ it restyles a heading. It is a server Component: no hook, no mode, no context, no
71
+ prop spread, and no client code, so a consumer renders it from a server file
72
+ with no provider mounted. Its node mark is a circle and a relation is a path
73
+ because both are shapes with no radius concept, which is what keeps a boundary
74
+ from moving anything but colour; a `<rect>` was rejected because its corner
75
+ attribute is a CSS property and no utility pins it. Every mark names an explicit
76
+ stroke width, an explicit stroke and an explicit fill, because a shape left to
77
+ its defaults either paints opaque black over its own labels or resolves its edge
78
+ to the surface it sits on. A relation naming a node it does not have is dropped,
79
+ and the count of dropped relations is on the element rather than swallowed.
80
+
81
+ `ProductMark` promotes the two-part mark the product row and the product switcher
82
+ each hand-wrote into one catalogue item. The ring is `brand-ink` and the core is
83
+ `primary`, which is not a preference: a pastel brand value is a fill and never
84
+ text, so the core may be `primary` and the name may not, and a wordmark reads
85
+ `brand-ink` rather than `foreground` or `primary-foreground`. A hand-written copy
86
+ gets one of those two wrong and nothing in the rendered result says which. The
87
+ mark carries its own `data-pack` boundary, so a product's hue follows from the
88
+ pack the caller passed rather than from whichever pack the page is wearing, and
89
+ the boundary sits on an element with no radius utility, which is the placement
90
+ the pack-boundary law allows. A product with no pack of its own is drawn as the
91
+ full spectrum rather than as a colourless mark, built from the five series tokens
92
+ because they are the only five-way colour set the contract publishes.
93
+
94
+ `scripts/check-vector-ink.mjs` holds both Components to the emitted contract. It
95
+ fails a literal colour, a gradient whose stops are not contract references, a
96
+ `var(--x)` naming a property the contract does not publish, and a paint utility
97
+ whose suffix is not a contract role, which is the last of the four a colour
98
+ regex cannot see because a Tailwind class is a name and not a value. Its scanned
99
+ set is a declared list of two file names rather than a pattern, a Component that
100
+ is not on disk fails the run rather than emptying it, and every exclusion carries
101
+ a reason and is printed on every run, with an exclusion that resolves to nothing
102
+ a finding rather than a line that quietly stops appearing.
103
+ - 4f9ce77: A server-rendered pack boundary can now be mode-correct
104
+
105
+ The only dark selector for a pack was `[data-pack="<id>"].dark`, which requires
106
+ the mode class on the same element. A server cannot know the reader's mode, so a
107
+ subtree carrying a second pack rendered in that pack's light values on a dark
108
+ page. The token build now emits the two-member list
109
+
110
+ ```
111
+ [data-pack="<id>"].dark, .dark [data-pack="<id>"]
112
+ ```
113
+
114
+ as one rule with one declaration block, for all five packs. The compound half is
115
+ retained, because it is what a dark surface on a light page is and it is a
116
+ capability the package already published.
117
+
118
+ Consumers get a second change alongside it. `themeAttributes` now takes `mode` as
119
+ optional, because a required one is that same impossibility written into the type:
120
+ the only way to ask for a boundary with no mode class of its own was to pass
121
+ `'light'` as a guess, and the guess is wrong for half of readers. Omitting `mode`
122
+ is the honest spelling of "wear the mode of the element carrying `.dark`". The
123
+ change is a widening, so every existing caller still compiles.
124
+
125
+ The theme-boundary law in `DESIGN.md` is rewritten to match. It previously
126
+ instructed an implementer to put `data-pack` and `class="dark"` on a boundary
127
+ together, which works for a client-applied boundary and cannot work for a
128
+ server-rendered one.
129
+ - c126ee3: A stored theme is written when a reader decides, and not before
130
+
131
+ `PrismProvider` wrote `prism-theme` on every resolved change, including the
132
+ first resolution from the server-rendered attributes or from its own defaults. A
133
+ first-time visitor therefore ended the load with a stored value they never chose,
134
+ and that value outranked the site's default on every later visit, so a site that
135
+ changed its default could not reach anyone who had ever loaded the page.
136
+
137
+ The write is now gated on a decision. It happens when a caller changes the pack,
138
+ the mode or the toggle, and when the library recovers a decision from
139
+ `prism-theme-mode`, a key earlier lines wrote and this one does not. It does not
140
+ happen on a first resolution from the document or the defaults, so a first visit
141
+ ends with an empty store and a returning visit keeps its choice.
142
+
143
+ Two readers of one stored value now share one rule. `resolveTheme` in
144
+ `@nanisoft/prism-ui/theming` is the rule, `PrismProvider` calls it, and the boot
145
+ script implements it as a string. The script cannot import the function, because
146
+ the build minifies and a renamed identifier inside its own `try` would throw
147
+ there and fail open, so the two are held together by a build-failing equivalence
148
+ gate that runs the emitted string and the rule over one table of stored values:
149
+ nothing stored, a valid pair, a valid mode with a retired pack, a valid pack with
150
+ a retired mode, garbage, a JSON array, a JSON string, `null`, an empty object, a
151
+ partial, a server-rendered pack and mode, and the retired key. Every fall-through
152
+ is asserted to be whole, which is the divergence this fixes: the old string
153
+ validated the pack and the mode independently, so a stored `{ mode: 'dark' }` with
154
+ no pack half-applied there and fell through whole in the rule.
155
+
156
+ A value that is present but is not a pair is no longer repaired field by field,
157
+ and it is not cleared. It is the only record that this reader ever chose anything.
158
+
159
+ The boot script records where the theme came from in `data-theme-origin` on
160
+ `<html>`, and it is the only writer: the provider never touches it, because two
161
+ writers would make the origin a race rather than a record. The value is one of
162
+
163
+ | Value | Meaning |
164
+ | --- | --- |
165
+ | `stored` | the key held a valid `{ pack, mode }` pair |
166
+ | `legacy` | the key was empty and `prism-theme-mode` held a recoverable mode |
167
+ | `unparsed` | the key held a value that is not a pair, and the theme fell through whole |
168
+ | `document` | the key was empty and the root element's own attributes supplied the theme |
169
+ | `default` | neither did, so the consumer's defaults apply |
170
+
171
+ It is written on every path the script reaches, so an absent origin is never an
172
+ unread one. It is not `data-theme`, which the migration guide retires; it records
173
+ the provenance of a resolution and never a pack.
174
+
175
+ The migration clause reads `prism-theme-mode`, puts the recovered decision in
176
+ `prism-theme` and removes the key it read, in one guarded step. That is how it
177
+ expires: it fires only while a retired key still holds a mode, at most once per
178
+ reader, and it is retired for good by deleting the last entry of
179
+ `LEGACY_MODE_STORAGE_KEYS`. There is no date and no version constant in it.
180
+
181
+ The pre-paint string gets its own ceiling in raw bytes,
182
+ `check-boot-budget.mjs`, because it runs before paint on every page and adding it
183
+ to the per-component client budget would corrupt the unit that table measures. The
184
+ ceiling is derived by addition: the pinned non-migration base, plus the measured
185
+ clause, plus one migration generation.
186
+ - 76c8d96: Fourteen items, four catalogue gates, and a Block that publishes its interface
187
+
188
+ **Fourteen items ship.** Three Components (`Prose`, `FactList`, `ProductSwitcher`),
189
+ nine Blocks (`ProcessRail01`, `StatusLedger01`, `ProductGrid01`, `StackGrid01`,
190
+ `LogoStrip01`, `NoteGrid01`, `InstrumentPanel01`, `SiteHeader`, `SiteFooter`) and two
191
+ Pages (`NotFoundPage`, `BlogPostPage`). Every one of them earns its place by the
192
+ growth rule rather than by taste: a marketing Block when at least three of the four
193
+ sites compose that section today, a Component when it has one job no existing
194
+ Component can express. The evidence is in the ticket and in each item's own
195
+ JSDoc, which names the sites and the line numbers.
196
+
197
+ **The three contested shapes are settled as specified, and the specifications
198
+ held.** A process rail admits four steps and refuses five, in the type: `steps` is
199
+ a tuple union, so a five-element array is a compile error and not a fifth column. A
200
+ status ledger has four tiers rather than five, and the words for a tier are the
201
+ caller's: the four sites between them use eight words for those four states
202
+ (`live`, `available` and `complete` are one; `approved`, `specified` and `designed`
203
+ are one; then `planned`; then `direction`), and the vocabulary is what `statusLabel`
204
+ is for. A product row's mark is two parts because a filled dot fails contrast on a
205
+ light card, so the mark is `ProductMark`: a hairline ring in `brand-ink` around a
206
+ core in the product's own `primary` fill, with the name in the brand ink beside it.
207
+
208
+ **Four gates the catalogue's own rules depend on now exist and are wired into
209
+ `check` and, because they read only `src/`, into `build`.** `check-item-docs`
210
+ holds AGENTS.md's claim that a documentation comment is the only path from an Item
211
+ to its published interface. `check-block-copy` holds DESIGN.md's claim that a
212
+ Block ships no copy and no sample data. `check-item-category` holds CONTEXT.md's
213
+ closed set of seven Categories and the sentence that a Block and a Page have none.
214
+ `check-block-imports` holds the claim that a Block fetches nothing and a Page
215
+ imports no router and no data client. Each resolves its roots from its own
216
+ location, fails a root that resolves to nothing, prints its coverage and every
217
+ exclusion on every run, has no report-only mode, and is proved red by a fixture
218
+ before it is proved green.
219
+
220
+ **Two of those four gates were red on the tree they were written against, and both
221
+ findings were real.** `check-item-docs` found seven exported functions whose
222
+ documentation comment was not attached to the declaration: `CardHeader`,
223
+ `CardTitle`, `CardDescription`, `CardContent`, `CardFooter`, `SectionHeading` and
224
+ `Hero01`. A comment three functions above a declaration does not reach the emitted
225
+ `.d.ts`, so those seven were absent from the corpus. `check-block-copy` found
226
+ `pricing-01` rendering the word "Popular" on a featured plan that passed no badge:
227
+ a hardcoded claim every consumer of the Block inherited, published by the corpus as
228
+ part of the Block's own documentation. The fallback is gone; a featured plan with
229
+ no badge now renders no badge.
230
+
231
+ **Five additive fields on four shipped items**, each with three of the four
232
+ consumers passing a value the item could not take, counted per consumer with a
233
+ file and a line and never per string. `SectionHeading.index`, for the mono ordinal
234
+ all four sites render above every section head (www `Landing.tsx:49`, atlas
235
+ `landing.tsx:48`, nexus `Landing.tsx:38`, alphalens `Landing.tsx:66`).
236
+ `Cta01.secondaryAction`, for the pair of actions all four closing bands render
237
+ (www `Landing.tsx:186`, atlas `landing.tsx:243`, nexus `Landing.tsx:221`,
238
+ alphalens `Landing.tsx:285`). `Cta01.note`, for the footnote three of the four
239
+ render under it (atlas `landing.tsx:250`, nexus `Landing.tsx:228`, alphalens
240
+ `Landing.tsx:292`). `FeatureGrid01.numbered`, for the ordinal all four render on
241
+ every card (www `Landing.tsx:166`, atlas `landing.tsx:116`, nexus `Landing.tsx:132`,
242
+ alphalens `Landing.tsx:142`). `Hero01.align`, for the left-aligned hero three of
243
+ the four compose (atlas `landing.tsx:69`, nexus `Landing.tsx:63`, alphalens
244
+ `Landing.tsx:87`). All five are additive: nothing changed shape and nothing was
245
+ removed.
246
+
247
+ **A Block and a Page publish both an interface section and a composition section.**
248
+ `packages/llms` read the extractor's output for a Block and threw the interface half
249
+ away, so the tool told an agent a Block had no own props. The extractor reads a
250
+ named props type off an emitted declaration whether that declaration belongs to a
251
+ Component or to a Block, so both sections are now built and both are assembled into
252
+ the mirror file, `llms-full.txt` and the Store's `doc`.
253
+
254
+ The Store now carries both fields for every kind, and `get_item_props` prints
255
+ both when it has both. The sentence "a Block has no own props" is gone from the
256
+ tool as well as from the published document. It was the one place the two
257
+ sections were still exclusive, because that tool branched on `item.props` and
258
+ fell back to the composition section, so a Block was answered with its
259
+ composition and a denial of the interface it publishes. The test asserts the
260
+ sentence's absence, so a refactor that restores it fails even if the composition
261
+ is still printed, and a second test covers the one-present case so the
262
+ both-present branch cannot swallow it.
263
+
264
+ **Four accessible names became required props** on `SiteHeader`, `LogoStrip01`,
265
+ `NotFoundPage` and `BlogPostPage`, and a Block that ships no copy ships no
266
+ reader-facing copy either. `navLabel`, `productsLabel`, `label`, `linksLabel` and
267
+ `trailLabel` are words a screen reader reads out, and no Block should be choosing
268
+ them for a consumer whose site calls its own landmarks something else. This is the
269
+ one breaking change in the release, and it is on four new Blocks and two new Pages
270
+ plus one new required prop on `SiteHeader`, all of which are new in the same
271
+ release, so no existing call site changes.
272
+
273
+ **`ProductSwitcher.label` has no default** for the same reason: a consumer whose set
274
+ is not a set of products should pass its own word rather than accept a claim about
275
+ what the set is.
276
+
277
+ ### Patch Changes
278
+
279
+ - 8d24f32: A pack boundary's effect on corner radius is stated, and gated
280
+
281
+ A scoped `data-pack` boundary resolves the pack's colour AND the pack's corner
282
+ radius beneath it, because radius is the only non-colour member of a pack block
283
+ and the token build emits it the same way. The radius range across the five packs
284
+ is 0.5rem to 1rem, so a row of elements each carrying its own pack shows a spread
285
+ of corner radii, and a small pill becomes a different pill five times over. The
286
+ governing law stated the colour half and was silent on the shape half, so an
287
+ implementer following it exactly produced mismatched corners and called it
288
+ correct.
289
+
290
+ The law now states both halves and where a boundary may sit. The agent-facing
291
+ surface no longer says the attribute belongs on the root element, which it did
292
+ while listing the radius among the values it will pick up: a tool that shows the
293
+ value and denies the subtree is worse than a tool that is silent.
294
+
295
+ `check-pack-boundary.mjs` holds the law. It reads the emitted `--radius-*`
296
+ bindings rather than a list of class names, which is the part that matters: a
297
+ scale step like `rounded-xl` is `calc(var(--radius) * 1.4)`, so an element
298
+ carrying one is pinned to a step and the step is pack-relative. The first version
299
+ of the gate treated "carries a radius utility" as the safe case and passed
300
+ exactly the defect the ticket is about. The gate also reads the colour contract
301
+ from the token source, so a second per-pack axis the token build has not been
302
+ asked about is a finding rather than a property silently counted as a colour.
303
+
304
+ One live boundary was found and repaired: the themes page scoped five cards, one
305
+ per pack, each with `rounded-xl`, so the page a reader uses to compare packs was
306
+ comparing shape as well as colour. The cards now carry a literal radius and the
307
+ pack's radius is shown in the row's own label, where it is information rather
308
+ than an accident.
309
+ - feb1384: The catalogue is a set comparison between three lists, not a count
310
+
311
+ A module on disk with no catalogue entry left every count correct. It shipped,
312
+ `shadcn add` installed it, and `registry.json` listed it, so it was absent from
313
+ the corpus, the documentation site and every agent-facing tool with a fully
314
+ green gate run. The two lists a reader trusts, the catalogue and the registry,
315
+ were on the wrong side of that hole, and the checks that believed they covered it
316
+ compared lengths or compared a file against something derived from itself.
317
+ `validate-registry.mjs` holds the registry against the disk and the manifest and
318
+ never reads the catalogue. `sync-registry.mjs` reads the disk, so it cannot
319
+ disagree with the disk about what is on it. The site's
320
+ `test/catalogue-order.test.ts` holds the catalogue against the generated
321
+ ordering and the documentation tree, and both are built from the catalogue, so a
322
+ disagreement there is impossible by construction.
323
+
324
+ `scripts/check-catalogue.mjs` reads the three sets independently and compares
325
+ them in both directions, name for name, with one printed line per offending
326
+ item. A length assertion cannot report which item is wrong, so nothing in the
327
+ run reports a length on its own: the three set sizes print as supplementary
328
+ coverage underneath the findings that carry the detail. All six directions a
329
+ three-set comparison implies are implemented, plus the kind of an item in each
330
+ pair of sets, the three naming rules that reconcile the three conventions, and a
331
+ claim that a canonical key is held by only one entry in each set.
332
+
333
+ The registry generator deliberately keeps reading the disk. Deriving the
334
+ registry from the catalogue is the obvious way to remove the second list, and it
335
+ would delete the disagreement the check exists to see, because the generator
336
+ would then agree with the catalogue by construction.
337
+
338
+ The version and the roster are two claims on two labelled lines, so a version
339
+ bump and a roster error cannot produce the same output. The run states its
340
+ coverage every time: roots resolved, files walked, files actually read, and how
341
+ many comparisons passed. Roots resolve from the script's own location and never
342
+ from the working directory, and a root that resolves to nothing fails the run
343
+ naming both causes.
344
+
345
+ `PRISM_CATALOGUE_ROOT` points a run at another package root. It changes which
346
+ directory is read and nothing else, which is what lets the tests run the real
347
+ command over a fixture tree and read the real output. The gate fails on a
348
+ catalogue it cannot read, including an empty one: a roster that cannot be read
349
+ and a roster with nothing in it are different facts, and only one of them is a
350
+ green run.
351
+ - 4744cce: The Slider thumb rings at full strength, and the surface check now reads both ways
352
+
353
+ Two changes, and only two, in the component package.
354
+
355
+ **The focus indicator.** The Slider's thumb suppressed the browser outline and
356
+ drew its ring at `ring-ring/50`, so the element a keyboard user lands on carried
357
+ an indicator that composites to between 1.14:1 and 2.74:1 against every surface
358
+ in all six themes. That fails WCAG 1.4.11 on the one indicator a keyboard user
359
+ has, and the Slider's own JSDoc says it is reachable by Tab. Thirteen other
360
+ components already drew that ring at full strength; the Slider was the one that
361
+ was missed. Nothing said so, because the contrast gate measures the `ring`
362
+ TOKEN and the alpha is applied in the component rather than in the token, so it
363
+ reads the Slider's ring as compliant.
364
+
365
+ `packages/ui/scripts/check-focus-indicators.mjs` is the new gate. It reads the
366
+ class strings on the elements of every shipped Component, derives the set of
367
+ components whose own JSDoc claims keyboard reach or operation, and fails any of
368
+ them that suppresses a focus style without declaring a `ring-ring` colour at
369
+ full alpha alongside a `focus-visible:ring-N` width. The claim is read from the
370
+ JSDoc because `AGENTS.md` names that as this repository's documentation source,
371
+ so a component that stops claiming to be focusable is a documentation change and
372
+ shows up in the table the run prints rather than in a list someone maintains
373
+ beside the code. The unit is a class string on one element, so a Dialog's panel
374
+ or a Tooltip's trigger can suppress the outline legitimately; every such
375
+ exclusion is declared with its reason, printed with the slots it resolved to,
376
+ and fails the run if it stops matching anything. The run fails if the table
377
+ empties, and states how much it read, classified, tabled and excluded.
378
+
379
+ **The surface check.** `scripts/check-surface.mjs` compared the published
380
+ surface against the source in one direction for two of its three rules. A
381
+ wildcard subpath that resolved to zero declarations used to leave those two
382
+ rules reading no file at all and the run still printed its success line, which is
383
+ how a published surface drifts from its source without a consumer failing first.
384
+ A wildcard target that matches nothing is now a finding naming the target. The
385
+ internal side, which the gate deliberately did not check, is now a declared
386
+ boundary checked in both directions: a new file under `dist/lib` is a finding
387
+ naming it, and a declared internal declaration that is no longer emitted is a
388
+ finding naming it too. The boundary and the per-target match counts are printed
389
+ on every run. The three existing rules and their messages are unchanged.
390
+
391
+ **The version the four downstream site plans pin is `0.6.0`.** It is
392
+ `@nanisoft/prism-ui@0.6.0`, and `@nanisoft/prism-tokens@0.6.0` with it, because
393
+ the two are linked in `.changeset/config.json` and the pending
394
+ `a-server-rendered-pack-boundary-can-be-mode-correct` changeset declares a minor
395
+ for both from `0.5.1`. The patch declared here does not raise it: changesets
396
+ takes the highest bump in the queue, and a minor is already queued. A site plan
397
+ that pins `0.6.0` therefore resolves every subpath the export map publishes,
398
+ including `./components`, `./blocks`, `./pages`, `./provider`, `./theming`,
399
+ `./catalog` and `./styles.css`.
400
+
401
+ No public prop, no type and no export changed. The one rendering change is a
402
+ ring alpha on the Slider thumb, in both modes.
403
+ - Updated dependencies [4f9ce77]
404
+ - Updated dependencies [5dcb347]
405
+ - @nanisoft/prism-tokens@0.6.0
406
+
407
+ ## 0.5.1
408
+
409
+ ### Patch Changes
410
+
411
+ - 2fc6a0a: Retune `muted-foreground` so muted text clears 4.5:1 on the muted surface
412
+
413
+ The base pack's `muted-foreground` moves from neutral 500 to neutral 600. It
414
+ cleared 4.5:1 on the page background but fell to 4.34:1 on `muted`, where the
415
+ pill, avatar fallback, kbd and tab-list pattern place it. The contrast gate now
416
+ also checks `muted-foreground` on `muted`. The pastel packs were already pinned
417
+ to their neutral 700 and are unchanged.
418
+ - Updated dependencies [2fc6a0a]
419
+ - @nanisoft/prism-tokens@0.5.1
420
+
421
+ ## 0.5.0
422
+
423
+ Prism was rebuilt from a fresh repository on a clean break. The package is now a
424
+ published React library a consumer installs and composes, not component source to
425
+ copy. It ships one precompiled `styles.css`, the optional `PrismProvider` and the
426
+ `data-pack` plus `dark` theme axes, and the Components, Blocks and Pages
427
+ taxonomy. There is no override path.
428
+
429
+ Changesets are appended above this entry for every published change.
@@ -0,0 +1,108 @@
1
+ # Accordion
2
+
3
+ The stack of headers that each reveal a panel in place.
4
+
5
+ ```tsx
6
+ import { Accordion, AccordionItem, AccordionTrigger, AccordionContent } from '@nanisoft/prism-ui/components/accordion'
7
+ ```
8
+
9
+ ## Overview
10
+
11
+ An Accordion is a stack of headers that each reveal a panel in place. It ships
12
+ as `Accordion`, `AccordionItem`, `AccordionTrigger` and `AccordionContent`, all
13
+ from the same module. Only one item is open at a time unless `multiple` is set.
14
+
15
+ Each trigger is a real button, and the library ties it to its panel, so the
16
+ header announces whether the item is expanded. Enter or Space opens the focused
17
+ item, and the header text stays visible when the panel is closed.
18
+
19
+ ## Guidelines
20
+
21
+ ### When to use
22
+
23
+ Use an Accordion when a page holds several long sections that a reader opens
24
+ selectively: frequently asked questions, advanced settings, or a policy split
25
+ into questions.
26
+
27
+ ### When not to use
28
+
29
+ Do not use an Accordion when the reader needs two sections side by side. Only
30
+ one is open at a time by default, so comparison is impossible.
31
+
32
+ Do not use it to hide the primary content of a page. If a section is essential,
33
+ it should be open, not behind a click.
34
+
35
+ Do not use an Accordion for a short list of statements. A definition list reads
36
+ better and needs no interaction.
37
+
38
+ ### Best practices
39
+
40
+ - Give every `AccordionItem` a stable `value`, and control the open set with
41
+ `value` and `onValueChange` when the state matters beyond the component.
42
+ - Open the item a reader most likely wants first, or none at all. Opening all of
43
+ them restores the wall of text the accordion was meant to break up.
44
+ - Use one heading level for the triggers. Each header is a heading element, so
45
+ the accordion participates in the page outline.
46
+ - Keep the trigger text a short question or label the reader can scan without
47
+ opening the panel.
48
+ - Set `multiple` only when the sections are truly independent and comparing them
49
+ is a normal task.
50
+
51
+ ### Content guidelines
52
+
53
+ - Phrase the trigger as the question or the topic, not as "Click to expand".
54
+ - Use sentence case and a question mark on a question, no period on a label.
55
+ - Keep the panel prose self-contained. A reader who opens one item should not
56
+ need another to make sense of it.
57
+
58
+ **An accordion with one panel open and the rest available on demand.**
59
+
60
+ ```tsx
61
+ 'use client'
62
+
63
+ import {
64
+ Accordion,
65
+ AccordionContent,
66
+ AccordionItem,
67
+ AccordionTrigger,
68
+ } from '@nanisoft/prism-ui/components/accordion'
69
+
70
+ /** An accordion with one panel open and the rest available on demand. */
71
+ export default function AccordionDemo() {
72
+ return (
73
+ <Accordion defaultValue={['shipping']} className="max-w-measure-narrow">
74
+ <AccordionItem value="shipping">
75
+ <AccordionTrigger>How long does shipping take?</AccordionTrigger>
76
+ <AccordionContent>
77
+ Orders leave the warehouse within one working day and arrive in two to
78
+ five, depending on the region.
79
+ </AccordionContent>
80
+ </AccordionItem>
81
+ <AccordionItem value="returns">
82
+ <AccordionTrigger>What is the return window?</AccordionTrigger>
83
+ <AccordionContent>
84
+ Return anything unused within thirty days for a full refund.
85
+ </AccordionContent>
86
+ </AccordionItem>
87
+ <AccordionItem value="support">
88
+ <AccordionTrigger>How do I reach support?</AccordionTrigger>
89
+ <AccordionContent>
90
+ Reply to any order email, or write to support and include the order
91
+ number.
92
+ </AccordionContent>
93
+ </AccordionItem>
94
+ </Accordion>
95
+ )
96
+ }
97
+ ```
98
+
99
+ ## Props
100
+
101
+ **`value`** `string[]` · optional · default: — — The open item values, when the accordion is controlled.
102
+ **`defaultValue`** `string[]` · optional · default: — — The item values open initially, for an uncontrolled accordion.
103
+ **`onValueChange`** `(value: string[]) => void` · optional · default: — — Called when the open set changes.
104
+ **`multiple`** `boolean` · optional · default: — — Whether more than one item may be open at once.
105
+ **`disabled`** `boolean` · optional · default: — — Whether every item ignores user interaction.
106
+ **`hiddenUntilFound`** `boolean` · optional · default: — — Whether the browser's page search may expand a panel.
107
+ **`keepMounted`** `boolean` · optional · default: — — Whether a closed panel stays in the DOM.
108
+ **`onOpenChange`** `(open: boolean) => void` · optional · default: — — Called when this item opens or closes.