wiki-formant 0.12.0 → 0.15.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 (137) hide show
  1. package/README.md +32 -26
  2. package/bin/check-classes.mjs +2 -3
  3. package/dist/analytics.d.ts +0 -1
  4. package/dist/analytics.js +0 -1
  5. package/dist/block-views.d.ts +97 -0
  6. package/dist/block-views.js +119 -0
  7. package/dist/blocks.d.ts +0 -1
  8. package/dist/blocks.js +1 -2
  9. package/dist/combobox.d.ts +0 -1
  10. package/dist/combobox.js +0 -1
  11. package/dist/conformance.d.ts +10 -3
  12. package/dist/conformance.js +73 -36
  13. package/dist/crawlers.d.ts +0 -1
  14. package/dist/crawlers.js +0 -1
  15. package/dist/dom.d.ts +0 -1
  16. package/dist/dom.js +0 -1
  17. package/dist/editor.d.ts +101 -0
  18. package/dist/editor.js +218 -0
  19. package/dist/feed.d.ts +0 -1
  20. package/dist/feed.js +0 -1
  21. package/dist/freshness.d.ts +0 -1
  22. package/dist/freshness.js +2 -2
  23. package/dist/headings.d.ts +0 -1
  24. package/dist/headings.js +1 -3
  25. package/dist/html.d.ts +16 -0
  26. package/dist/html.js +23 -0
  27. package/dist/http.d.ts +32 -1
  28. package/dist/http.js +40 -4
  29. package/dist/index.d.ts +2 -2
  30. package/dist/index.js +2 -2
  31. package/dist/license.d.ts +0 -1
  32. package/dist/license.js +0 -1
  33. package/dist/link-check.d.ts +92 -0
  34. package/dist/link-check.js +249 -0
  35. package/dist/links.d.ts +0 -1
  36. package/dist/links.js +1 -6
  37. package/dist/maps.d.ts +0 -1
  38. package/dist/maps.js +2 -2
  39. package/dist/markdown.d.ts +1 -1
  40. package/dist/markdown.js +34 -3
  41. package/dist/mcp.d.ts +43 -3
  42. package/dist/mcp.js +37 -11
  43. package/dist/pagination.d.ts +21 -2
  44. package/dist/pagination.js +32 -3
  45. package/dist/rate-limit.d.ts +32 -1
  46. package/dist/rate-limit.js +51 -1
  47. package/dist/react-server.d.ts +91 -9
  48. package/dist/react-server.js +68 -16
  49. package/dist/react.d.ts +37 -48
  50. package/dist/react.js +44 -23
  51. package/dist/revisions.d.ts +0 -1
  52. package/dist/revisions.js +0 -1
  53. package/dist/rola.d.ts +0 -1
  54. package/dist/rola.js +0 -1
  55. package/dist/seeded.d.ts +11 -0
  56. package/dist/seeded.js +32 -0
  57. package/dist/sidebar.d.ts +0 -1
  58. package/dist/sidebar.js +0 -1
  59. package/dist/taxonomy.d.ts +0 -1
  60. package/dist/taxonomy.js +0 -1
  61. package/dist/text.d.ts +14 -1
  62. package/dist/text.js +15 -2
  63. package/dist/tiptap.d.ts +1 -1
  64. package/dist/tiptap.js +1 -2
  65. package/dist/validation.d.ts +2 -3
  66. package/dist/validation.js +4 -5
  67. package/dist/versioning.d.ts +0 -1
  68. package/dist/versioning.js +0 -1
  69. package/dist/well-known.d.ts +19 -2
  70. package/dist/well-known.js +28 -2
  71. package/dist/x402.d.ts +2 -11
  72. package/dist/x402.js +4 -16
  73. package/package.json +52 -27
  74. package/dist/analytics.d.ts.map +0 -1
  75. package/dist/analytics.js.map +0 -1
  76. package/dist/blocks.d.ts.map +0 -1
  77. package/dist/blocks.js.map +0 -1
  78. package/dist/combobox.d.ts.map +0 -1
  79. package/dist/combobox.js.map +0 -1
  80. package/dist/conformance.d.ts.map +0 -1
  81. package/dist/conformance.js.map +0 -1
  82. package/dist/crawlers.d.ts.map +0 -1
  83. package/dist/crawlers.js.map +0 -1
  84. package/dist/dom.d.ts.map +0 -1
  85. package/dist/dom.js.map +0 -1
  86. package/dist/entities.d.ts +0 -3
  87. package/dist/entities.d.ts.map +0 -1
  88. package/dist/entities.js +0 -30
  89. package/dist/entities.js.map +0 -1
  90. package/dist/feed.d.ts.map +0 -1
  91. package/dist/feed.js.map +0 -1
  92. package/dist/freshness.d.ts.map +0 -1
  93. package/dist/freshness.js.map +0 -1
  94. package/dist/headings.d.ts.map +0 -1
  95. package/dist/headings.js.map +0 -1
  96. package/dist/http.d.ts.map +0 -1
  97. package/dist/http.js.map +0 -1
  98. package/dist/index.d.ts.map +0 -1
  99. package/dist/index.js.map +0 -1
  100. package/dist/license.d.ts.map +0 -1
  101. package/dist/license.js.map +0 -1
  102. package/dist/links.d.ts.map +0 -1
  103. package/dist/links.js.map +0 -1
  104. package/dist/maps.d.ts.map +0 -1
  105. package/dist/maps.js.map +0 -1
  106. package/dist/markdown.d.ts.map +0 -1
  107. package/dist/markdown.js.map +0 -1
  108. package/dist/mcp.d.ts.map +0 -1
  109. package/dist/mcp.js.map +0 -1
  110. package/dist/pagination.d.ts.map +0 -1
  111. package/dist/pagination.js.map +0 -1
  112. package/dist/rate-limit.d.ts.map +0 -1
  113. package/dist/rate-limit.js.map +0 -1
  114. package/dist/react-server.d.ts.map +0 -1
  115. package/dist/react-server.js.map +0 -1
  116. package/dist/react.d.ts.map +0 -1
  117. package/dist/react.js.map +0 -1
  118. package/dist/revisions.d.ts.map +0 -1
  119. package/dist/revisions.js.map +0 -1
  120. package/dist/rola.d.ts.map +0 -1
  121. package/dist/rola.js.map +0 -1
  122. package/dist/sidebar.d.ts.map +0 -1
  123. package/dist/sidebar.js.map +0 -1
  124. package/dist/taxonomy.d.ts.map +0 -1
  125. package/dist/taxonomy.js.map +0 -1
  126. package/dist/text.d.ts.map +0 -1
  127. package/dist/text.js.map +0 -1
  128. package/dist/tiptap.d.ts.map +0 -1
  129. package/dist/tiptap.js.map +0 -1
  130. package/dist/validation.d.ts.map +0 -1
  131. package/dist/validation.js.map +0 -1
  132. package/dist/versioning.d.ts.map +0 -1
  133. package/dist/versioning.js.map +0 -1
  134. package/dist/well-known.d.ts.map +0 -1
  135. package/dist/well-known.js.map +0 -1
  136. package/dist/x402.d.ts.map +0 -1
  137. package/dist/x402.js.map +0 -1
