@nanisoft/prism-llms 0.5.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.
package/dist/extractor.js CHANGED
@@ -36,13 +36,13 @@ export function extractProps(source) {
36
36
  if (!(match[1] ?? '').endsWith('Props'))
37
37
  continue;
38
38
  const openBrace = pattern.lastIndex - 1;
39
- const body = readBalancedBody(source, openBrace);
40
- if (body === undefined)
39
+ const found = readBalancedBody(source, openBrace);
40
+ if (found === undefined)
41
41
  continue;
42
42
  results.push({
43
43
  typeName: match[1] ?? '',
44
44
  ...(match[2] ? { extendsType: match[2].trim() } : {}),
45
- props: parseMembers(body).map(toProp),
45
+ props: parseMembers(found.body).map(toProp),
46
46
  });
47
47
  }
48
48
  return results;
@@ -64,7 +64,15 @@ export function extractExports(source, exportNames) {
64
64
  };
65
65
  });
66
66
  }
67
- /** The `{...}` body of the declaration whose brace sits at `openBrace`. */
67
+ /**
68
+ * The `{...}` body of the declaration whose brace sits at `openBrace`, and the
69
+ * index of the brace that closed it.
70
+ *
71
+ * The index is returned as well as the text because `findTypeBodies` has to
72
+ * resume scanning after the body, and re-walking it with a second depth counter
73
+ * is how the two came to disagree. `undefined` for the text means the body never
74
+ * closed, and the caller stops rather than guessing where it ended.
75
+ */
68
76
  function readBalancedBody(source, openBrace) {
69
77
  let depth = 0;
70
78
  let i = openBrace;
@@ -94,7 +102,7 @@ function readBalancedBody(source, openBrace) {
94
102
  if (ch === '}') {
95
103
  depth -= 1;
96
104
  if (depth === 0)
97
- return source.slice(openBrace + 1, i);
105
+ return { body: source.slice(openBrace + 1, i), end: i };
98
106
  }
99
107
  i += 1;
100
108
  }
@@ -131,6 +139,24 @@ function skipString(source, start, quote) {
131
139
  * the declaration at `variant?: 'icon';` and never reach the second arm, which
132
140
  * is the defect this function exists to remove. Angle brackets are tracked
133
141
  * because a generic argument can carry its own braces.
142
+ *
143
+ * **A body is consumed once and skipped, not re-walked.** `readBalancedBody`
144
+ * returns a body's text, and the scan used to leave `i` on the opening brace and
145
+ * walk the body a second time with its own depth counter. The second walk counts
146
+ * every `{` the balanced read had already accounted for, so the two disagreed and
147
+ * `depth` could not be trusted to reach 0 at the brace that closed the body. The
148
+ * scan therefore depended on finding a terminator it was no longer counting
149
+ * correctly, and the caller concatenates the emitted JavaScript of the same
150
+ * module beside the declaration, so a scan that ran long published every object
151
+ * literal in that implementation as another arm of the type. Advancing `i` to the
152
+ * brace `readBalancedBody` reported removes the second walk rather than trying to
153
+ * correct for it, and one walk cannot disagree with itself.
154
+ *
155
+ * The honest limit on that claim: a declaration whose body closes and is followed
156
+ * immediately by a terminator terminated the scan correctly even before this
157
+ * change, which is why every Page in the package was published correctly and only
158
+ * the first Page with a two-line barrel was not. The defect was a miscount, not a
159
+ * missing terminator, and a miscount is only visible where a miscount is.
134
160
  */
135
161
  function findTypeBodies(source, name) {
136
162
  const pattern = new RegExp(`(?:export\\s+)?(?:type|interface)\\s+${name}\\b`);
@@ -154,16 +180,17 @@ function findTypeBodies(source, name) {
154
180
  continue;
155
181
  }
156
182
  if (ch === '{') {
157
- const body = readBalancedBody(source, i);
158
- if (body === undefined)
183
+ const found = readBalancedBody(source, i);
184
+ if (found === undefined)
159
185
  return bodies;
160
- bodies.push(body);
161
- depth += 1;
186
+ bodies.push(found.body);
187
+ // Resume AFTER the brace that closed the body. The re-walk this replaces
188
+ // counted every `{` in the body a second time and only the closing `}`
189
+ // once, so `depth` never returned to 0 and the scan never terminated.
190
+ i = found.end;
162
191
  continue;
163
192
  }
164
- if (ch === '}')
165
- depth -= 1;
166
- else if (ch === '(')
193
+ if (ch === '(')
167
194
  depth += 1;
168
195
  else if (ch === ')')
169
196
  depth -= 1;
@@ -247,9 +274,9 @@ function resolveAnnotation(annotation, source) {
247
274
  return { props: named.flatMap((body) => parseMembers(body)).map(toProp), inherited: [] };
248
275
  }
249
276
  if (annotation.startsWith('{')) {
250
- const body = readBalancedBody(annotation, 0);
251
- if (body !== undefined)
252
- return { props: parseMembers(body).map(toProp), inherited: [] };
277
+ const found = readBalancedBody(annotation, 0);
278
+ if (found !== undefined)
279
+ return { props: parseMembers(found.body).map(toProp), inherited: [] };
253
280
  }
254
281
  const props = [];
255
282
  const inherited = [];
@@ -6,6 +6,44 @@
6
6
 
7
7
  # @nanisoft/prism-llms
8
8
 
9
+ ## 0.5.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 5c43520: Add `DocsShell`, a documentation page that takes a site's navigation as data
14
+
15
+ `DocsShell` is a documentation screen: a navigation rail, the document, a
16
+ contents rail, and a pager to the neighbouring pages. It replaces the unstyled
17
+ `docs-shell` the retired line published, and it is the Page the three NaniSoft
18
+ product sites already import by that name.
19
+
20
+ The navigation is data. A tree of one page and a tree of twenty-seven render the
21
+ same way, at any depth, and the frame is sized by what the caller passed rather
22
+ than by a fixed arrangement, so three sites whose documentation sets differ in
23
+ shape share one Page with no per-site fork.
24
+
25
+ - `nav` and `toc` are closed unions of `page`, `group` and `divider`. Both are
26
+ read in the order given: nothing is sorted or alphabetised, so a site that
27
+ files its rail as a pipeline and its contents as a reference has both in one
28
+ render.
29
+ - The pager is derived from `nav` and `currentHref`. There is no `neighbours`
30
+ prop, so a neighbour that is not in the tree cannot be expressed, and each
31
+ consumer loses one derivation of its own.
32
+ - A group with no `href` renders a `span` rather than an anchor with no
33
+ destination. The three sites spell that case today as an empty string, and two
34
+ of them carry a stylesheet rule that styles the result back into a label; those
35
+ rules are now deletable.
36
+ - A section carries no status, no badge, no count, no collapse and no sort. A
37
+ status written into a title stays in the title as words.
38
+ - Both rails are bounded against the viewport, because one site's tree is half
39
+ again as tall as another's.
40
+
41
+ The corpus fix that shipped with it: the props extractor read each declaration
42
+ body twice with two depth counters that disagreed, so a Page whose `index.tsx`
43
+ re-exported a value and a type from one sibling published every prop twice and
44
+ swept the members of its navigation union into its own prop list. It now reads
45
+ each body once.
46
+
9
47
  ## 0.5.0
10
48
 
11
49
  ### Minor Changes
@@ -216,6 +254,13 @@ Changesets are appended above this entry for every published change.
216
254
 
217
255
  # @nanisoft/prism-mcp-server
218
256
 
257
+ ## 0.4.1
258
+
259
+ ### Patch Changes
260
+
261
+ - Updated dependencies [5c43520]
262
+ - @nanisoft/prism-llms@0.5.1
263
+
219
264
  ## 0.4.0
220
265
 
221
266
  ### Patch Changes
@@ -438,6 +483,44 @@ Changesets are appended above this entry for every published change.
438
483
 
439
484
  # @nanisoft/prism-ui
440
485
 
486
+ ## 0.7.0
487
+
488
+ ### Minor Changes
489
+
490
+ - 5c43520: Add `DocsShell`, a documentation page that takes a site's navigation as data
491
+
492
+ `DocsShell` is a documentation screen: a navigation rail, the document, a
493
+ contents rail, and a pager to the neighbouring pages. It replaces the unstyled
494
+ `docs-shell` the retired line published, and it is the Page the three NaniSoft
495
+ product sites already import by that name.
496
+
497
+ The navigation is data. A tree of one page and a tree of twenty-seven render the
498
+ same way, at any depth, and the frame is sized by what the caller passed rather
499
+ than by a fixed arrangement, so three sites whose documentation sets differ in
500
+ shape share one Page with no per-site fork.
501
+
502
+ - `nav` and `toc` are closed unions of `page`, `group` and `divider`. Both are
503
+ read in the order given: nothing is sorted or alphabetised, so a site that
504
+ files its rail as a pipeline and its contents as a reference has both in one
505
+ render.
506
+ - The pager is derived from `nav` and `currentHref`. There is no `neighbours`
507
+ prop, so a neighbour that is not in the tree cannot be expressed, and each
508
+ consumer loses one derivation of its own.
509
+ - A group with no `href` renders a `span` rather than an anchor with no
510
+ destination. The three sites spell that case today as an empty string, and two
511
+ of them carry a stylesheet rule that styles the result back into a label; those
512
+ rules are now deletable.
513
+ - A section carries no status, no badge, no count, no collapse and no sort. A
514
+ status written into a title stays in the title as words.
515
+ - Both rails are bounded against the viewport, because one site's tree is half
516
+ again as tall as another's.
517
+
518
+ The corpus fix that shipped with it: the props extractor read each declaration
519
+ body twice with two depth counters that disagreed, so a Page whose `index.tsx`
520
+ re-exported a value and a type from one sibling published every prop twice and
521
+ swept the members of its navigation union into its own prop list. It now reads
522
+ each body once.
523
+
441
524
  ## 0.6.0
442
525
 
443
526
  ### Minor Changes
@@ -3812,15 +3895,6 @@ export default function ProductMarkDemo() {
3812
3895
  **`pack`** `PackId | null` · optional · default: — — The product's pack, which is the mark's hue. Omit it, or pass `null`, for the one entry in a set that has no pack of its own: that entry is drawn as the full spectrum rather than as a colourless mark. See `SPECTRUM` above. `default` is a real answer and not the same as the empty one. The base pack is a pack a product can wear, and it is the absence of a `data-pack` attribute, so a product whose pack is `default` carries no attribute and its core resolves the base pack's `primary` from the page.
3813
3896
  **`size`** `ProductMarkSize` · optional · default: `'md'` — Which of the three drawn sizes to use.
3814
3897
  **`className`** `string` · optional · default: — — Layout only, exactly as on every Component. Changing a Prism-owned visual property from here is prohibited, and this Component spreads no other prop, so there is no second route to its ink.
3815
- **`stored`** `string | null` · required · default: — — Raw contents of the key this line writes, or `null` when it is absent.
3816
- **`legacy`** `string | null` · required · default: — — Raw contents of one key this line no longer writes, or `null`.
3817
- **`documentPack`** `string | null` · required · default: — — `data-pack` on the root element as rendered, valid or not.
3818
- **`documentDark`** `boolean` · required · default: — — Whether the root element carries `.dark` as rendered.
3819
- **`defaultPack`** `PackId` · required · default: —
3820
- **`defaultMode`** `Mode` · required · default: —
3821
- **`mode`** `Mode` · required · default: —
3822
- **`origin`** `ThemeOrigin` · required · default: — — Which source named the pair, for `THEME_ORIGIN_ATTRIBUTE`.
3823
- **`key`** `string` · required · default: —
3824
3898
 
3825
3899
 
3826
3900
  ---
@@ -4465,12 +4539,12 @@ export default function SelectDemo() {
4465
4539
  **`items`** `Record<string, React.ReactNode> | ReadonlyArray<{ label: React.ReactNode; value: string; }>` · optional · default: — — The options, keyed by value, so the trigger can render a label before any item has mounted. Without it, `SelectValue` shows the raw value.
4466
4540
  **`itemToStringLabel`** `(value: string) => string` · optional · default: — — Converts an item value to the label shown in the trigger.
4467
4541
  **`children`** `React.ReactNode` · optional · default: — — The trigger and the popup content.
4468
- **`label`** `React.ReactNode` · required · default: —
4469
4542
  **`size`** `'sm' | 'default'` · optional · default: `default` — The trigger's height.
4470
4543
  **`placeholder`** `React.ReactNode` · optional · default: — — Shown when no value is selected.
4471
4544
  **`side`** `'top' | 'right' | 'bottom' | 'left'` · optional · default: `bottom` — Which side of the trigger the list is placed on.
4472
4545
  **`align`** `'start' | 'center' | 'end'` · optional · default: `center` — How the list aligns against the trigger.
4473
4546
  **`sideOffset`** `number` · optional · default: `4` — The distance in pixels between the trigger and the list.
4547
+ **`label`** `string` · optional · default: — — A text label used for keyboard typeahead when the option text differs.
4474
4548
 
4475
4549
 
4476
4550
  ---
@@ -6455,10 +6529,6 @@ export default function NoteGrid01Demo() {
6455
6529
  **`notes`** `readonly NoteGrid01Note[]` · required · default: — — The notes, in the order a reader should meet them. Order is the caller's because it is a claim about which point matters first.
6456
6530
  **`caption`** `string` · optional · default: — — The line under the grid, for the sentence that qualifies it: what the points do not cover, or where to read more.
6457
6531
  **`headingLevel`** `HeadingLevel` · optional · default: — — Heading level for the section title. See `HeadingLevel`.
6458
- **`body`** `string` · required · default: — — One or two sentences about the point.
6459
- **`children`** `[title ? (_jsx(SectionHeading, { as: headingLevel, align: "left", eyebrow: eyebrow, title: title, description: description, className: "mb-12" })) : null, _jsx("dl", { "data-slot": "note-grid", className: "grid gap-8 sm:grid-cols-2 lg:grid-cols-3", children: notes.map((note) => (_jsxs("div", { "data-slot": "note-grid-note", className: "border-border flex flex-col gap-2 border-t pt-4", children: [_jsx("dt", { className: "text-sm font-semibold", children: note.title }), _jsx("dd", { className: "text-muted-foreground text-pretty text-sm", children: note.body })] }, note.title))) }), caption ? _jsx("p", { className: "text-muted-foreground mt-6 text-pretty text-sm", children: caption }) : null]` · required · default: —
6460
- **`as`** `headingLevel, align: "left", eyebrow: eyebrow, title: title, description: description, className: "mb-12"` · required · default: —
6461
- **`className`** `"text-sm font-semibold", children: note.title` · required · default: —
6462
6532
 
6463
6533
  ## Composition
6464
6534
 
@@ -6876,12 +6946,6 @@ export default function ProductGrid01Demo() {
6876
6946
  **`products`** `readonly ProductGrid01Product[]` · required · default: — — The products, in the order a reader should meet them. Order is the caller's because it is a claim about which product matters first.
6877
6947
  **`caption`** `string` · optional · default: — — The line under the grid, for the sentence that qualifies it: what the set does not include, or where to read more.
6878
6948
  **`headingLevel`** `HeadingLevel` · optional · default: — — Heading level for the section title. See `HeadingLevel`.
6879
- **`id`** `string` · required · default: — — The product's key within the set. Carried on the markup as `data-product` through `ProductMark`, so a test can name one product rather than the first one, and two products in one document are individually addressable.
6880
- **`name`** `string` · required · default: — — The product's own name, which is the second part of its mark. It is the anchor of the row and the first thing a reader reads.
6881
- **`pack`** `ProductMarkProps['pack']` · optional · default: — — The product's pack, which is the hue of the mark's core. Omit it, or pass `null`, for the one member of a set that has no pack of its own: that member wears the full spectrum rather than a colourless mark.
6882
- **`tagline`** `string` · required · default: — — One line about what the product is.
6883
- **`href`** `string` · required · default: — — Where the row goes. Rendered as a native anchor's `href`, so the row is a link with the browser's own affordances rather than a click handler.
6884
- **`newTab`** `boolean` · optional · default: — — Opens the destination in a new browsing context, which defaults the link relationship to `noopener noreferrer`. Prism does not decide what counts as external, so the caller declares it.
6885
6949
 
6886
6950
  ## Composition
6887
6951
 
@@ -7032,8 +7096,6 @@ export default function SettingsPanel01Demo() {
7032
7096
  **`onSubmit`** `(event: FormEvent<HTMLFormElement>) => void` · optional · default: —
7033
7097
  **`secondaryAction`** `{ label: string; onClick?: () => void; }` · optional · default: — — A secondary control, such as Cancel.
7034
7098
  **`footer`** `ReactNode` · optional · default: — — Extra content in the footer, e.g. a status note.
7035
- **`label`** `string` · required · default: —
7036
- **`onClick`** `() => void` · optional · default: —
7037
7099
 
7038
7100
  ## Composition
7039
7101
 
@@ -7268,10 +7330,6 @@ export default function SiteHeaderDemo() {
7268
7330
  **`actions`** `ReactNode` · optional · default: — — A slot for the controls a product owns on the right: an account menu, a sign in link, a mode toggle, a theme picker. A slot rather than a prop, because every one of those is application state and none of them is a fact a header can be told.
7269
7331
  **`sticky`** `boolean` · optional · default: — — Keeps the header at the top of the viewport as the page scrolls. Off by default, because a sticky header is a decision about a page rather than about a header, and a header that is sticky on a page with no scroll costs the reader a strip of their viewport.
7270
7332
  **`className`** `string` · optional · default: — — Layout only, exactly as on every Component. Changing a Prism-owned visual property from here is prohibited.
7271
- **`label`** `string` · required · default: — — The text of the link.
7272
- **`href`** `string` · required · default: — — Where the link goes.
7273
- **`current`** `boolean` · optional · default: — — Marks this link as the page the reader is on. Set it on the one link whose destination is the current route; leave it off every other link.
7274
- **`newTab`** `boolean` · optional · default: — — Opens the destination in a new browsing context, which defaults the link relationship to `noopener noreferrer`. Prism does not decide what counts as external: a cross-origin destination is not by itself a reason to open a tab.
7275
7333
 
7276
7334
  ## Composition
7277
7335
 
@@ -8148,6 +8206,228 @@ export default function DashboardPageDemo() {
8148
8206
  - [Stats01](https://prism.nanisoft.com/blocks/stats-01.md)
8149
8207
 
8150
8208
 
8209
+ ---
8210
+
8211
+ # DocsShell
8212
+
8213
+ A documentation screen: a navigation rail whose shape arrives as data, the document at the measure, a contents rail, and a pager derived from the navigation rather than passed.
8214
+
8215
+ ```tsx
8216
+ import { DocsShell } from '@nanisoft/prism-ui/pages/docs-shell'
8217
+ ```
8218
+
8219
+ ## Overview
8220
+
8221
+ DocsShell is a documentation screen: a navigation rail, the document, a contents
8222
+ rail, and the pager to the neighbouring pages.
8223
+
8224
+ **It is the Page the other three NaniSoft sites import, and the name is theirs.**
8225
+ All three already read `import { DocsShell, type DocsNavEntry } from
8226
+ '@nanisoft/prism-ui/pages'` in their own document template, and all three map
8227
+ their page tree into that shape in their own `lib/to-prism-tree`. A published
8228
+ name cannot be cheaply changed and there is no redirect lane for item routes, so
8229
+ the name follows those three import lines rather than this file's shape. It was
8230
+ `docs-shell` in the retired line too, where it was an empty `div` and a class
8231
+ name each site then had to style from scratch.
8232
+
8233
+ **The shape arrives as data.** The three sites publish full documentation sets
8234
+ and none of them agrees on the shape:
8235
+
8236
+ - Nexus files six sections of five pages, each named for a documentation genre,
8237
+ each with an index.
8238
+ - Atlas files four sections, one of which holds nine pages under a single
8239
+ heading, and not one of them has an index.
8240
+ - AlphaLens files six sections separated by rules, named for subject matter
8241
+ rather than for a genre, with a status inside three of the titles.
8242
+
8243
+ The tree is a prop and the Page renders it, so none of the three had to be told
8244
+ which of the others it looks like.
8245
+
8246
+ ## Guidelines
8247
+
8248
+ ### When to use
8249
+
8250
+ Use DocsShell as the body of the route that renders one documentation page,
8251
+ handed the page tree, the document's own outline, and the address being rendered.
8252
+
8253
+ Use it for every page of a documentation set. A documentation site whose pages
8254
+ each carry their own rail is a site whose rail drifts.
8255
+
8256
+ ### When not to use
8257
+
8258
+ Do not use it for a single-page site. It is a whole screen around a tree, and a
8259
+ site with one page has no tree.
8260
+
8261
+ Do not use it for a blog. A post is a dated argument about one thing, and
8262
+ `BlogPostPage` is that screen.
8263
+
8264
+ Do not hand it a `nav` you built by parsing a folder tree at render time. Map
8265
+ the tree once, in your own adapter, and pass the result: a Page that walked a
8266
+ filesystem would be a Page that fetches, and a Page imports no router and no data
8267
+ client.
8268
+
8269
+ ### Best practices
8270
+
8271
+ - **A section is a label, not a control.** It carries no status, no badge, no
8272
+ count, no collapse and no sort, and that is the whole of its interface. AlphaLens
8273
+ files its documentation as a pipeline, where "Unified data contract (approved)"
8274
+ is a stage of the work and not a topic a reader may visit in any order, and
8275
+ every structural affordance says the opposite. A status in the title is part of
8276
+ the title and stays there as words: the Page reads no parentheses, matches no
8277
+ vocabulary, and renders no pill beside a heading that has one.
8278
+ - **Omit `href` on a group that has no index.** That absence is what makes the
8279
+ heading a label rather than a route, and it is the same thing all three sites
8280
+ already spell as `url: node.index?.url ?? ''`, which two of them then restyle
8281
+ back into a label in their own stylesheets. Naming the case in the type makes
8282
+ those rules dead rather than load-bearing, and the type is the only thing that
8283
+ had to change.
8284
+ - **Do not pass a previous and a next.** The pager is derived from `nav` and
8285
+ `currentHref`, which removes one derivation from every consumer. It is derived
8286
+ rather than passed for a second reason: a caller who passes neighbours can pass
8287
+ a neighbour that is not in the tree, and this is the one arrangement where a
8288
+ reader would notice.
8289
+ - **Give the rail and the contents their own orders, and let both stand.** `nav`
8290
+ is in the order the rail shows and `toc` is in the order the contents rail
8291
+ shows. Nothing is sorted, deduplicated or alphabetised. A site that files its
8292
+ rail as a pipeline and its contents as a reference has both in one render.
8293
+ - Pass `toc` whole or not at all. A partial outline is a contents list that lies
8294
+ about the page.
8295
+ - Omit `title` when your document owns its own heading. The Page renders no empty
8296
+ `h1` to fill the gap, so a document with no title keeps exactly the outline its
8297
+ own content declares.
8298
+ - Name the three regions. `navLabel`, `tocLabel` and `pagerLabel` are required
8299
+ because they are words a reader hears, and a Page that ships no copy ships no
8300
+ reader-facing copy either. A site that files its pages as a pipeline and its
8301
+ contents as a reference names the three regions three different ways.
8302
+
8303
+ ### Content guidelines
8304
+
8305
+ - Put a section's stage in its title, as AlphaLens does, and let it stay a
8306
+ sentence. The Page will not pull it out into a badge for you, and a badge is a
8307
+ state the reader can act on, which a stage of a pipeline is not.
8308
+ - Keep a rule's title short or leave it empty. A rule with words is a label a
8309
+ reader reads twice: once in the rule and once on the section beneath it.
8310
+ - Keep a section index page's title the same as its section's title, so a reader
8311
+ who arrives at the index and a reader who is already inside the section are
8312
+ reading the same words.
8313
+
8314
+ **A documentation screen: the rail, the document, the contents and the pager.**
8315
+
8316
+ ```tsx
8317
+ import { DocsShell, type DocsNavEntry } from '@nanisoft/prism-ui/pages/docs-shell'
8318
+
8319
+ /**
8320
+ * The shape the three consumer sites actually publish, taken from AlphaLens
8321
+ * because it is the one that exercises every part of the interface at once: a
8322
+ * status inside a section title, a rule between sections, and a section whose
8323
+ * own index is a page a reader lands on.
8324
+ */
8325
+ const NAV: DocsNavEntry[] = [
8326
+ { type: 'page', title: 'AlphaLens', href: '#alphalens' },
8327
+ { type: 'divider', title: 'Data platform (live)' },
8328
+ {
8329
+ type: 'group',
8330
+ title: 'Data platform (live)',
8331
+ href: '#data-platform',
8332
+ items: [
8333
+ { type: 'page', title: 'Capture', href: '#capture' },
8334
+ { type: 'page', title: 'Storage', href: '#storage' },
8335
+ { type: 'page', title: 'Operations', href: '#platform-operations' },
8336
+ ],
8337
+ },
8338
+ { type: 'divider', title: 'Unified data contract (approved)' },
8339
+ {
8340
+ type: 'group',
8341
+ title: 'Unified data contract (approved)',
8342
+ href: '#data-contract',
8343
+ items: [
8344
+ { type: 'page', title: 'Data feed view', href: '#feed-view' },
8345
+ { type: 'page', title: 'Timestamps and symbols', href: '#timestamps' },
8346
+ ],
8347
+ },
8348
+ { type: 'divider', title: 'Research pipeline (designed)' },
8349
+ {
8350
+ // No `href`: this group is a label, because its folder holds no index page.
8351
+ type: 'group',
8352
+ title: 'Research pipeline (designed)',
8353
+ items: [
8354
+ { type: 'page', title: 'Anatomy', href: '#anatomy' },
8355
+ { type: 'page', title: 'NSE portability', href: '#nse-portability' },
8356
+ ],
8357
+ },
8358
+ ]
8359
+
8360
+ /** The headings inside this page. Your own pipeline knows them; Prism does not. */
8361
+ const TOC: DocsNavEntry[] = [
8362
+ { type: 'page', title: 'The capture path', href: '#the-capture-path' },
8363
+ { type: 'page', title: 'What the store holds', href: '#what-the-store-holds' },
8364
+ { type: 'page', title: 'Where this is heading', href: '#where-this-is-heading' },
8365
+ ]
8366
+
8367
+ /** A documentation screen: the rail, the document, the contents and the pager. */
8368
+ export default function DocsShellDemo() {
8369
+ return (
8370
+ <DocsShell
8371
+ title="Storage"
8372
+ description="Where a full chain of bars lands, and how it is partitioned."
8373
+ nav={NAV}
8374
+ toc={TOC}
8375
+ currentHref="#storage"
8376
+ navLabel="AlphaLens documentation"
8377
+ tocLabel="On this page"
8378
+ pagerLabel="AlphaLens pages"
8379
+ pagerLabels={{ previous: 'Previous', next: 'Next' }}
8380
+ >
8381
+ <p>
8382
+ The chain is written in columnar blocks, one per session per symbol, and
8383
+ nothing is rewritten to correct it. What lands is what the exchange sent.
8384
+ </p>
8385
+ <h2 id="the-capture-path">The capture path</h2>
8386
+ <p>
8387
+ A capture is a session and a symbol, never a whole day. A day is a
8388
+ partition over sessions, which is what makes a backtest cheap to repeat
8389
+ and expensive to store twice.
8390
+ </p>
8391
+ <h2 id="what-the-store-holds">What the store holds</h2>
8392
+ <ul>
8393
+ <li>Every strike, call and put, at the exchange clock.</li>
8394
+ <li>Open interest and the implied volatility of each strike.</li>
8395
+ <li>The index future and the spot it settled against.</li>
8396
+ </ul>
8397
+ <h2 id="where-this-is-heading">Where this is heading</h2>
8398
+ <p>
8399
+ The same path, ported to .NET, is designed rather than built. The stage it
8400
+ is at is in its title, because a stage of a pipeline is not a topic a
8401
+ reader may visit in any order.
8402
+ </p>
8403
+ </DocsShell>
8404
+ )
8405
+ }
8406
+ ```
8407
+
8408
+ ## Props
8409
+
8410
+ **`title`** `string` · optional · default: — — The page's title, and the document's `h1`. It is optional because the three consumer sites do not agree about whether a documentation page has one: two render the frontmatter title as the page heading, and one lets the document's own Markdown own it. When it is absent the Page renders no heading at all rather than an empty one, so the document keeps exactly the outline its own content declares.
8411
+ **`description`** `ReactNode` · optional · default: — — One or two sentences under the title: what this part of the documentation covers. A `ReactNode` because a site that files a page for a pipeline states the pipeline's stage as markup rather than as a sentence.
8412
+ **`nav`** `readonly DocsNavEntry[]` · optional · default: — — The documentation tree, in the order a reader should meet it. The whole shape arrives here, so the Page holds no opinion about how many sections a site has, how deep they nest, or what a section is called.
8413
+ **`toc`** `readonly DocsNavEntry[]` · optional · default: — — The headings inside this page, in document order, for the contents rail. It is the consumer's document that knows its own outline, because Prism does not parse Markdown and a Page that guessed at it would guess wrong on every document rather than on some. Pass the whole outline or none: a partial one is a contents list that lies about the page.
8414
+ **`currentHref`** `string` · optional · default: — — The address of the page being rendered. The rail marks it with `aria-current="page"` and the pager derives its two neighbours from it, so the same navigation renders a different pager for every page without the caller deriving anything.
8415
+ **`navLabel`** `string` · required · default: — — The accessible name of the rail. Required whenever `nav` is passed, because it is a word a reader hears and a Page that ships no copy ships no reader-facing copy either.
8416
+ **`tocLabel`** `string` · required · default: — — The accessible name of the contents rail. Required whenever `toc` is passed, for the same reason `navLabel` is.
8417
+ **`pagerLabel`** `string` · required · default: — — The accessible name of the pager. Required whenever there is a neighbour to reach, for the same reason `navLabel` is.
8418
+ **`pagerLabels`** `DocsPagerLabels` · required · default: — — The two words the pager renders above the neighbouring pages' titles. Required whenever either neighbour exists, for the same reason `navLabel` is.
8419
+ **`children`** `ReactNode` · required · default: — — The document's own body. Any flow content: paragraphs, headings, lists, code, images, tables. `Prose` holds it to the reading measure and to the block rhythm, and it is passed `fullWidth` so the document fills the column the frame gives it rather than sitting in a second measure inside one.
8420
+ **`header`** `ReactNode` · optional · default: — — A slot above the frame, inside the container: a breadcrumb trail, a version picker, a search field. A slot rather than a prop because each of those is a Block, and a Page that took one per prop would be a Page whose interface lists the whole catalogue.
8421
+ **`footer`** `ReactNode` · optional · default: — — A slot below the frame, inside the container: a feedback prompt, a link back to the section index, a support address.
8422
+ **`className`** `string` · optional · default: — — Layout only, exactly as on every Component. Changing a Prism-owned visual property from here is prohibited.
8423
+
8424
+ ## Composition
8425
+
8426
+ **`exports`** `DocsShell` — the public runtime names this Page promises
8427
+ **`source`** `packages/ui/src/pages/docs-shell/index.tsx` — the file this Page is defined in
8428
+ **`dependencies`** `Prose` — the public Prism exports this item composes
8429
+
8430
+
8151
8431
  ---
8152
8432
 
8153
8433
  # MarketingPage
package/dist/llms.txt CHANGED
@@ -98,13 +98,14 @@
98
98
  - [AuthPage](https://prism.nanisoft.com/pages/auth-page.md): A full authentication screen composing a sign-in form with an optional supporting card.
99
99
  - [BlogPostPage](https://prism.nanisoft.com/pages/blog-post-page.md): 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.
100
100
  - [DashboardPage](https://prism.nanisoft.com/pages/dashboard-page.md): A full dashboard screen composing the app shell, a page header, a KPI row and a data table.
101
+ - [DocsShell](https://prism.nanisoft.com/pages/docs-shell.md): A documentation screen: a navigation rail whose shape arrives as data, the document at the measure, a contents rail, and a pager derived from the navigation rather than passed.
101
102
  - [MarketingPage](https://prism.nanisoft.com/pages/marketing-page.md): A complete marketing page composing a hero, a feature grid, a KPI row, a plan comparison and a closing call to action.
102
103
  - [NotFoundPage](https://prism.nanisoft.com/pages/not-found-page.md): A complete not-found screen: the status code as the heading of the page, one sentence about what happened, and the ways out.
103
104
  - [SettingsPage](https://prism.nanisoft.com/pages/settings-page.md): A full settings screen composing the app shell, a page header and tabs of grouped settings forms.
104
105
 
105
106
  ## Changelogs
106
107
 
107
- - [@nanisoft/prism-llms](https://prism.nanisoft.com/changelogs/prism-llms.md): 0.5.0, 0.2.0, 0.1.0
108
- - [@nanisoft/prism-mcp-server](https://prism.nanisoft.com/changelogs/prism-mcp-server.md): 0.4.0, 0.2.0, 0.1.0
108
+ - [@nanisoft/prism-llms](https://prism.nanisoft.com/changelogs/prism-llms.md): 0.5.1, 0.5.0, 0.2.0, 0.1.0
109
+ - [@nanisoft/prism-mcp-server](https://prism.nanisoft.com/changelogs/prism-mcp-server.md): 0.4.1, 0.4.0, 0.2.0, 0.1.0
109
110
  - [@nanisoft/prism-tokens](https://prism.nanisoft.com/changelogs/prism-tokens.md): 0.6.0, 0.5.1, 0.5.0
110
- - [@nanisoft/prism-ui](https://prism.nanisoft.com/changelogs/prism-ui.md): 0.6.0, 0.5.1, 0.5.0
111
+ - [@nanisoft/prism-ui](https://prism.nanisoft.com/changelogs/prism-ui.md): 0.7.0, 0.6.0, 0.5.1, 0.5.0
@@ -90,10 +90,6 @@ export default function NoteGrid01Demo() {
90
90
  **`notes`** `readonly NoteGrid01Note[]` · required · default: — — The notes, in the order a reader should meet them. Order is the caller's because it is a claim about which point matters first.
91
91
  **`caption`** `string` · optional · default: — — The line under the grid, for the sentence that qualifies it: what the points do not cover, or where to read more.
92
92
  **`headingLevel`** `HeadingLevel` · optional · default: — — Heading level for the section title. See `HeadingLevel`.
93
- **`body`** `string` · required · default: — — One or two sentences about the point.
94
- **`children`** `[title ? (_jsx(SectionHeading, { as: headingLevel, align: "left", eyebrow: eyebrow, title: title, description: description, className: "mb-12" })) : null, _jsx("dl", { "data-slot": "note-grid", className: "grid gap-8 sm:grid-cols-2 lg:grid-cols-3", children: notes.map((note) => (_jsxs("div", { "data-slot": "note-grid-note", className: "border-border flex flex-col gap-2 border-t pt-4", children: [_jsx("dt", { className: "text-sm font-semibold", children: note.title }), _jsx("dd", { className: "text-muted-foreground text-pretty text-sm", children: note.body })] }, note.title))) }), caption ? _jsx("p", { className: "text-muted-foreground mt-6 text-pretty text-sm", children: caption }) : null]` · required · default: —
95
- **`as`** `headingLevel, align: "left", eyebrow: eyebrow, title: title, description: description, className: "mb-12"` · required · default: —
96
- **`className`** `"text-sm font-semibold", children: note.title` · required · default: —
97
93
 
98
94
  ## Composition
99
95
 
@@ -99,12 +99,6 @@ export default function ProductGrid01Demo() {
99
99
  **`products`** `readonly ProductGrid01Product[]` · required · default: — — The products, in the order a reader should meet them. Order is the caller's because it is a claim about which product matters first.
100
100
  **`caption`** `string` · optional · default: — — The line under the grid, for the sentence that qualifies it: what the set does not include, or where to read more.
101
101
  **`headingLevel`** `HeadingLevel` · optional · default: — — Heading level for the section title. See `HeadingLevel`.
102
- **`id`** `string` · required · default: — — The product's key within the set. Carried on the markup as `data-product` through `ProductMark`, so a test can name one product rather than the first one, and two products in one document are individually addressable.
103
- **`name`** `string` · required · default: — — The product's own name, which is the second part of its mark. It is the anchor of the row and the first thing a reader reads.
104
- **`pack`** `ProductMarkProps['pack']` · optional · default: — — The product's pack, which is the hue of the mark's core. Omit it, or pass `null`, for the one member of a set that has no pack of its own: that member wears the full spectrum rather than a colourless mark.
105
- **`tagline`** `string` · required · default: — — One line about what the product is.
106
- **`href`** `string` · required · default: — — Where the row goes. Rendered as a native anchor's `href`, so the row is a link with the browser's own affordances rather than a click handler.
107
- **`newTab`** `boolean` · optional · default: — — Opens the destination in a new browsing context, which defaults the link relationship to `noopener noreferrer`. Prism does not decide what counts as external, so the caller declares it.
108
102
 
109
103
  ## Composition
110
104
 
@@ -138,8 +138,6 @@ export default function SettingsPanel01Demo() {
138
138
  **`onSubmit`** `(event: FormEvent<HTMLFormElement>) => void` · optional · default: —
139
139
  **`secondaryAction`** `{ label: string; onClick?: () => void; }` · optional · default: — — A secondary control, such as Cancel.
140
140
  **`footer`** `ReactNode` · optional · default: — — Extra content in the footer, e.g. a status note.
141
- **`label`** `string` · required · default: —
142
- **`onClick`** `() => void` · optional · default: —
143
141
 
144
142
  ## Composition
145
143
 
@@ -107,10 +107,6 @@ export default function SiteHeaderDemo() {
107
107
  **`actions`** `ReactNode` · optional · default: — — A slot for the controls a product owns on the right: an account menu, a sign in link, a mode toggle, a theme picker. A slot rather than a prop, because every one of those is application state and none of them is a fact a header can be told.
108
108
  **`sticky`** `boolean` · optional · default: — — Keeps the header at the top of the viewport as the page scrolls. Off by default, because a sticky header is a decision about a page rather than about a header, and a header that is sticky on a page with no scroll costs the reader a strip of their viewport.
109
109
  **`className`** `string` · optional · default: — — Layout only, exactly as on every Component. Changing a Prism-owned visual property from here is prohibited.
110
- **`label`** `string` · required · default: — — The text of the link.
111
- **`href`** `string` · required · default: — — Where the link goes.
112
- **`current`** `boolean` · optional · default: — — Marks this link as the page the reader is on. Set it on the one link whose destination is the current route; leave it off every other link.
113
- **`newTab`** `boolean` · optional · default: — — Opens the destination in a new browsing context, which defaults the link relationship to `noopener noreferrer`. Prism does not decide what counts as external: a cross-origin destination is not by itself a reason to open a tab.
114
110
 
115
111
  ## Composition
116
112
 
@@ -1,5 +1,43 @@
1
1
  # @nanisoft/prism-llms
2
2
 
3
+ ## 0.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 5c43520: Add `DocsShell`, a documentation page that takes a site's navigation as data
8
+
9
+ `DocsShell` is a documentation screen: a navigation rail, the document, a
10
+ contents rail, and a pager to the neighbouring pages. It replaces the unstyled
11
+ `docs-shell` the retired line published, and it is the Page the three NaniSoft
12
+ product sites already import by that name.
13
+
14
+ The navigation is data. A tree of one page and a tree of twenty-seven render the
15
+ same way, at any depth, and the frame is sized by what the caller passed rather
16
+ than by a fixed arrangement, so three sites whose documentation sets differ in
17
+ shape share one Page with no per-site fork.
18
+
19
+ - `nav` and `toc` are closed unions of `page`, `group` and `divider`. Both are
20
+ read in the order given: nothing is sorted or alphabetised, so a site that
21
+ files its rail as a pipeline and its contents as a reference has both in one
22
+ render.
23
+ - The pager is derived from `nav` and `currentHref`. There is no `neighbours`
24
+ prop, so a neighbour that is not in the tree cannot be expressed, and each
25
+ consumer loses one derivation of its own.
26
+ - A group with no `href` renders a `span` rather than an anchor with no
27
+ destination. The three sites spell that case today as an empty string, and two
28
+ of them carry a stylesheet rule that styles the result back into a label; those
29
+ rules are now deletable.
30
+ - A section carries no status, no badge, no count, no collapse and no sort. A
31
+ status written into a title stays in the title as words.
32
+ - Both rails are bounded against the viewport, because one site's tree is half
33
+ again as tall as another's.
34
+
35
+ The corpus fix that shipped with it: the props extractor read each declaration
36
+ body twice with two depth counters that disagreed, so a Page whose `index.tsx`
37
+ re-exported a value and a type from one sibling published every prop twice and
38
+ swept the members of its navigation union into its own prop list. It now reads
39
+ each body once.
40
+
3
41
  ## 0.5.0
4
42
 
5
43
  ### Minor Changes
@@ -1,5 +1,12 @@
1
1
  # @nanisoft/prism-mcp-server
2
2
 
3
+ ## 0.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [5c43520]
8
+ - @nanisoft/prism-llms@0.5.1
9
+
3
10
  ## 0.4.0
4
11
 
5
12
  ### Patch Changes