@nanisoft/prism-llms 0.4.0 → 0.5.1

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 +4037 -386
  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 +299 -156
  8. package/dist/index.d.ts +12 -7
  9. package/dist/index.js +9 -7
  10. package/dist/llms-full.txt +8248 -2013
  11. package/dist/llms.txt +98 -93
  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 +98 -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 +107 -0
  27. package/dist/md/blocks/settings-panel-01.md +146 -0
  28. package/dist/md/blocks/site-footer.md +103 -11
  29. package/dist/md/blocks/site-header.md +107 -11
  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 +244 -0
  34. package/dist/md/changelogs/prism-mcp-server.md +95 -0
  35. package/dist/md/changelogs/prism-tokens.md +126 -0
  36. package/dist/md/changelogs/prism-ui.md +467 -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 +114 -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/docs-shell.md +210 -15
  95. package/dist/md/pages/marketing-page.md +199 -0
  96. package/dist/md/pages/not-found-page.md +105 -0
  97. package/dist/md/pages/settings-page.md +206 -0
  98. package/dist/prism-skill.md +50 -0
  99. package/dist/store.d.ts +203 -0
  100. package/dist/store.js +317 -0
  101. package/package.json +29 -26
  102. package/dist/demo-graph.d.ts.map +0 -1
  103. package/dist/demo-graph.js.map +0 -1
  104. package/dist/extractor.d.ts.map +0 -1
  105. package/dist/extractor.js.map +0 -1
  106. package/dist/index.d.ts.map +0 -1
  107. package/dist/index.js.map +0 -1
  108. package/dist/markdown.d.ts.map +0 -1
  109. package/dist/markdown.js.map +0 -1
  110. package/dist/md/blocks/component-demo.md +0 -19
  111. package/dist/md/blocks/page-header.md +0 -19
  112. package/dist/md/components/affix.md +0 -14
  113. package/dist/md/components/anchor.md +0 -14
  114. package/dist/md/components/auto-complete.md +0 -14
  115. package/dist/md/components/back-top.md +0 -14
  116. package/dist/md/components/border-beam.md +0 -14
  117. package/dist/md/components/calendar.md +0 -14
  118. package/dist/md/components/carousel.md +0 -14
  119. package/dist/md/components/cascader.md +0 -14
  120. package/dist/md/components/col.md +0 -14
  121. package/dist/md/components/collapse.md +0 -14
  122. package/dist/md/components/color-picker.md +0 -14
  123. package/dist/md/components/date-picker.md +0 -14
  124. package/dist/md/components/descriptions.md +0 -14
  125. package/dist/md/components/display-title.md +0 -20
  126. package/dist/md/components/divider.md +0 -14
  127. package/dist/md/components/drawer.md +0 -14
  128. package/dist/md/components/dropdown.md +0 -14
  129. package/dist/md/components/empty.md +0 -14
  130. package/dist/md/components/flex.md +0 -14
  131. package/dist/md/components/float-button.md +0 -14
  132. package/dist/md/components/form.md +0 -14
  133. package/dist/md/components/grid.md +0 -14
  134. package/dist/md/components/image.md +0 -14
  135. package/dist/md/components/input-number.md +0 -14
  136. package/dist/md/components/layout.md +0 -14
  137. package/dist/md/components/list.md +0 -14
  138. package/dist/md/components/listy.md +0 -14
  139. package/dist/md/components/masonry.md +0 -14
  140. package/dist/md/components/mentions.md +0 -14
  141. package/dist/md/components/menu.md +0 -14
  142. package/dist/md/components/modal.md +0 -14
  143. package/dist/md/components/popconfirm.md +0 -14
  144. package/dist/md/components/qrcode.md +0 -14
  145. package/dist/md/components/radio.md +0 -14
  146. package/dist/md/components/rate.md +0 -14
  147. package/dist/md/components/result.md +0 -14
  148. package/dist/md/components/row.md +0 -14
  149. package/dist/md/components/segmented.md +0 -14
  150. package/dist/md/components/space.md +0 -14
  151. package/dist/md/components/spin.md +0 -14
  152. package/dist/md/components/splitter.md +0 -14
  153. package/dist/md/components/statistic.md +0 -14
  154. package/dist/md/components/steps.md +0 -14
  155. package/dist/md/components/tag.md +0 -14
  156. package/dist/md/components/time-picker.md +0 -14
  157. package/dist/md/components/timeline.md +0 -14
  158. package/dist/md/components/tour.md +0 -14
  159. package/dist/md/components/transfer.md +0 -14
  160. package/dist/md/components/tree-select.md +0 -14
  161. package/dist/md/components/tree.md +0 -14
  162. package/dist/md/components/upload.md +0 -14
  163. package/dist/md/components/watermark.md +0 -14
  164. package/dist/md/docs/brand.md +0 -41
  165. package/dist/md/pages/blog-layout.md +0 -19
  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,89 @@