package/README.md CHANGED
@@ -123,11 +123,16 @@ What it gets right:
123
123
  - **Batches are capped** (default 20) with a teaching error, because the rate limiter charges one token per HTTP request before the body is parsed. Under 2025-06-18 they are refused outright, because the revision removed them.
124
124
  - **The protocol version is negotiated, not asserted.** `initialize` echoes what the client asked for when it is one of `2025-06-18`, `2025-03-26` or `2024-11-05`, and offers the newest otherwise; every response carries the negotiated version back in `MCP-Protocol-Version`. Answering a constant is legal and still costs the caller structured output, tool titles and `_meta` without ever saying so.
125
125
  - **`Access-Control-Expose-Headers` is set.** Allow-Headers governs what a browser may send, Expose-Headers what it may read — without the second a browser client cannot see `Retry-After` on a 429, and a rate limit presents as a hang.
126
+ - **The rate limit is declared, not wired.** Put `rateLimit: {capacity, refillPerSec}` on the config and `mcpResponse` enforces it before parsing the body, refuses with a JSON-RPC envelope, and states `RateLimit-Limit`/`-Remaining`/`-Reset` on every answer. Four routes had each transcribed that by hand through three differently-named local helpers, which is how a headroom header gets added to one surface and forgotten on the next.
126
127
  - **`mcpRateLimited(retryAfterSec)` is a JSON-RPC envelope.** A 429 whose body is `{"error": "..."}` is a string where the client's parser expects `{code, message}`, on the one response an agent meets exactly when it is working hard.
127
128
 
128
129
  ### Structured output, `_meta`, and the envelope gate
129
130
 
130
- Declare an `outputSchema` and the result carries `structuredContent` beside the text block, so a client reads the answer rather than scraping prose for it. A handler's second argument is its context:
131
+ **Every object a handler returns comes back as `structuredContent` beside the text block**, so a client reads the answer rather than scraping prose for it. This used to be gated on declaring an `outputSchema`, and the result was that across four live servers and thirty-two tools — every one of them answering in JSON — not a single response ever carried it. `outputSchema` remains the stronger contract, because a client validates against it; it is no longer the price of admission. A handler returning a string is left alone.
132
+
133
+ `inputSchema.requireOneOf` names a set of which at least one must be present. `required` cannot express "query or popular", so the one tool needing it checked in its handler and the caller learned at execution time — the single class of argument mistake this module was otherwise catching before dispatch.
134
+
135
+ A handler's second argument is its context:
131
136
 
132
137
  ```ts
133
138
  handler: async (args, ctx) => {
@@ -139,6 +144,20 @@ handler: async (args, ctx) => {
139
144
 
140
145
  `config.gate` is envelope-level middleware: it may withhold entries before dispatch and merge its own responses back afterwards. A payment gate has to sit there rather than in a handler, because the demand *replaces* the call and the receipt rides on the envelope.
141
146
 
147
+ ## Conformance
148
+
149
+ `wiki-formant/conformance` is the half of an MCP conformance run that is not about any one server's tools: a JSON-RPC client that backs off on a 429, the transport assertions (CORS preflight, `GET`→405, a notification answering 202 with no body, `-32700`, the batch cap, honest `capabilities`, version negotiation), version coherence across every descriptor a surface publishes, A2A card parity, conditional-GET and `robots` checks, and a pass/fail table with an exit code.
150
+
151
+ ```ts
152
+ const t = createTester({ base: 'https://example.com', clientName: 'my-mcp-test' });
153
+ await transportChecks(t, 'my-mcp-test');
154
+ await annotationChecks(t, { writes: ['create_page'] });
155
+ await payloadBudget(t, [{ name: 'search', args: { query: 'x' } }]);
156
+ process.exit(t.summary());
157
+ ```
158
+
159
+ What stays in your repo is fixtures: which tools you expect, what a good answer from each looks like, and which text surfaces you publish. `payloadBudget` weighs every listed call **and** every read-only tool that takes no required arguments, because the one tool nobody thought to list is the one that answers with 3.3 MB; pass a per-call `maxBytes` for a bulk-export tool that is deliberately large. It also asserts that a JSON answer arrived with `structuredContent`.
160
+
142
161
  ## Markdown twins
143
162
 
144
163
  Pass an `etag` and answer `notModified` before rendering: a twin is the single most recrawled URL a page has, so a twin with no validator is a full render on every pass, forever — the same arithmetic that justifies the corpus ETag, applied per page.
@@ -245,7 +264,9 @@ are unit-tested without a DOM.
245
264
 
246
265
  `FacetBar` renders the rows `createTaxonomy` already produces. This is why the taxonomy exports a rows model rather than markup: the rows could always cross the boundary and, until this subpath existed, the markup could not, so all three wikis hand-rendered it and two put `aria-pressed` on an `<a>`. `Breadcrumbs` renders the trail and its `BreadcrumbList` JSON-LD together, because a trail whose structured data is written somewhere else is a trail that will one day disagree with its own markup — which is the case Google penalises. It takes a `base` origin: structured-data URLs must be absolute and a package cannot know the site.
247
266
 
248
- `WikiRail` stays in `wiki-formant/react`, because it calls `useSidebar` and genuinely is a client component. It renders the whole `wiki-rail__*` tree — scroll wrapper, both labelled `<nav>`s, the table of contents between them — where three wikis had drifted on which of those they had.
267
+ `PageNav` is the previous/next pair at the foot of an article — the sequential read the infobox rail's lateral links do not cover. Ordering is the caller's, because it is the one part that is never portable: a wiki's sequence is its section's configured sort, a knowledge base's is a taxonomy walk. Pair it with `adjacentPages` from `wiki-formant/pagination` over a list you already hold — neither wiki needs a query for it, and the two indexed lookups the neighbours used to cost were the reason one of them dropped the control.
268
+
269
+ `RailShell` stays in `wiki-formant/react`, because it calls `useSidebar` and genuinely is a client component. It renders the rail's landmark, its scroll wrapper and the three collapse states — including the `--instant` class that keeps a remembered-closed rail from animating shut on first paint, and the close-on-tap that a mobile rail needs. Compose your own rail inside it and mark the active link with `isRailLinkActive`; all three wikis do, and the pre-composed component that used to sit here had one consumer and had already lost both of those behaviours.
249
270
 
250
271
  All three take the router's link component as a prop:
251
272
 
@@ -400,30 +421,15 @@ they name a gap in the corpus in the reader's own words.
400
421
 
401
422
  ## API
402
423
 
403
- | Export | From |
404
- |---|---|
405
- | `createTaxonomy`, `defaultHref`, `firstLetter`, `toggleFilter` | `wiki-formant/taxonomy` |
406
- | `injectHeadingIds`, `headingsFrom`, `slugifyHeading` | `wiki-formant/headings` |
407
- | `mcpResponse`, `mcpGet`, `mcpOptions`, `handleMcp`, `withMcpCors`, `McpToolError`, `MCP_CORS`, `MCP_PROTOCOL_VERSION` | `wiki-formant/mcp` |
408
- | `htmlToMarkdown`, `inlineToMarkdown`, `tableToMarkdown`, `frontmatter`, `markdownDocument`, `decodeEntities` | `wiki-formant/markdown` |
409
- | `corpusEtag`, `notModified`, `textHeaders`, `markdownHeaders`, `descriptorHeaders`, `descriptorResponse`, `cleanSnippet`, `pageLine` | `wiki-formant/http` |
410
- | `parsePagination`, `paginatedResponse`, `toOffset` | `wiki-formant/pagination` |
411
- | `mapBlockTree`, `mapBlockTreeAsync`, `someBlock`, `renderBlockTree` | `wiki-formant/blocks` |
412
- | `parseVersion`, `formatVersion`, `incrementVersion`, `bump`, `compareVersions` | `wiki-formant/versioning` |
413
- | `plausibleEvent`, `mcpCallProps`, `searchQueryProps`, `plausibleDomain` | `wiki-formant/analytics` |
414
- | `comboboxAria`, `listId`, `optionId` | `wiki-formant/combobox` |
415
- | `computeRevisionDiff`, `diffBlocks`, `extractBlocks`, `classifyChanges`, `changeSummary` | `wiki-formant/revisions` |
416
- | `renderFeed`, `renderItem`, `escXml`, `cdata`, `clampWords`, `absolutise`, `FEED_HEADERS` | `wiki-formant/feed` |
417
- | `ccBy40`, `licenseBlock`, `licenseLines`, `licenseNote` | `wiki-formant/license` |
418
- | `AI_CRAWLERS`, `detectAiBot`, `aiCrawlerTokens`, `aiCrawlerRules` | `wiki-formant/crawlers` |
419
- | `useCollapsibleSidebar`, `SidebarProvider`, `useSidebar`, `TableOfContents`, `useTypeahead`, `useLinkPreview`, `useClickOutside`, `useTableSort`, `useCopy`, `ErrorBoundary`, `WikiRail` | `wiki-formant/react` |
420
- | `FacetBar`, `Breadcrumbs` | `wiki-formant/react-server` |
421
- | `gatePaidCalls` | `wiki-formant/x402` |
422
- | `resolveSidebarOpen`, `sidebarBootScript`, `SIDEBAR_ATTRIBUTE` | `wiki-formant/sidebar` |
423
- | `addCopyButtons`, `activateTabGroups`, `tweetEmbedSrc`, `onTweetResize`, `hydrateTweetEmbeds`, `sizeTweetEmbeds`, `TWITTER_ORIGIN` | `wiki-formant/dom` |
424
- | `Iframe`, `YouTube`, `TwitterEmbed`, `createMapEmbed`, `createCodeBlock`, `createTabs` | `wiki-formant/tiptap` |
425
-
426
- Everything above `wiki-formant/react` is also re-exported from the package root. The React, sidebar, DOM, tiptap, react-server and x402 subpaths are not: they carry `'use client'`, reach for a browser global, or need a peer the root must not assume, and the root has to stay importable from a route handler.
424
+ Every module has a subpath — `wiki-formant/taxonomy`, `wiki-formant/mcp`, and so on.
425
+ The emitted `.d.ts` files are the reference. There is no hand-maintained symbol list
426
+ here, because the one that used to be here drifted from them.
427
+
428
+ The package root re-exports the runtime modules that need no peer dependency and no
429
+ client boundary, so `import … from 'wiki-formant'` stays importable from a route
430
+ handler with nothing else installed. Everything that reaches for React, a browser
431
+ global, tiptap or a wallet — and the tooling modules, which no route imports — is
432
+ subpath-only.
427
433
 
428
434
  ## A bin
429
435
 
@@ -113,7 +113,6 @@ for (const [tok, at] of [...dead].sort((a, b) => b[1].length - a[1].length)) {
113
113
  console.error(` ${tok.padEnd(24)} ${String(at.length).padStart(3)}x ${at.slice(0, 4).join(', ')}${at.length > 4 ? ', …' : ''}`);
114
114
  }
115
115
  // Derived, not hardcoded: this runs in every sibling project and their
116
- // globals.css does not sit at the same path.
117
- const globals = walk(SRC_DIR, '.css').find(f => f.endsWith('globals.css')) ?? 'your globals.css';
118
- console.error(`\nEither define it in ${globals} or delete the usage.`);
116
+ // globals.css does not sit at the same path. Found once, above.
117
+ console.error(`\nEither define it in ${globalsFile ?? 'your globals.css'} or delete the usage.`);
119
118
  process.exit(WARN_ONLY ? 0 : 1);
@@ -62,4 +62,3 @@ export interface SearchQueryInput {
62
62
  export declare function searchQueryProps({ query, results, surface, }: SearchQueryInput): Record<string, string> | null;
63
63
  /** The hostname of a site URL, for the `domain` a Plausible property is filed under. */
64
64
  export declare function plausibleDomain(siteUrl: string | undefined, fallback: string): string;
65
- //# sourceMappingURL=analytics.d.ts.map
package/dist/analytics.js CHANGED
@@ -88,4 +88,3 @@ export function plausibleDomain(siteUrl, fallback) {
88
88
  return fallback.replace(/^https?:\/\//, '').replace(/\/.*$/, '');
89
89
  }
90
90
  }
91
- //# sourceMappingURL=analytics.js.map
@@ -0,0 +1,97 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { CodeTab, LinkGridGroup, ReferenceItem, StatItem } from './blocks.js';
3
+ import type { WikiLinkComponent } from './react-server.js';
4
+ /**
5
+ * Tabbed code samples. Every tab's body stays mounted and the inactive ones are
6
+ * hidden rather than unmounted: the panels carry pre-highlighted markup, and
7
+ * remounting one would re-run whatever the consumer's highlighter attached to
8
+ * it on every tab press.
9
+ */
10
+ export declare function CodeTabsView({ tabs }: {
11
+ tabs: readonly CodeTab[];
12
+ }): import("react").JSX.Element | null;
13
+ export interface ColumnsViewProps<B extends {
14
+ id: string;
15
+ }> {
16
+ columns: ReadonlyArray<{
17
+ id: string;
18
+ blocks?: readonly B[];
19
+ }>;
20
+ gap?: 'sm' | 'md' | 'lg';
21
+ align?: 'start' | 'center' | 'end' | 'stretch';
22
+ /** The caller's own dispatch, because the block union is the caller's. */
23
+ render: (block: B) => ReactNode;
24
+ }
25
+ /**
26
+ * A row of columns, each holding blocks the caller renders.
27
+ *
28
+ * Children are wrapped in a `Fragment`, not a `<div>`. One repo used a div and
29
+ * it was a layout bug rather than a style: `.column-view` is the flow context
30
+ * its stylesheet spaces children in, and an extra element between them means
31
+ * every gap rule inside a column silently matches nothing.
32
+ */
33
+ export declare function ColumnsView<B extends {
34
+ id: string;
35
+ }>({ columns, gap, align, render, }: ColumnsViewProps<B>): import("react").JSX.Element;
36
+ /**
37
+ * Banded groups of link pills.
38
+ *
39
+ * A link whose href fails `safeLinkHref` is DROPPED, not rendered inert. These
40
+ * hrefs are author-supplied, and a `javascript:` URL fails the http test above
41
+ * — so without the guard it falls through to the router's link component, which
42
+ * renders it looking like an ordinary pill and executes it on click.
43
+ */
44
+ export declare function LinkGridView({ groups, intro, link: Link, }: {
45
+ groups?: readonly (LinkGridGroup & {
46
+ id?: string;
47
+ })[];
48
+ intro?: string;
49
+ link?: WikiLinkComponent;
50
+ }): import("react").JSX.Element;
51
+ /**
52
+ * The numbered citation list, with the `#cite-n` back-links the inline markers
53
+ * point at. `processHtml` is a prop because only one wiki runs reference text
54
+ * through its HTML pipeline, and running it in the other would rewrite anchors
55
+ * that were minted under different rules.
56
+ */
57
+ export declare function ReferencesView({ items, title, processHtml, }: {
58
+ items?: readonly (ReferenceItem & {
59
+ id?: string;
60
+ })[];
61
+ title?: string;
62
+ processHtml?: (html: string) => string;
63
+ }): import("react").JSX.Element | null;
64
+ /**
65
+ * A row of measured figures.
66
+ *
67
+ * The column count is a class (`stat-grid-4`), never an inline
68
+ * `gridTemplateColumns`. One repo did it inline, which put a layout decision
69
+ * outside the stylesheet that owns every other one — invisible to the dead-class
70
+ * check, unreachable from a media query, and so a four-up row that could not
71
+ * become a two-up row on a phone.
72
+ */
73
+ export declare function StatsView({ items, columns, }: {
74
+ items?: readonly (StatItem & {
75
+ id?: string;
76
+ })[];
77
+ columns?: number;
78
+ }): import("react").JSX.Element | null;
79
+ /**
80
+ * A maintenance notice.
81
+ *
82
+ * The label and the fallback message arrive as `meta` because they are this
83
+ * wiki's editorial voice — "You can help RADIX Wiki by expanding it" has a name
84
+ * in it. The markup, the `role="note"` and the variant class are shared, and
85
+ * were identical in both repos. `icon` is optional: one wiki sets one, and a
86
+ * component library that hardcoded an icon set would make its consumers install
87
+ * that icon set.
88
+ */
89
+ export declare function BannerView({ variant, text, meta, icon, }: {
90
+ variant: string;
91
+ text?: string | null;
92
+ meta: {
93
+ label: string;
94
+ message: string;
95
+ };
96
+ icon?: ReactNode;
97
+ }): import("react").JSX.Element;
@@ -0,0 +1,119 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ // block-views.tsx — the block renderers both wikis had written twice.
4
+ //
5
+ // Behind its own subpath for the reason `react.tsx` is: React is an OPTIONAL
6
+ // peer, so a consumer that only wants the taxonomy or the MCP transport still
7
+ // installs a package with no runtime dependencies.
8
+ //
9
+ // WHY THESE FIVE AND NOT THE SWITCH. The block union is per-repo and staying
10
+ // that way (see the Block Model note in the workspace CLAUDE.md) — a closed
11
+ // union is what makes `switch (block.type)` exhaustive, so a new block type is
12
+ // a compile error rather than a silent blank. What is NOT per-repo is what a
13
+ // codeTabs or a linkGrid LOOKS like once you have dispatched to it: those were
14
+ // byte-identical in both repos, down to the class names. So the dispatch stays
15
+ // with the caller and the leaves move here.
16
+ //
17
+ // The class names are NOT props. Like the rail's `wiki-rail__*` tree they are
18
+ // the shared convention both stylesheets already implement, and making them
19
+ // configurable would only let that convention fork again. Everything that
20
+ // genuinely differs — the prose of a banner, whether references run through an
21
+ // HTML processor, the router's link — arrives as a prop.
22
+ import { Fragment, useState } from 'react';
23
+ import { Anchor } from './react-server.js';
24
+ import { safeLinkHref } from './validation.js';
25
+ import { cx } from './html.js';
26
+ // ---- codeTabs ---------------------------------------------------------------
27
+ /**
28
+ * Tabbed code samples. Every tab's body stays mounted and the inactive ones are
29
+ * hidden rather than unmounted: the panels carry pre-highlighted markup, and
30
+ * remounting one would re-run whatever the consumer's highlighter attached to
31
+ * it on every tab press.
32
+ */
33
+ export function CodeTabsView({ tabs }) {
34
+ const [activeTab, setActiveTab] = useState(0);
35
+ if (!tabs.length)
36
+ return null;
37
+ return (_jsxs("div", { className: "code-tabs", children: [_jsx("div", { className: "code-tabs-list", children: tabs.map((tab, i) => (_jsx("button", { type: "button", className: cx('code-tabs-btn', i === activeTab && 'code-tabs-btn-active'), onClick: () => setActiveTab(i), children: tab.label }, i))) }), tabs.map((tab, i) => (_jsx("div", { className: i === activeTab ? 'block' : 'hidden', dangerouslySetInnerHTML: { __html: tab.code } }, i)))] }));
38
+ }
39
+ /**
40
+ * A row of columns, each holding blocks the caller renders.
41
+ *
42
+ * Children are wrapped in a `Fragment`, not a `<div>`. One repo used a div and
43
+ * it was a layout bug rather than a style: `.column-view` is the flow context
44
+ * its stylesheet spaces children in, and an extra element between them means
45
+ * every gap rule inside a column silently matches nothing.
46
+ */
47
+ export function ColumnsView({ columns, gap = 'md', align = 'start', render, }) {
48
+ const gapClass = { sm: 'gap-2', md: 'gap-4', lg: 'gap-6' }[gap];
49
+ const alignClass = {
50
+ start: 'items-start',
51
+ center: 'items-center',
52
+ end: 'items-end',
53
+ stretch: 'items-stretch',
54
+ }[align];
55
+ return (_jsx("div", { className: cx('columns-layout', gapClass, alignClass), children: columns.map(col => (_jsx("div", { className: "column-view", children: (col.blocks ?? []).map(bl => (_jsx(Fragment, { children: render(bl) }, bl.id))) }, col.id))) }));
56
+ }
57
+ // ---- linkGrid ---------------------------------------------------------------
58
+ /**
59
+ * Banded groups of link pills.
60
+ *
61
+ * A link whose href fails `safeLinkHref` is DROPPED, not rendered inert. These
62
+ * hrefs are author-supplied, and a `javascript:` URL fails the http test above
63
+ * — so without the guard it falls through to the router's link component, which
64
+ * renders it looking like an ordinary pill and executes it on click.
65
+ */
66
+ export function LinkGridView({ groups, intro, link: Link = Anchor, }) {
67
+ return (_jsxs("div", { className: "link-grid", children: [intro && _jsx("p", { children: intro }), (groups ?? []).map((group, gi) => (_jsxs("section", { className: "link-grid-group", children: [_jsx("h3", { children: group.heading }), group.description && (_jsx("div", { className: "link-grid-group-description", dangerouslySetInnerHTML: { __html: group.description } })), _jsx("div", { className: "link-grid-pills", children: (group.links ?? []).map((item, i) => {
68
+ const href = safeLinkHref(item.href);
69
+ if (!href)
70
+ return null;
71
+ return /^https?:\/\//.test(href) ? (_jsx("a", { href: href, target: "_blank", rel: "noopener", children: item.label }, i)) : (_jsx(Link, { href: href, children: item.label }, i));
72
+ }) })] }, group.id ?? gi)))] }));
73
+ }
74
+ // ---- references -------------------------------------------------------------
75
+ /**
76
+ * The numbered citation list, with the `#cite-n` back-links the inline markers
77
+ * point at. `processHtml` is a prop because only one wiki runs reference text
78
+ * through its HTML pipeline, and running it in the other would rewrite anchors
79
+ * that were minted under different rules.
80
+ */
81
+ export function ReferencesView({ items, title = 'References', processHtml = (html) => html, }) {
82
+ const rows = items ?? [];
83
+ if (!rows.length)
84
+ return null;
85
+ return (_jsxs("section", { className: "references-block", "aria-labelledby": "references-heading", children: [_jsx("h2", { id: "references-heading", children: title }), _jsx("ol", { className: "references-list", children: rows.map((item, i) => {
86
+ const url = safeLinkHref(item.url);
87
+ return (_jsxs("li", { id: `ref-${i + 1}`, className: "reference-item", children: [_jsx("a", { href: `#cite-${i + 1}`, className: "ref-backlink", "aria-label": "Back to citation", children: "\u2191" }), ' ', _jsx("span", { dangerouslySetInnerHTML: { __html: processHtml(item.text) } }), url && (_jsxs(_Fragment, { children: [' ', _jsx("a", { href: url, target: "_blank", rel: "noopener", className: "reference-link", "aria-label": "Open source", children: "\u2197" })] }))] }, item.id ?? i));
88
+ }) })] }));
89
+ }
90
+ // ---- stats ------------------------------------------------------------------
91
+ /**
92
+ * A row of measured figures.
93
+ *
94
+ * The column count is a class (`stat-grid-4`), never an inline
95
+ * `gridTemplateColumns`. One repo did it inline, which put a layout decision
96
+ * outside the stylesheet that owns every other one — invisible to the dead-class
97
+ * check, unreachable from a media query, and so a four-up row that could not
98
+ * become a two-up row on a phone.
99
+ */
100
+ export function StatsView({ items, columns = 4, }) {
101
+ const rows = items ?? [];
102
+ if (!rows.length)
103
+ return null;
104
+ return (_jsx("div", { className: cx('stat-grid', `stat-grid-${columns}`), children: rows.map((item, i) => (_jsxs("div", { className: "stat-card", children: [_jsxs("span", { className: "stat-value", children: [item.value, item.suffix && _jsx("span", { className: "stat-suffix", children: item.suffix })] }), _jsx("span", { className: "stat-label", children: item.label })] }, item.id ?? i))) }));
105
+ }
106
+ // ---- banner -----------------------------------------------------------------
107
+ /**
108
+ * A maintenance notice.
109
+ *
110
+ * The label and the fallback message arrive as `meta` because they are this
111
+ * wiki's editorial voice — "You can help RADIX Wiki by expanding it" has a name
112
+ * in it. The markup, the `role="note"` and the variant class are shared, and
113
+ * were identical in both repos. `icon` is optional: one wiki sets one, and a
114
+ * component library that hardcoded an icon set would make its consumers install
115
+ * that icon set.
116
+ */
117
+ export function BannerView({ variant, text, meta, icon, }) {
118
+ return (_jsxs("div", { className: cx('editorial-banner', `editorial-banner-${variant}`), role: "note", children: [icon, _jsxs("p", { className: "editorial-banner-body", children: [_jsxs("strong", { children: [meta.label, "."] }), " ", text?.trim() || meta.message] })] }));
119
+ }
package/dist/blocks.d.ts CHANGED
@@ -94,4 +94,3 @@ export declare function statsToMarkdown(items: readonly StatItem[]): string;
94
94
  export declare function linkGridToMarkdown(groups: readonly LinkGridGroup[], intro?: string | null): string;