1
+ # Theming
2
+
3
+ Packs and modes are two independent axes, delivered as markup attributes with no override path.
4
+
5
+ Prism themes are complete atmospheres, not a primary-colour swap. A pack changes
6
+ the page ground, the surfaces, the hairlines, the text bases and the focus
7
+ treatment together.
8
+
9
+ ## Two axes
10
+
11
+ Theming is two independent axes:
12
+
13
+ - **Pack** selects the palette: `default`, `blush`, `mint`, `lavender`, `sky`
14
+ or `peach`.
15
+ - **Mode** selects the appearance: `light` or `dark`.
16
+
17
+ A **theme** is one pack in one mode, such as Blush dark. It is a fully resolved
18
+ token set, not an object and not an API. There is no theme factory.
19
+
20
+ | Pack | Character |
21
+ | --- | --- |
22
+ | `default` | The neutral base. `primary` reads as ink, not colour. |
23
+ | `blush` | Soft rose, with the softest radius of the set. |
24
+ | `mint` | Soft green, calm and low-energy. |
25
+ | `lavender` | Soft violet, the most saturated brand ramp. |
26
+ | `sky` | Soft blue, the crispest hue and the smallest radius. |
27
+ | `peach` | Soft orange, the warmest hue and the roundest corners. |
28
+
29
+ ## Declarative selection
30
+
31
+ Put `data-pack` on any element to make it, and its subtree, a theme boundary.
32
+ Add the `dark` class for dark mode. The neutral base pack is the absence of
33
+ `data-pack`, so it is expressed by omitting the attribute.
34
+
35
+ ```tsx
36
+ <!-- the default pack, light -->
37
+ <html lang="en">
38
+
39
+ <!-- Lavender, dark -->
40
+ <html lang="en" data-pack="lavender" class="dark">
41
+ ```
42
+
43
+ This form is server-rendered and needs no JavaScript. It is also the cheapest
44
+ way to show several packs at once: any subtree can carry its own `data-pack`.
45
+ See the [themes page](/foundation/themes) for all packs side by side.
46
+
47
+ ## Programmatic selection
48
+
49
+ When a user needs to switch and persist a choice, mount the optional provider
50
+ and read the hook.
51
+
52
+ ```tsx
53
+ import { PrismProvider, usePrismTheme } from '@nanisoft/prism-ui/provider'
54
+
55
+ function PackPicker() {
56
+ const { pack, setPack, mode, toggleMode } = usePrismTheme()
57
+ return (
58
+ <button onClick={() => setPack('peach')}>
59
+ {pack} / {mode}
60
+ </button>
61
+ )
62
+ }
63
+
64
+ export function App({ children }: { children: React.ReactNode }) {
65
+ return <PrismProvider>{children}</PrismProvider>
66
+ }
67
+ ```
68
+
69
+ The provider's only outputs are the two document-element attributes and the
70
+ context. It has no override parameter, no token object and no merge.
71
+
72
+ ## Flash-free first paint
73
+
74
+ Under a static export there is no server and no middleware, so a blocking inline
75
+ script in `<head>` is what honours a stored choice before first paint. The
76
+ provider's package includes `PrismThemeScript` for exactly this, and the root
77
+ element carries `suppressHydrationWarning` because the script may set the
78
+ attributes before React hydrates.
79
+
80
+ ## There is no consumer theming
81
+
82
+ Prism does not expose `createPrismTheme`, a per-key merge or semantic overrides.
83
+ The supported path is to select a pack and a mode. A product that needs a
84
+ different palette, a new semantic token or a changed component should request it
85
+ upstream, so every consumer benefits from the change and no project carries a
86
+ private fork.
87
+
88
+ `className` is for layout composition only. Changing a Prism-owned visual
89
+ property through a class is not supported.
@@ -0,0 +1,83 @@
1
+ # Using LLMs
2
+
3
+ Using LLMs against Prism: the corpus, the Markdown mirror and the read-only MCP endpoint, so an agent reads the same source the human documentation does.
4
+
5
+ Prism treats agents as first-class consumers. The site, the package declarations,
6
+ the demo sources, the Markdown mirror and the MCP endpoint all project the same
7
+ source, so an agent and a human cannot be told different things.
8
+
9
+ ## Fast path
10
+
11
+ 1. Read `llms.txt` for the catalogue and the page index.
12
+ 2. Use `search_docs` when the item name is unknown.
13
+ 3. Use `get_item_doc` for the assembled item documentation and its rules.
14
+ 4. Use `get_item_props` for the public TypeScript surface.
15
+ 5. Use `get_item_source` for the verbatim demo and the public import line.
16
+ 6. Use `get_theme_doc` before choosing a pack or reading a token scale.
17
+ 7. Use `get_changelog` before upgrading a dependency on Prism.
18
+
19
+ ## The MCP endpoint
20
+
21
+ The endpoint is public and read-only. It returns Markdown and never writes to a
22
+ consumer project.
23
+
24
+ ```json
25
+ {
26
+ "mcpServers": {
27
+ "prism": {
28
+ "type": "http",
29
+ "url": "https://prism.nanisoft.com/mcp"
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ ## The nine tools
36
+
37
+ | Tool | Use |
38
+ | --- | --- |
39
+ | `list_items` | Browse the catalogue, filtered by `kind` and `category` |
40
+ | `get_item_doc` | Read the assembled item documentation |
41
+ | `get_item_props` | Read the `## Props` subset of an item |
42
+ | `get_item_source` | Copy the verbatim demo plus the import line |
43
+ | `get_theme_doc` | Read the token contract and the bound scales |
44
+ | `list_pages` | Browse the non-item pages: Overview, Foundation, Content, Changelogs |
45
+ | `get_page` | Read one non-item page as Markdown |
46
+ | `search_docs` | Search the complete corpus and return references |
47
+ | `get_changelog` | Read a published package's changelog, optionally one version |
48
+
49
+ `list_items` is the one tool that exists because a kind exists; the other seven
50
+ exist because a retrieval pattern exists, and `get_changelog` exists because a
51
+ published breaking change has to be findable by an agent. Search returns
52
+ references, not page bodies, so an agent follows a hit with `get_item_doc` or
53
+ `get_page`.
54
+
55
+ `get_changelog` takes a package and an optional version, and returns that
56
+ package's own `CHANGELOG.md` as it shipped rather than a summary of it. The
57
+ Changelogs section of this site renders the same bytes, so an agent and a reader
58
+ cannot be told different things about what changed.
59
+
60
+ ## Import contract for generated code
61
+
62
+ Generated applications import from `@nanisoft/prism-ui`. The internal Base UI
63
+ dependency is not a consumer dependency and must not appear in application
64
+ source.
65
+
66
+ ```tsx
67
+ import { Button } from '@nanisoft/prism-ui/components/button'
68
+ import { Hero01 } from '@nanisoft/prism-ui/blocks/hero-01'
69
+ ```
70
+
71
+ The internal shadcn registry never appears in generated code. It is an integrity
72
+ artifact inside the package, not an install lane.
73
+
74
+ ## Truth rules
75
+
76
+ - The corpus is versioned with `prism-ui`. Compare the version echoed by
77
+ `list_items` with the installed package when behaviour looks inconsistent.
78
+ - Demos are single-source files: the same text is rendered live, copied by the
79
+ site, and returned by `get_item_source`.
80
+ - A generated item has exactly one demo. When an item has no demo, the tool says
81
+ so rather than fabricating one.
82
+ - Product claims belong to the product team. The corpus documents implementation
83
+ and usage, never invented customer evidence.
@@ -0,0 +1,149 @@
1
+ # AuthPage
2
+
3
+ A full authentication screen composing a sign-in form with an optional supporting card.
4
+
5
+ ```tsx
6
+ import { AuthPage } from '@nanisoft/prism-ui/pages/auth-page'
7
+ ```
8
+
9
+ ## Overview
10
+
11
+ AuthPage is a complete authentication screen. It composes a sign-in form with an
12
+ optional supporting card, and it owns the centering, the breakpoint and the
13
+ fallback column only. The form carries the credentials, the error slot and the
14
+ submit; the card carries product context beside it.
15
+
16
+ The page composes these parts:
17
+
18
+ | Region | Piece | Role |
19
+ | --- | --- | --- |
20
+ | Form | AuthForm01 | the title, the credential fields, the remember row and the submit |
21
+ | Support | Card | an optional surface beside the form for context or reassurance |
22
+ | Footer | footer | a line under the form, such as a legal note or a link |
23
+
24
+ Every field, label, handler, error and supporting string is a prop. The page
25
+ fetches nothing and imports no router, so the consumer owns the request and the
26
+ links. When no `aside` is passed the layout is one centered column; with one it
27
+ is a two-column split from the large breakpoint up, and the card falls below the
28
+ form on a narrow screen.
29
+
30
+ ## Guidelines
31
+
32
+ ### When to use
33
+
34
+ Use AuthPage for a sign-in screen whose only task is to collect one email and one
35
+ secret. It works when the screen is the first thing a returning reader sees and
36
+ the supporting card answers a question they are likely to have at that moment.
37
+
38
+ ### When not to use
39
+
40
+ Do not use it for registration, password reset or profile editing. Those have
41
+ different field sets, and a sign-in card with the wrong fields is a dead end;
42
+ compose a form from the same parts instead.
43
+
44
+ Do not use it when the screen must carry more than a few words of supporting
45
+ content. The card is context beside a form, not a second page; a longer story
46
+ belongs on a marketing route the reader saw before signing in.
47
+
48
+ ### Best practices
49
+
50
+ - Set `type`, `autoComplete` and `required` on every field. Browsers use them to
51
+ fill and validate, so the typing is the whole reason the field is typed.
52
+ - Keep the error to one sentence and mark the failed fields. AuthForm01 sets
53
+ `aria-invalid` when an error is present, so the form and the message agree.
54
+ - Disable the submit control while a request is in flight and pass `pendingLabel`,
55
+ so the reader sees that work is happening.
56
+ - Omit `aside` when there is nothing true to say. An empty card is worse than a
57
+ single centered form.
58
+ - Keep the footer to one line, such as a legal note or a route to create an
59
+ account, and pass the link in as a node so the consumer owns it.
60
+
61
+ ### Content guidelines
62
+
63
+ - The title is a short verb phrase in sentence case: "Sign in", "Continue".
64
+ - The description states the outcome, not a policy.
65
+ - An error says what to do next: "Enter your email and password to continue."
66
+
67
+ **The auth page, with a controlled form and a supporting card.**
68
+
69
+ ```tsx
70
+ 'use client'
71
+
72
+ import { useState } from 'react'
73
+
74
+ import { AuthPage } from '@nanisoft/prism-ui/pages/auth-page'
75
+
76
+ /** The auth page, with a controlled form and a supporting card. */
77
+ export default function AuthPageDemo() {
78
+ const [email, setEmail] = useState('')
79
+ const [password, setPassword] = useState('')
80
+ const [error, setError] = useState<string>()
81
+
82
+ return (
83
+ <AuthPage
84
+ form={{
85
+ title: 'Sign in',
86
+ description: 'Welcome back. Enter your details to continue.',
87
+ error,
88
+ submitLabel: 'Sign in',
89
+ fields: [
90
+ {
91
+ id: 'auth-page-email',
92
+ label: 'Email',
93
+ type: 'email',
94
+ autoComplete: 'email',
95
+ placeholder: 'you@example.com',
96
+ value: email,
97
+ onChange: setEmail,
98
+ required: true,
99
+ },
100
+ {
101
+ id: 'auth-page-password',
102
+ label: 'Password',
103
+ type: 'password',
104
+ autoComplete: 'current-password',
105
+ value: password,
106
+ onChange: setPassword,
107
+ required: true,
108
+ },
109
+ ],
110
+ remember: { id: 'auth-page-remember', label: 'Remember me for 30 days' },
111
+ onSubmit: (event) => {
112
+ event.preventDefault()
113
+ setError(
114
+ email && password ? undefined : 'Enter your email and password to continue.',
115
+ )
116
+ },
117
+ }}
118
+ aside={{
119
+ title: 'Everything in one workspace',
120
+ description: 'Projects, deployments and usage, without a second tool.',
121
+ children: (
122
+ <ul className="text-muted-foreground flex flex-col gap-2 text-sm">
123
+ <li>One place for every team</li>
124
+ <li>Usage that updates as you deploy</li>
125
+ <li>Roles from viewer to admin</li>
126
+ </ul>
127
+ ),
128
+ }}
129
+ footer={<p>No account yet? Create one.</p>}
130
+ />
131
+ )
132
+ }
133
+ ```
134
+
135
+ ## Props
136
+
137
+ **`form`** `ComponentProps<typeof AuthForm01>` · required · default: — — The credential form.
138
+ **`aside`** `AuthPageAside` · optional · default: — — A supporting card beside the form. Omit it for a single centered column.
139
+ **`footer`** `ReactNode` · optional · default: — — A node under the form, such as a legal line or a link to another screen.
140
+
141
+ ## Composition
142
+
143
+ **`exports`** `AuthPage` — the public runtime names this Page promises
144
+ **`source`** `packages/ui/src/pages/auth-page/index.tsx` — the file this Page is defined in
145
+ **`dependencies`** `AuthForm01, Card` — the public Prism exports this item composes
146
+
147
+ ## Blocks
148
+
149
+ - [AuthForm01](https://prism.nanisoft.com/blocks/auth-form-01.md)
@@ -0,0 +1,136 @@
1
+ # BlogPostPage
2
+
3
+ A complete blog post screen: title, standfirst, byline with both the display and the machine date, the body at the measure and the trail to the neighbouring posts.
4
+
5
+ ```tsx
6
+ import { BlogPostPage } from '@nanisoft/prism-ui/pages/blog-post-page'
7
+ ```
8
+
9
+ ## Overview
10
+
11
+ BlogPostPage is a complete blog post screen: the title, the standfirst, the
12
+ byline, the body at the reading measure, and the trail to the neighbouring posts.
13
+
14
+ All four NaniSoft sites publish a blog, each with four posts and the same
15
+ frontmatter: a title, a description, a date and a list of tags. Each one then
16
+ rendered that frontmatter in its own component, with its own wrapper, its own
17
+ measure and its own treatment of the date, and each of them wrote the trail to the
18
+ neighbouring posts separately or not at all.
19
+
20
+ **The date is two props.** `date` is the words a reader sees and `dateTime` is the
21
+ machine value on the `time` element, and they are kept apart because parsing a
22
+ formatted date back into an ISO one is a guess about a locale. A wrong date in a
23
+ `datetime` attribute is a wrong date in a feed reader and in a search result, and
24
+ neither reader can see the string that was wrong.
25
+
26
+ ## Guidelines
27
+
28
+ ### When to use
29
+
30
+ Use BlogPostPage as the body of the route that renders one post, handed the
31
+ frontmatter your content pipeline produced.
32
+
33
+ Use it for every post. A blog whose posts each have their own wrapper is a blog
34
+ whose posts drift apart within a year.
35
+
36
+ ### When not to use
37
+
38
+ Do not use it for a documentation page. Documentation is a set of short pages
39
+ about one thing; a post is a dated argument about one thing, and a trail to the
40
+ post before it is not a trail through a manual.
41
+
42
+ Do not use it for a landing page. `MarketingPage` is that composition.
43
+
44
+ ### Best practices
45
+
46
+ - Pass `dateTime` in `YYYY-MM-DD`. It is the value a feed reader and a search
47
+ engine order by, and it is the one that is wrong silently.
48
+ - The body is `children`, held by `Prose`. Your pipeline owns the document; this
49
+ owns the measure and the rhythm. Do not parse it into an array of paragraphs to
50
+ hand over - that is a second representation of something your pipeline already
51
+ has.
52
+ - Render only the halves of the trail you have. A previous link on the oldest post
53
+ is a link to the first post a reader has already read, and a next link on the
54
+ newest is a promise the site cannot keep.
55
+ - `trailLabel` and `trailLabels` are required alongside a neighbour. They are words
56
+ a reader reads and hears, and a Page that ships no copy ships no reader-facing
57
+ copy either: a site that files posts newest-last says the opposite pair.
58
+ - Use `footer` for what comes after the trail: related posts, a newsletter form, a
59
+ closing call to action. It is a slot because every one of those is a Block, and a
60
+ Page that took a Block per prop would be a Page whose interface lists the whole
61
+ catalogue.
62
+ - Pass `indexLink` with the words and the route your archive lives at. The page
63
+ ships neither.
64
+
65
+ ### Content guidelines
66
+
67
+ - Write the standfirst as one or two sentences a reader can stop at. It is what an
68
+ index, a feed and a search result show.
69
+ - Keep tags to nouns. A tag is a category a reader browses, not a sentence.
70
+ - Keep the title to one line at the largest viewport unless the post is the only
71
+ thing on the page.
72
+
73
+ **A post: the title, the standfirst, both dates, tags, the body and the trail.**
74
+
75
+ ```tsx
76
+ import { BlogPostPage } from '@nanisoft/prism-ui/pages/blog-post-page'
77
+
78
+ /** A post: the title, the standfirst, both dates, tags, the body and the trail. */
79
+ export default function BlogPostPageDemo() {
80
+ return (
81
+ <BlogPostPage
82
+ title="Every minute, the whole chain"
83
+ description="What live full-chain capture at index scale actually takes: the spike, then production, then Kubernetes."
84
+ date="21 September 2026"
85
+ dateTime="2026-09-21"
86
+ author="NaniSoft"
87
+ readingTime="6 min read"
88
+ tags={[{ label: 'market-data' }, { label: 'collector' }]}
89
+ trailLabels={{ previous: 'Previous', next: 'Next' }}
90
+ trailLabel="More posts"
91
+ previous={{ title: 'One feed to research them all', href: '#one-feed' }}
92
+ next={{ title: 'Multi-agent research, mapped', href: '#multi-agent' }}
93
+ indexLink={{ title: 'All posts', href: '#all' }}
94
+ >
95
+ <p>
96
+ The claim sounds like a feature. It is a measurement with a timestamp on
97
+ it, and it started as a suspicion that it was impossible.
98
+ </p>
99
+ <h2>The assumption that was backwards</h2>
100
+ <p>
101
+ Going in, full-chain-per-minute looked like a streaming problem. It was a
102
+ counting problem, and the count had to fit in a minute.
103
+ </p>
104
+ <ul>
105
+ <li>Every strike, call and put.</li>
106
+ <li>Open interest, implied volatility and the Greeks.</li>
107
+ <li>Index futures, index spot and the volatility index.</li>
108
+ </ul>
109
+ </BlogPostPage>
110
+ )
111
+ }
112
+ ```
113
+
114
+ ## Props
115
+
116
+ **`title`** `string` · required · default: — — The post's title. It is the page's `h1`, so the page has exactly one and it is the post's name.
117
+ **`description`** `string` · optional · default: — — The standfirst: one or two sentences under the title that say what the post is about. It is a prop rather than the first paragraph of the body because a reader deciding whether to read needs it before the body starts, and because it is what an index, a feed and a search result show.
118
+ **`date`** `string` · required · default: — — The publication date, already formatted for display, and the machine value for the `time` element's `dateTime`. Both are props, and the machine one is required: `dateTime` is what a reader's browser, a feed reader and a search engine use to order posts, and a formatted string is none of those.
119
+ **`dateTime`** `string` · required · default: — — The date in a machine-readable form, `YYYY-MM-DD`, carried on the `time` element. Required, and deliberately not derived from `date`: parsing a formatted date back into an ISO one is a guess about a locale, and a guess in a `datetime` attribute is a wrong date in a feed.
120
+ **`author`** `ReactNode` · optional · default: — — Who wrote the post. A name, or a link to an author page.
121
+ **`tags`** `readonly BlogPostTag[]` · optional · default: — — The tags on the post, in the order a reader should meet them. Omit them for a post with none: an empty row of badges is a row of nothing.
122
+ **`readingTime`** `string` · optional · default: — — How long the post takes to read, in the consumer's own words.
123
+ **`children`** `ReactNode` · required · default: — — The post's body. Any flow content: paragraphs, headings, lists, code, images, tables. `Prose` holds it to the reading measure and to the block rhythm.
124
+ **`previous`** `BlogPostNeighbour` · optional · default: — — The post before this one and the post after it, for the trail at the foot of the page. Omit the half there is no neighbour for; a "previous" link with no previous post is a link to nothing.
125
+ **`next`** `BlogPostNeighbour` · optional · default: —
126
+ **`trailLabels`** `BlogPostTrailLabels` · required · default: — — The two words the trail renders above the neighbouring posts' titles. Required whenever either neighbour is passed, because they are words a reader reads and a Page that ships no copy ships no reader-facing copy either.
127
+ **`trailLabel`** `string` · required · default: — — The accessible name of the trail between the neighbouring posts. It is required whenever either neighbour is passed, because it is a word a reader hears and a Page that ships no copy ships no reader-facing copy either. "More posts" is the Page's own phrasing and a site that files posts by release calls the same landmark something else.
128
+ **`indexLink`** `BlogPostNeighbour` · optional · default: — — The way back to the archive the post is filed in, rendered as one call to action under the trail. A prop rather than a hardcoded "back to the index", because the words and the route belong to the site that publishes the post and a Page that held either would be a Page making a claim about a consumer's own site.
129
+ **`footer`** `ReactNode` · optional · default: — — A slot for what comes after the trail: a related-posts grid, a newsletter form, a call to action. A slot rather than a prop because every one of those is a Block, and a Page that took a Block per prop would be a Page whose interface lists the whole catalogue.
130
+ **`className`** `string` · optional · default: — — Layout only, exactly as on every Component. Changing a Prism-owned visual property from here is prohibited.
131
+
132
+ ## Composition
133
+
134
+ **`exports`** `BlogPostPage` — the public runtime names this Page promises
135
+ **`source`** `packages/ui/src/pages/blog-post-page/index.tsx` — the file this Page is defined in
136
+ **`dependencies`** `Badge, CtaLink, Prose, Section` — the public Prism exports this item composes