95
95
  /** A flat bullet list of links — resolved page lists, feed items, link rails. */
96
96
  export declare function linkList(items: readonly LinkGridLink[]): string;
97
- //# sourceMappingURL=blocks.d.ts.map
package/dist/blocks.js CHANGED
@@ -10,7 +10,7 @@
10
10
  // new block type is a compile error until it is handled) and the bodies come
11
11
  // from here. Each renderer takes the block's DATA rather than the block, which
12
12
  // keeps this file free of any one repo's type union.
13
- import { decodeEntities } from './entities.js';
13
+ import { decodeEntities } from './markdown.js';
14
14
  import { htmlToMarkdown, inlineToMarkdown } from './markdown.js';
15
15
  /**
16
16
  * The whole tree as markdown. Containers flatten in document order, because a
@@ -121,4 +121,3 @@ export function linkGridToMarkdown(groups, intro) {
121
121
  export function linkList(items) {
122
122
  return items.map(l => `- [${l.label}](${l.href})`).join('\n');
123
123
  }
124
- //# sourceMappingURL=blocks.js.map
@@ -57,4 +57,3 @@ export declare function listId(baseId: string, listKey?: string): string;
57
57
  * have an active option.
58
58
  */
59
59
  export declare function comboboxAria({ baseId, listKey, count, highlight, open }: ComboboxInput): ComboboxAria;
60
- //# sourceMappingURL=combobox.d.ts.map
package/dist/combobox.js CHANGED
@@ -56,4 +56,3 @@ export function comboboxAria({ baseId, listKey, count, highlight, open }) {
56
56
  }),
57
57
  };
58
58
  }
59
- //# sourceMappingURL=combobox.js.map
@@ -7,12 +7,21 @@ export interface Rpc {
7
7
  isError?: boolean;
8
8
  tools?: Array<{
9
9
  name: string;
10
+ title?: string;
10
11
  description?: string;
12
+ inputSchema?: {
13
+ required?: string[];
14
+ requireOneOf?: string[];
15
+ };
11
16
  annotations?: Record<string, unknown>;
12
17
  }>;
13
18
  resources?: Array<{
14
19
  uri: string;
15
20
  }>;
21
+ prompts?: Array<{
22
+ name: string;
23
+ description?: string;
24
+ }>;
16
25
  resourceTemplates?: Array<{
17
26
  uriTemplate?: string;
18
27
  }>;
@@ -82,8 +91,6 @@ export declare function transportChecks(t: Tester, clientName: string,
82
91
  * business, so it is a parameter rather than one surface's set hardcoded here.
83
92
  */
84
93
  expectedCapabilities?: readonly string[]): Promise<Rpc>;
85
- /** The newest protocol revision `wiki-formant/mcp` speaks. */
86
- export declare const CURRENT_PROTOCOL = "2025-06-18";
87
94
  /**
88
95
  * One service, many descriptors — server.json, the two agent-card paths, the
89
96
  * OpenAPI document, the MCP server card, and `initialize` — should never
@@ -142,6 +149,7 @@ export declare function annotationChecks(t: Tester, opts?: {
142
149
  export declare function payloadBudget(t: Tester, calls: ReadonlyArray<{
143
150
  name: string;
144
151
  args?: Record<string, unknown>;
152
+ maxBytes?: number;
145
153
  }>, maxBytes?: number): Promise<void>;
146
154
  /**
147
155
  * The default `User-agent: *` group may reach everything the descriptors
@@ -151,4 +159,3 @@ export declare function payloadBudget(t: Tester, calls: ReadonlyArray<{
151
159
  * every unnamed crawler is telling two different stories to the same caller.
152
160
  */
153
161
  export declare function robotsChecks(t: Tester, paths: readonly string[]): Promise<void>;
154
- //# sourceMappingURL=conformance.d.ts.map
@@ -11,6 +11,31 @@
11
11
  // same package — so the suite that checks it belongs next to it. What each app
12
12
  // keeps is its own fixtures: which tools it expects, what a good answer from each
13
13
  // looks like, and which text surfaces it publishes.
14
+ //
15
+ // Every value the transport assertions compare against is imported from `mcp.ts`,
16
+ // not restated here. A suite that carries its own copy of the contract stops
17
+ // testing the boundary the moment the contract moves and says nothing about it.
18
+ import { DEFAULT_MAX_BATCH, MCP_CORS, MCP_PROTOCOL_VERSION } from './mcp.js';
19
+ /**
20
+ * Does the live preflight carry every token `MCP_CORS` declares for this header?
21
+ *
22
+ * Derived rather than listed: the assertion used to name three headers by hand,
23
+ * so a fourth added to `MCP_CORS` was tested by nobody.
24
+ */
25
+ const corsCovers = (res, header) => {
26
+ const live = res.headers.get(header) ?? '';
27
+ return (MCP_CORS[header] ?? '').split(',').every(tok => live.includes(tok.trim()));
28
+ };
29
+ /**
30
+ * A POST that `Tester.rpc` cannot make: one whose raw status and headers are the
31
+ * thing under test, and which must not take `rpc`'s 429 retry. A string body is
32
+ * sent as-is, so the malformed-JSON case uses the same door as the rest.
33
+ */
34
+ const rawPost = (endpoint, body, headers = {}) => fetch(endpoint, {
35
+ method: 'POST',
36
+ headers: { 'Content-Type': 'application/json', ...headers },
37
+ body: typeof body === 'string' ? body : JSON.stringify(body),
38
+ });
14
39
  export function createTester(opts) {
15
40
  const base = opts.base.replace(/\/$/, '');
16
41
  const endpoint = opts.endpoint ?? `${base}/api/mcp`;
@@ -87,42 +112,42 @@ expectedCapabilities = ['tools']) {
87
112
  // 2025-03-26 — forfeiting structured output, tool titles and `_meta` — while
88
113
  // every suite reported green.
89
114
  const echoed = await t.rpc('initialize', {
90
- protocolVersion: CURRENT_PROTOCOL,
115
+ protocolVersion: MCP_PROTOCOL_VERSION,
91
116
  capabilities: {},
92
117
  clientInfo: { name: clientName, version: '1' },
93
118
  });
94
- t.check('protocol negotiated', echoed.result?.protocolVersion === CURRENT_PROTOCOL, `asked ${CURRENT_PROTOCOL}, got ${echoed.result?.protocolVersion}`);
119
+ t.check('protocol negotiated', echoed.result?.protocolVersion === MCP_PROTOCOL_VERSION, `asked ${MCP_PROTOCOL_VERSION}, got ${echoed.result?.protocolVersion}`);
95
120
  t.check('downgrades gracefully', (init.result?.protocolVersion ?? '').length > 0, `asked 2024-11-05, got ${init.result?.protocolVersion}`);
96
121
  console.log(`\n=== transport ===`);
97
122
  const opt = await fetch(t.endpoint, { method: 'OPTIONS' });
98
123
  t.check('OPTIONS preflight', opt.status === 204 &&
99
124
  opt.headers.get('access-control-allow-origin') === '*' &&
100
- (opt.headers.get('access-control-allow-headers') ?? '').includes('Mcp-Protocol-Version'), `${opt.status} ACAO=${opt.headers.get('access-control-allow-origin')}`);
125
+ corsCovers(opt, 'Access-Control-Allow-Headers'), `${opt.status} ACAO=${opt.headers.get('access-control-allow-origin')}`);
101
126
  // Allow-Headers governs what a browser may send; Expose-Headers what it may
102
127
  // read. Without the second, a browser client cannot see `Retry-After` on a
103
128
  // 429 and a rate limit reads to it as a hang.
104
- t.check('CORS exposes response headers', ['Mcp-Protocol-Version', 'Retry-After'].every(h => (opt.headers.get('access-control-expose-headers') ?? '').includes(h)), `expose=${opt.headers.get('access-control-expose-headers')}`);
129
+ t.check('CORS exposes response headers', corsCovers(opt, 'Access-Control-Expose-Headers'), `expose=${opt.headers.get('access-control-expose-headers')}`);
130
+ // A budget stated only in the 429 can be discovered only by exceeding it —
131
+ // the one moment an agent is least able to act on it. This suite is the
132
+ // proof: with no header to read, its own client blind-sleeps five seconds on
133
+ // a 429 and hopes.
134
+ const headroom = await rawPost(t.endpoint, { jsonrpc: '2.0', id: 'headroom', method: 'ping' });
135
+ t.recordCall();
136
+ const remaining = headroom.headers.get('ratelimit-remaining');
137
+ t.check('rate limit states headroom', !!headroom.headers.get('ratelimit-limit') && remaining !== null, `RateLimit-Limit=${headroom.headers.get('ratelimit-limit')} Remaining=${remaining} Reset=${headroom.headers.get('ratelimit-reset')}`);
105
138
  const get = await fetch(t.endpoint);
106
139
  t.check('GET→405', get.status === 405 && get.headers.get('access-control-allow-origin') === '*', `${get.status} Allow=${get.headers.get('allow')} ACAO=${get.headers.get('access-control-allow-origin')}`);
107
140
  // A notification has no id, so it must be acknowledged with no body at all —
108
141
  // a JSON-RPC response to one is a protocol error.
109
- const notif = await fetch(t.endpoint, {
110
- method: 'POST',
111
- headers: { 'Content-Type': 'application/json' },
112
- body: JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }),
113
- });
142
+ const notif = await rawPost(t.endpoint, { jsonrpc: '2.0', method: 'notifications/initialized' });
114
143
  const notifBody = await notif.text();
115
144
  t.check('notification→202', notif.status === 202 && notifBody === '', `${notif.status} body="${notifBody}"`);
116
- const bad = await fetch(t.endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: 'not json' });
145
+ const bad = await rawPost(t.endpoint, 'not json');
117
146
  const badJson = (await bad.json());
118
147
  t.check('parse error→400', bad.status === 400 && badJson.error?.code === -32700, `${bad.status} code=${badJson.error?.code}`);
119
- const over = await fetch(t.endpoint, {
120
- method: 'POST',
121
- headers: { 'Content-Type': 'application/json' },
122
- body: JSON.stringify(Array.from({ length: 21 }, (_, i) => ({ jsonrpc: '2.0', id: i, method: 'ping' }))),
123
- });
148
+ const over = await rawPost(t.endpoint, Array.from({ length: DEFAULT_MAX_BATCH + 1 }, (_, i) => ({ jsonrpc: '2.0', id: i, method: 'ping' })));
124
149
  const overJson = (await over.json());
125
- t.check('batch cap (21)', overJson.error?.code === -32600, `code=${overJson.error?.code}: ${(overJson.error?.message ?? '').slice(0, 80)}`);
150
+ t.check(`batch cap (${DEFAULT_MAX_BATCH + 1})`, overJson.error?.code === -32600, `code=${overJson.error?.code}: ${(overJson.error?.message ?? '').slice(0, 80)}`);
126
151
  const caps = init.result?.capabilities ?? {};
127
152
  t.check('capabilities honest', expectedCapabilities.every(k => k in caps), `${JSON.stringify(caps)} expected=[${expectedCapabilities.join(', ')}]`);
128
153
  // Declaring `resources` makes a client ask for templates. -32601 to a method
@@ -131,26 +156,20 @@ expectedCapabilities = ['tools']) {
131
156
  const templates = await t.rpc('resources/templates/list');
132
157
  t.check('resources/templates/list', Array.isArray(templates.result?.resourceTemplates), templates.error ? `-${templates.error.code}` : `${templates.result?.resourceTemplates?.length} templates`);
133
158
  }
134
- const versioned = await fetch(t.endpoint, {
135
- method: 'POST',
136
- headers: { 'Content-Type': 'application/json', 'MCP-Protocol-Version': CURRENT_PROTOCOL },
137
- body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'ping' }),
159
+ const versioned = await rawPost(t.endpoint, { jsonrpc: '2.0', id: 1, method: 'ping' }, {
160
+ 'MCP-Protocol-Version': MCP_PROTOCOL_VERSION,
138
161
  });
139
162
  t.recordCall();
140
- t.check('MCP-Protocol-Version echoed', versioned.headers.get('mcp-protocol-version') === CURRENT_PROTOCOL, `sent ${CURRENT_PROTOCOL}, got ${versioned.headers.get('mcp-protocol-version')}`);
163
+ t.check('MCP-Protocol-Version echoed', versioned.headers.get('mcp-protocol-version') === MCP_PROTOCOL_VERSION, `sent ${MCP_PROTOCOL_VERSION}, got ${versioned.headers.get('mcp-protocol-version')}`);
141
164
  // Batching was removed in 2025-06-18. A server that keeps honouring it under
142
165
  // a version that forbids it is telling the client something untrue.
143
- const batched = await fetch(t.endpoint, {
144
- method: 'POST',
145
- headers: { 'Content-Type': 'application/json', 'MCP-Protocol-Version': CURRENT_PROTOCOL },
146
- body: JSON.stringify([{ jsonrpc: '2.0', id: 1, method: 'ping' }]),
166
+ const batched = await rawPost(t.endpoint, [{ jsonrpc: '2.0', id: 1, method: 'ping' }], {
167
+ 'MCP-Protocol-Version': MCP_PROTOCOL_VERSION,
147
168
  });
148
169
  t.recordCall();
149
170
  t.check('batch refused at 2025-06-18', batched.status === 400, `${batched.status}`);
150
171
  return init;
151
172
  }
152
- /** The newest protocol revision `wiki-formant/mcp` speaks. */
153
- export const CURRENT_PROTOCOL = '2025-06-18';
154
173
  /**
155
174
  * One service, many descriptors — server.json, the two agent-card paths, the
156
175
  * OpenAPI document, the MCP server card, and `initialize` — should never
@@ -200,11 +219,7 @@ export async function agentCardParity(t) {
200
219
  // `url` is where a client sends its first call. Pointed at the homepage it
201
220
  // gets HTML back, which is the failure that looks like a broken agent.
202
221
  const endpoint = String(a.url ?? '');
203
- const probe = await fetch(endpoint, {
204
- method: 'POST',
205
- headers: { 'Content-Type': 'application/json' },
206
- body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'ping' }),
207
- });
222
+ const probe = await rawPost(endpoint, { jsonrpc: '2.0', id: 1, method: 'ping' });
208
223
  t.recordCall();
209
224
  t.check('agent card url is callable', (probe.headers.get('content-type') ?? '').includes('json'), `POST ${endpoint} -> ${probe.status} ${probe.headers.get('content-type')}`);
210
225
  }
@@ -290,7 +305,21 @@ export async function annotationChecks(t, opts = {}) {
290
305
  * answer, so this weighs it.
291
306
  */
292
307
  export async function payloadBudget(t, calls, maxBytes = 120_000) {
293
- for (const { name, args } of calls) {
308
+ // Every read-only tool a caller can invoke with no arguments at all, whether
309
+ // or not the suite thought to name it. This check used to weigh only the
310
+ // listed calls, which is how a tool taking no parameters and answering with
311
+ // 3.3 MB — 27× the budget it was exempt from — passed a suite that measured
312
+ // the four tools beside it. A tool with required arguments still has to be
313
+ // listed: the suite is the only thing that knows a valid pair.
314
+ const listed = new Set(calls.map(c => c.name));
315
+ const bare = ((await t.rpc('tools/list')).result?.tools ?? [])
316
+ .filter(x => x.annotations?.readOnlyHint === true && !listed.has(x.name))
317
+ // `requireOneOf` is a required argument too, just one this cannot pick for
318
+ // the caller — such a tool has to be listed with args like any other.
319
+ .filter(x => !(x.inputSchema?.required ?? []).length && !(x.inputSchema?.requireOneOf ?? []).length)
320
+ .map(x => ({ name: x.name, args: undefined, maxBytes: undefined }));
321
+ for (const { name, args, maxBytes: own } of [...calls, ...bare]) {
322
+ const budget = own ?? maxBytes;
294
323
  const answer = await t.call(name, args ?? {});
295
324
  const text = answer.result?.content?.[0]?.text ?? '';
296
325
  // An empty answer is not a small one. The first draft compared only the
@@ -299,9 +328,18 @@ export async function payloadBudget(t, calls, maxBytes = 120_000) {
299
328
  // measured nothing. A budget that green-lights a call that never returned
300
329
  // is the exact failure it exists to catch.
301
330
  const answered = text.length > 0 && !answer.error && !answer.result?.isError;
302
- t.check(`${name} within budget`, answered && text.length <= maxBytes, answered
303
- ? `${text.length.toLocaleString()} chars (max ${maxBytes.toLocaleString()})`
331
+ t.check(`${name} within budget`, answered && text.length <= budget, answered
332
+ ? `${text.length.toLocaleString()} chars (max ${budget.toLocaleString()})`
304
333
  : `no answer to weigh: ${answer.error ? `-${answer.error.code} ${answer.error.message}` : answer.result?.isError ? `isError: ${text.slice(0, 80)}` : 'empty result'}`);
334
+ // A JSON answer that arrives only as prose costs every client a parse it
335
+ // should never have had to write. Measured on the answer already in hand,
336
+ // so it is free — and it stays quiet for a tool that genuinely returns
337
+ // prose, which is the only reason a read tool may skip it.
338
+ if (answered && /^\s*\{/.test(text)) {
339
+ t.check(`${name} structured`, answer.result?.structuredContent !== undefined, answer.result?.structuredContent !== undefined
340
+ ? 'structuredContent beside the text'
341
+ : 'JSON answer with no structuredContent — the client has to parse prose');
342
+ }
305
343
  }
306
344
  }
307
345
  /**
@@ -337,4 +375,3 @@ export async function robotsChecks(t, paths) {
337
375
  t.check(`robots allows ${path}`, !match || match.allow, match ? `${match.allow ? 'Allow' : 'Disallow'}: ${match.path}` : 'no matching rule');
338
376
  }
339
377
  }
340
- //# sourceMappingURL=conformance.js.map
@@ -42,4 +42,3 @@ export declare function aiCrawlerRules(opts: {
42
42
  disallow: string | string[];
43
43
  aiAllow: string | string[];
44
44
  }): RobotsGroup[];
45
- //# sourceMappingURL=crawlers.d.ts.map
package/dist/crawlers.js CHANGED
@@ -84,4 +84,3 @@ export function aiCrawlerRules(opts) {
84
84
  })),
85
85
  ];
86
86
  }
87
- //# sourceMappingURL=crawlers.js.map
package/dist/dom.d.ts CHANGED
@@ -66,4 +66,3 @@ export interface TabGroupClassNames {
66
66
  * silently reset it to the first tab.
67
67
  */
68
68
  export declare function activateTabGroups(root: ParentNode, classNames?: TabGroupClassNames): void;
69
- //# sourceMappingURL=dom.d.ts.map
package/dist/dom.js CHANGED
@@ -164,4 +164,3 @@ export function activateTabGroups(root, classNames = {}) {
164
164
  group.appendChild(tabPanels);
165
165
  }
166
166
  }
167
- //# sourceMappingURL=dom.js.map