blume 1.6.4 → 1.6.5

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.
@@ -789,6 +789,16 @@ export interface AnalyticsScript {
789
789
  }
790
790
  /** Analytics providers. Configure one, several, or none. */
791
791
  export interface AnalyticsConfig {
792
+ /**
793
+ * Cloudflare Web Analytics, for a site Cloudflare doesn't proxy (manual
794
+ * setup). Not needed on a proxied zone with automatic RUM enabled — that
795
+ * injects the beacon at the edge, and configuring it here too would count
796
+ * every pageview twice.
797
+ */
798
+ cloudflare?: {
799
+ /** Site token from the Web Analytics JS snippet (`data-cf-beacon`). */
800
+ token: string;
801
+ };
792
802
  /** PostHog product analytics. */
793
803
  posthog?: {
794
804
  /** API host (for self-hosted / EU). Defaults to PostHog cloud. */
@@ -561,6 +561,9 @@ export declare const blumeConfigSchema: z.ZodObject<{
561
561
  webmcp: z.ZodDefault<z.ZodBoolean>;
562
562
  }, z.core.$strict>>;
563
563
  analytics: z.ZodOptional<z.ZodObject<{
564
+ cloudflare: z.ZodOptional<z.ZodObject<{
565
+ token: z.ZodString;
566
+ }, z.core.$strict>>;
564
567
  posthog: z.ZodOptional<z.ZodObject<{
565
568
  host: z.ZodOptional<z.ZodString>;
566
569
  key: z.ZodString;
@@ -733,10 +736,10 @@ export declare const blumeConfigSchema: z.ZodObject<{
733
736
  }, z.core.$strict>>;
734
737
  deployment: z.ZodPrefault<z.ZodObject<{
735
738
  adapter: z.ZodDefault<z.ZodNullable<z.ZodEnum<{
739
+ cloudflare: "cloudflare";
736
740
  vercel: "vercel";
737
741
  node: "node";
738
742
  netlify: "netlify";
739
- cloudflare: "cloudflare";
740
743
  }>>>;
741
744
  base: z.ZodOptional<z.ZodString>;
742
745
  output: z.ZodDefault<z.ZodEnum<{
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  title: Analytics
3
- description: First-party web analytics — Vercel Web Analytics, PostHog, or any custom script — wired up from blume.config.ts.
3
+ description: First-party web analytics — Vercel Web Analytics, Cloudflare Web Analytics, PostHog, or any custom script — wired up from blume.config.ts.
4
4
  ---
5
5
 
6
- Blume injects analytics for you from a single `analytics` block in `blume.config.ts`. Vercel Web Analytics and PostHog are first-class, and a `scripts` escape hatch covers every other provider — Plausible, Fathom, Google Analytics, Umami, and the rest.
6
+ Blume injects analytics for you from a single `analytics` block in `blume.config.ts`. Vercel Web Analytics, Cloudflare Web Analytics, and PostHog are first-class, and a `scripts` escape hatch covers every other provider — Plausible, Fathom, Google Analytics, Umami, and the rest.
7
7
 
8
8
  Analytics loads in **production builds only**. The scripts are emitted by `blume build`, never by `blume dev`, so local traffic never reaches your dashboards and you don't need a separate "development" project.
9
9
 
@@ -23,6 +23,24 @@ analytics: {
23
23
 
24
24
  No keys are needed — the script reports to the project it's deployed under. This only collects data on Vercel deployments, where the `/_vercel/insights` endpoint exists.
25
25
 
26
+ ## Cloudflare Web Analytics
27
+
28
+ [Cloudflare Web Analytics](https://developers.cloudflare.com/web-analytics/) has two setups, and only one of them needs config.
29
+
30
+ **Proxied zone (automatic setup).** If Cloudflare serves your site — a Worker with a custom domain, Pages, or any zone with the orange cloud on — enable Web Analytics for the zone in the Cloudflare dashboard and stop there. Cloudflare injects the beacon at the edge, so leave `analytics.cloudflare` unset; configuring it as well would count every pageview twice.
31
+
32
+ **Any other host (manual setup).** For a site Cloudflare doesn't proxy, add the site under Web Analytics in the dashboard, copy the token out of the JS snippet it gives you (the `token` inside `data-cf-beacon`), and pass it here. Blume renders the same beacon tag the snippet does.
33
+
34
+ ```ts blume.config.ts lineNumbers
35
+ analytics: {
36
+ cloudflare: {
37
+ token: "0123456789abcdef0123456789abcdef",
38
+ },
39
+ }
40
+ ```
41
+
42
+ The token is safe to ship to the browser — it only identifies the site. The beacon tracks history changes on its own, so client-router navigations count without any extra wiring.
43
+
26
44
  ## PostHog
27
45
 
28
46
  Provide your **project API key** to add [PostHog](https://posthog.com). The host defaults to PostHog Cloud US; set `host` for EU Cloud (`https://eu.i.posthog.com`) or a self-hosted instance.
@@ -72,6 +90,7 @@ analytics: {
72
90
  | Option | Default | Description |
73
91
  | --- | --- | --- |
74
92
  | `vercel` | `false` | Add Vercel Web Analytics (Vercel deployments only). |
93
+ | `cloudflare.token` | — | Cloudflare Web Analytics site token (manual setup). Enables the beacon when set. |
75
94
  | `posthog.key` | — | PostHog project API key. Enables PostHog when set. |
76
95
  | `posthog.host` | `https://us.i.posthog.com` | PostHog ingestion host (EU Cloud or self-hosted). |
77
96
  | `scripts[].src` | — | External script URL. Mutually exclusive with `content`. |
@@ -384,7 +384,7 @@ flowchart LR
384
384
  ```
385
385
  ````
386
386
 
387
- Diagrams render on the client, so this is an MDX-only feature, and the Mermaid library loads only on pages that include one. The rest of this section is a gallery of common types — see the [Mermaid docs](https://mermaid.js.org/intro/) for the full list.
387
+ Diagrams render on the client, so this is an MDX-only feature, and the Mermaid library loads only on pages that include one. Diagrams use Mermaid's dagre layout and classic look by default; opt a single diagram into another layout or look through Mermaid front matter (a `config:` block with `layout: elk` or `look: neo`), and the ELK engine loads only for diagrams that ask for it. The rest of this section is a gallery of common types — see the [Mermaid docs](https://mermaid.js.org/intro/) for the full list.
388
388
 
389
389
  ### Flowchart
390
390
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blume",
3
- "version": "1.6.4",
3
+ "version": "1.6.5",
4
4
  "description": "Documentation that's fast, AI-ready, and zero-config.",
5
5
  "keywords": [
6
6
  "astro",
@@ -74,12 +74,12 @@
74
74
  "@astrojs/vercel": "^11.0.10",
75
75
  "@asyncapi/converter": "^2.0.2",
76
76
  "@clack/prompts": "^1.8.0",
77
- "@iconify-json/lucide": "^1.2.130",
77
+ "@iconify-json/lucide": "^1.2.131",
78
78
  "@iconify/types": "^2.0.0",
79
79
  "@iconify/utils": "^3.1.7",
80
80
  "@modelcontextprotocol/sdk": "^1.30.0",
81
81
  "@orama/orama": "^3.1.18",
82
- "@pierre/diffs": "^1.4.1",
82
+ "@pierre/diffs": "^1.4.2",
83
83
  "@scalar/astro": "^0.4.18",
84
84
  "@scalar/openapi-parser": "^0.29.1",
85
85
  "@scalar/openapi-types": "^0.9.5",
@@ -89,7 +89,7 @@
89
89
  "@tailwindcss/vite": "^4.3.3",
90
90
  "@types/mdast": "^4.0.4",
91
91
  "@vercel/analytics": "^2.0.1",
92
- "ai": "^7.0.94",
92
+ "ai": "^7.0.99",
93
93
  "astro": "^7.3.2",
94
94
  "babel-plugin-react-compiler": "^1.0.0",
95
95
  "chokidar": "^5.0.0",
@@ -114,7 +114,7 @@
114
114
  "mdast-util-gfm": "^3.1.0",
115
115
  "mdast-util-to-string": "^4.0.0",
116
116
  "medium-zoom": "^1.1.0",
117
- "mermaid": "^11.17.2",
117
+ "mermaid": "^12.0.0",
118
118
  "micromark-extension-gfm": "^3.0.0",
119
119
  "nanotar": "^0.3.0",
120
120
  "node-html-parser": "^9.0.4",
@@ -127,8 +127,8 @@
127
127
  "pathe": "^2.0.3",
128
128
  "perfect-debounce": "^2.1.0",
129
129
  "picomatch": "^4.0.7",
130
- "react": "^19.2.8",
131
- "react-dom": "^19.2.8",
130
+ "react": "^19.3.0",
131
+ "react-dom": "^19.3.0",
132
132
  "robots-parser": "^3.0.1",
133
133
  "satteri": "^0.10.5",
134
134
  "semver": "^7.8.5",
@@ -145,10 +145,10 @@
145
145
  "ufo": "^1.6.4",
146
146
  "undici": "^8.10.2",
147
147
  "write-file-atomic": "^8.0.0",
148
- "zod": "^4.5.4"
148
+ "zod": "^4.6.2"
149
149
  },
150
150
  "devDependencies": {
151
- "@ai-sdk/openai-compatible": "^3.0.45",
151
+ "@ai-sdk/openai-compatible": "^3.0.48",
152
152
  "@mixedbread/sdk": "^0.77.0",
153
153
  "@notionhq/client": "^5.26.0",
154
154
  "@openrouter/ai-sdk-provider": "^3.0.0",
@@ -156,14 +156,14 @@
156
156
  "@sanity/client": "^8.6.1",
157
157
  "@types/cross-spawn": "^6.0.6",
158
158
  "@types/html-escaper": "^3.0.4",
159
- "@types/node": "^22.20.1",
159
+ "@types/node": "^22.20.2",
160
160
  "@types/picomatch": "^4.0.3",
161
- "@types/react": "^19.2.18",
162
- "@types/react-dom": "^19.2.7",
161
+ "@types/react": "^19.3.0",
162
+ "@types/react-dom": "^19.3.0",
163
163
  "@types/semver": "^7.8.0",
164
164
  "@types/write-file-atomic": "^4.0.3",
165
165
  "@typescript/native-preview": "^7.0.0-dev.20260707.2",
166
- "algoliasearch": "^5.57.0",
166
+ "algoliasearch": "^5.59.0",
167
167
  "bun-types": "^1.4.2",
168
168
  "flexsearch": "^0.8.212",
169
169
  "typesense": "^3.0.6"
@@ -601,19 +601,18 @@ const islandFrameworkWarnings = (
601
601
  * rather than let the build die with an opaque ERR_MODULE_NOT_FOUND from the
602
602
  * hidden generated config. Availability mirrors the search-provider check: a
603
603
  * dep resolves from the project root or from the Blume package itself.
604
+ * `pkgDir` is injectable for testing.
604
605
  */
605
- const deploymentAdapterWarnings = (
606
+ export const deploymentAdapterWarnings = (
606
607
  deployment: ResolvedConfig["deployment"],
607
- root: string
608
+ root: string,
609
+ pkgDir: string = packageRoot()
608
610
  ): string[] => {
609
611
  const dep =
610
612
  deployment.output === "server" && deployment.adapter
611
613
  ? DEPLOYMENT_ADAPTER_DEPS.get(deployment.adapter)
612
614
  : undefined;
613
- if (
614
- dep &&
615
- !(canResolveFrom(root, dep) || canResolveFrom(packageRoot(), dep))
616
- ) {
615
+ if (dep && !(canResolveFrom(root, dep) || canResolveFrom(pkgDir, dep))) {
617
616
  return [
618
617
  `Deployment adapter "${deployment.adapter}" needs "${dep}", which isn't installed. Run \`npm install ${dep}\` (or your package manager's equivalent).`,
619
618
  ];
@@ -42,6 +42,14 @@ class BlumeMermaid extends HTMLElement {
42
42
  const token = this.#renderToken;
43
43
  const mermaid = await loadMermaid();
44
44
  mermaid.initialize({
45
+ // Mermaid 12 defaults to the bundled ELK layout and the "neo" look,
46
+ // which re-lays out and restyles every existing diagram and pulls a
47
+ // ~1.4 MB ELK chunk onto any page with a flowchart. Pin the previous
48
+ // defaults so diagrams keep rendering as authored; a diagram opts
49
+ // into ELK or neo through its own front matter (`config: { layout:
50
+ // elk }`), which outranks these initialize() values.
51
+ layout: "dagre",
52
+ look: "classic",
45
53
  securityLevel: "strict",
46
54
  startOnLoad: false,
47
55
  theme: prefersDark() ? "dark" : "default",
@@ -3,7 +3,7 @@ import VercelAnalytics from "@vercel/analytics/astro";
3
3
 
4
4
  // Analytics scripts injected into <head>. PostHog and custom providers use the
5
5
  // inline-snippet house style (banner/theme/JSON-LD); Vercel uses its official
6
- // Astro component. Loads ONLY in production builds so `blume dev` stays clean
6
+ // Astro component; Cloudflare is its beacon script tag. Loads ONLY in production builds so `blume dev` stays clean
7
7
  // and local traffic never reaches your analytics.
8
8
  interface AnalyticsScript {
9
9
  attributes?: Record<string, string>;
@@ -14,6 +14,7 @@ interface AnalyticsScript {
14
14
 
15
15
  interface Props {
16
16
  analytics?: {
17
+ cloudflare?: { token: string };
17
18
  posthog?: { host?: string; key: string };
18
19
  scripts?: AnalyticsScript[];
19
20
  vercel?: boolean;
@@ -45,6 +46,14 @@ const posthogSnippet = posthog
45
46
  ? `${POSTHOG_LOADER}posthog.init(${JSON.stringify(posthog.key)},{api_host:${JSON.stringify(posthog.host ?? "https://us.i.posthog.com")}});${POSTHOG_SPA_PAGEVIEWS}`
46
47
  : null;
47
48
 
49
+ // Cloudflare Web Analytics (manual setup): the beacon script keyed by the
50
+ // site token, exactly as the dashboard's snippet renders it. It tracks
51
+ // history changes itself, so client-router navigations need no extra hook.
52
+ const cloudflareBeacon =
53
+ enabled && analytics?.cloudflare
54
+ ? JSON.stringify({ token: analytics.cloudflare.token })
55
+ : null;
56
+
48
57
  // Custom scripts: any other provider. Each is either external (`src`) or inline
49
58
  // (`content`). Explicit fields win over spread `attributes`.
50
59
  const customScripts = enabled ? (analytics?.scripts ?? []) : [];
@@ -62,6 +71,16 @@ const inlineScripts = customScripts
62
71
  ---
63
72
 
64
73
  {vercelEnabled && <VercelAnalytics />}
74
+ {
75
+ cloudflareBeacon && (
76
+ <script
77
+ is:inline
78
+ defer
79
+ src="https://static.cloudflareinsights.com/beacon.min.js"
80
+ data-cf-beacon={cloudflareBeacon}
81
+ />
82
+ )
83
+ }
65
84
  {posthogSnippet && <script is:inline set:html={posthogSnippet} />}
66
85
  {externalScripts.map((attrs) => <script is:inline {...attrs} />)}
67
86
  {
@@ -48,6 +48,7 @@ interface Props {
48
48
  key: string;
49
49
  } | null;
50
50
  analytics?: {
51
+ cloudflare?: { token: string };
51
52
  posthog?: { host?: string; key: string };
52
53
  scripts?: {
53
54
  attributes?: Record<string, string>;
@@ -84,6 +84,7 @@ interface Props {
84
84
  key: string;
85
85
  } | null;
86
86
  analytics?: {
87
+ cloudflare?: { token: string };
87
88
  posthog?: { host?: string; key: string };
88
89
  scripts?: {
89
90
  attributes?: Record<string, string>;
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * Send a custom analytics event to every analytics platform configured in
3
3
  * `blume.config.ts`. Mirrors the providers wired by `Analytics.astro`: Vercel
4
- * Web Analytics and PostHog are first-class; any other provider added through
4
+ * Web Analytics and PostHog are first-class (Cloudflare Web Analytics is too,
5
+ * but has no custom-event API to forward to); any other provider added through
5
6
  * `analytics.scripts` is reached via best-effort global detection or the
6
7
  * `blume:track` CustomEvent, which fires unconditionally so a project can bridge
7
8
  * the event to anything. Every call no-ops cleanly when a provider isn't present
@@ -23,6 +23,7 @@ import { asyncSampleLanguages } from "./async-snippets.ts";
23
23
  import { languageSamplePanels } from "./sample-panels.ts";
24
24
  import { buildMessage, defaultMessageValues } from "./message.ts";
25
25
  import { messageModel } from "./message-model.ts";
26
+ import Description from "./Description.astro";
26
27
  import MessageComposer from "./MessageComposer.astro";
27
28
  import Authorization from "./Authorization.astro";
28
29
  import Bindings from "./Bindings.astro";
@@ -156,9 +157,10 @@ const channelBindings = bindingGroups(channel?.bindings);
156
157
  : "Message"}
157
158
  </div>
158
159
  {named.message.description && (
159
- <p class="mb-2 text-muted-foreground text-sm">
160
- {named.message.description}
161
- </p>
160
+ <Description
161
+ class="mb-2 text-muted-foreground text-sm"
162
+ text={named.message.description}
163
+ />
162
164
  )}
163
165
  {named.message.contentType && (
164
166
  <div class="mb-2 text-muted-foreground text-xs">
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import Description from "./Description.astro";
2
3
  import {
3
4
  type OperationSecurity,
4
5
  schemeCarrier,
@@ -38,7 +39,7 @@ const { security } = Astro.props;
38
39
  {alternative.map((resolved) => {
39
40
  const carrier = schemeCarrier(resolved);
40
41
  return (
41
- <div class="border-border border-t py-3 first:border-t-0">
42
+ <div class="border-border border-t py-4 first:border-t-0">
42
43
  <div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
43
44
  <code class="font-mono text-foreground text-sm">
44
45
  {carrier?.name ?? resolved.key}
@@ -54,13 +55,10 @@ const { security } = Astro.props;
54
55
  )}
55
56
  </div>
56
57
  {resolved.scheme?.description && (
57
- <div
58
- class="mt-1 text-muted-foreground text-sm"
59
- set:text={resolved.scheme.description}
60
- />
58
+ <Description class="mt-2 text-muted-foreground text-sm" text={resolved.scheme.description} />
61
59
  )}
62
60
  {resolved.scopes.length > 0 && (
63
- <div class="mt-1 flex flex-wrap items-center gap-1 text-xs">
61
+ <div class="mt-2 flex flex-wrap items-center gap-1.5 text-xs">
64
62
  <span class="text-muted-foreground">Scopes:</span>
65
63
  {resolved.scopes.map((scope) => (
66
64
  <code class="rounded bg-muted px-1 py-0.5 text-foreground">
@@ -54,13 +54,13 @@ const isSchemaish = (value: unknown): value is SchemaLike => {
54
54
  </div>
55
55
  {groups.map((group) => (
56
56
  <div class="not-prose mb-3 rounded-blume border border-border px-4 last:mb-0">
57
- <div class="flex items-baseline gap-2 border-border py-3">
57
+ <div class="flex items-baseline gap-2 border-border py-4">
58
58
  <code class="font-mono font-semibold text-foreground text-sm">
59
59
  {group.protocol}
60
60
  </code>
61
61
  </div>
62
62
  {group.rows.map((row) => (
63
- <div class="border-border border-t py-3">
63
+ <div class="border-border border-t py-4">
64
64
  <div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
65
65
  <code class="font-mono text-foreground text-sm">{row.name}</code>
66
66
  {!isSchemaish(row.value) && (
@@ -0,0 +1,109 @@
1
+ ---
2
+ import { descriptionHtml } from "./description.ts";
3
+
4
+ /**
5
+ * A spec description, rendered as Markdown.
6
+ *
7
+ * One component rather than the same three lines in six places, and that is not only tidiness:
8
+ * the styling below is emitted ONCE per page here, where writing it as utility classes on each
9
+ * element repeated it per description instead. On the largest reference page that is 2,400
10
+ * descriptions — the first attempt at this put 1.5 MB of identical class attributes into a single
11
+ * HTML document and pushed eight pages past Googlebot's 2 MB crawl limit, which `blume audit`
12
+ * caught. A class name and one stylesheet cost the same at one description as at two thousand.
13
+ */
14
+ interface Props {
15
+ /** Extra classes for the wrapper — position and base type come from the caller. */
16
+ class?: string;
17
+ text?: string;
18
+ }
19
+
20
+ const { class: className = "", text } = Astro.props;
21
+ const html = descriptionHtml(text);
22
+ ---
23
+
24
+ {html && <div class:list={["blume-api-description", className]} set:html={html} />}
25
+
26
+ <style is:global>
27
+ /* Plain CSS on a single class, not the typography plugin's `prose`. `prose` sets its own font
28
+ size, colour and rhythm, all of which fight the muted small type these sit in — a property
29
+ description would come out larger and darker than the property name above it. Only what
30
+ Markdown needs is styled; everything else inherits, so a description still reads as
31
+ annotation rather than as body copy. */
32
+ .blume-api-description > :first-child {
33
+ margin-top: 0;
34
+ }
35
+
36
+ .blume-api-description > :last-child {
37
+ margin-bottom: 0;
38
+ }
39
+
40
+ .blume-api-description p,
41
+ .blume-api-description ol,
42
+ .blume-api-description ul,
43
+ .blume-api-description pre {
44
+ margin-block: 0.5rem;
45
+ }
46
+
47
+ .blume-api-description ol {
48
+ list-style: decimal;
49
+ padding-inline-start: 1.25rem;
50
+ }
51
+
52
+ .blume-api-description ul {
53
+ list-style: disc;
54
+ padding-inline-start: 1.25rem;
55
+ }
56
+
57
+ .blume-api-description li {
58
+ margin-block: 0.25rem;
59
+ }
60
+
61
+ /* A heading in a description is a label for the paragraph under it, not a section of the
62
+ page: it keeps the description's own size and only gains the weight and colour of a label.
63
+ Without this it inherits the reset's unstyled heading and is indistinguishable from text. */
64
+ .blume-api-description :is(h1, h2, h3, h4, h5, h6) {
65
+ color: var(--color-foreground);
66
+ font-weight: 600;
67
+ margin-block: 0.5rem;
68
+ }
69
+
70
+ .blume-api-description strong {
71
+ color: var(--color-foreground);
72
+ font-weight: 600;
73
+ }
74
+
75
+ .blume-api-description a {
76
+ text-decoration: underline;
77
+ text-underline-offset: 2px;
78
+ }
79
+
80
+ /* Matched to the chip the reference already draws for an enum value, so an `apiKey` in prose
81
+ looks like the `apiKey` in the chips beside it. */
82
+ .blume-api-description code {
83
+ background: var(--color-muted);
84
+ border-radius: 0.25rem;
85
+ color: var(--color-foreground);
86
+ font-family: var(--font-mono);
87
+ font-size: 0.75rem;
88
+ padding: 0.125rem 0.25rem;
89
+ }
90
+
91
+ .blume-api-description pre {
92
+ background: var(--color-muted);
93
+ border-radius: 0.25rem;
94
+ overflow-x: auto;
95
+ padding: 0.5rem;
96
+ }
97
+
98
+ .blume-api-description pre code {
99
+ background: none;
100
+ padding: 0;
101
+ }
102
+
103
+ /* The scroll frame a table gets from `descriptionHtml` carries the body's own 1.5rem rhythm,
104
+ which is three times what everything else in a description sits at. Only the margin is
105
+ restated; the frame, the cell padding and the scrolling are the renderer's. */
106
+ .blume-api-description .blume-table-scroll {
107
+ margin-block: 0.5rem;
108
+ }
109
+ </style>
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  import type { GraphqlFieldRow } from "./graphql-helpers.ts";
3
3
  import { isOutputField } from "./graphql-helpers.ts";
4
+ import Description from "./Description.astro";
4
5
  import GraphqlChip from "./GraphqlChip.astro";
5
6
 
6
7
  /**
@@ -46,7 +47,7 @@ const isRequired = (row: GraphqlFieldRow): boolean =>
46
47
  const route = routes.get(row.type.name);
47
48
  const args = isOutputField(row) ? row.args : [];
48
49
  return (
49
- <div class="border-border border-t py-3 first:border-t-0">
50
+ <div class="border-border border-t py-4 first:border-t-0">
50
51
  <div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
51
52
  <code class="font-mono text-foreground text-sm">
52
53
  {row.name}
@@ -68,13 +69,10 @@ const isRequired = (row: GraphqlFieldRow): boolean =>
68
69
  )}
69
70
  </div>
70
71
  {row.description && (
71
- <div
72
- class="mt-1 text-muted-foreground text-sm"
73
- set:text={row.description}
74
- />
72
+ <Description class="mt-2 text-muted-foreground text-sm" text={row.description} />
75
73
  )}
76
74
  {"default" in row && row.default !== undefined && (
77
- <div class="mt-1 text-muted-foreground text-xs">
75
+ <div class="mt-2 text-muted-foreground text-xs">
78
76
  Default:{" "}
79
77
  <code class="rounded bg-muted px-1 py-0.5 text-foreground">
80
78
  {row.default}
@@ -82,7 +80,7 @@ const isRequired = (row: GraphqlFieldRow): boolean =>
82
80
  </div>
83
81
  )}
84
82
  {row.deprecationReason && (
85
- <div class="mt-1 text-muted-foreground text-xs">
83
+ <div class="mt-2 text-muted-foreground text-xs">
86
84
  Deprecated: <span set:text={row.deprecationReason} />
87
85
  </div>
88
86
  )}
@@ -23,6 +23,7 @@ import {
23
23
  import { buildRequest, defaultValues } from "./request.ts";
24
24
  import { languageSamplePanels } from "./sample-panels.ts";
25
25
  import { sampleLanguages } from "./snippets.ts";
26
+ import Description from "./Description.astro";
26
27
  import GraphqlChip from "./GraphqlChip.astro";
27
28
  import GraphqlFieldsTable from "./GraphqlFieldsTable.astro";
28
29
  import GraphqlType from "./GraphqlType.astro";
@@ -154,9 +155,9 @@ const returnRoute = field ? routes.get(field.type.name) : undefined;
154
155
  />
155
156
  </div>
156
157
  {document.types[field.type.name]?.description && (
157
- <p
158
- class="mt-1 text-muted-foreground text-sm"
159
- set:text={document.types[field.type.name]?.description}
158
+ <Description
159
+ class="mt-2 text-muted-foreground text-sm"
160
+ text={document.types[field.type.name]?.description}
160
161
  />
161
162
  )}
162
163
  </section>
@@ -7,6 +7,7 @@ import {
7
7
  graphqlRoutes,
8
8
  graphqlUsage,
9
9
  } from "./graphql-helpers.ts";
10
+ import Description from "./Description.astro";
10
11
  import GraphqlChip from "./GraphqlChip.astro";
11
12
  import GraphqlFieldsTable from "./GraphqlFieldsTable.astro";
12
13
  import MethodBadge from "./MethodBadge.astro";
@@ -86,7 +87,7 @@ const SECTION_HEADING = "mb-2 font-semibold text-foreground text-sm";
86
87
  </div>
87
88
  <div class="rounded-blume border border-border px-4">
88
89
  {(type.enumValues ?? []).map((value) => (
89
- <div class="border-border border-t py-3 first:border-t-0">
90
+ <div class="border-border border-t py-4 first:border-t-0">
90
91
  <div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
91
92
  <code class="font-mono text-foreground text-sm">
92
93
  {value.name}
@@ -98,13 +99,10 @@ const SECTION_HEADING = "mb-2 font-semibold text-foreground text-sm";
98
99
  )}
99
100
  </div>
100
101
  {value.description && (
101
- <div
102
- class="mt-1 text-muted-foreground text-sm"
103
- set:text={value.description}
104
- />
102
+ <Description class="mt-2 text-muted-foreground text-sm" text={value.description} />
105
103
  )}
106
104
  {value.deprecationReason && (
107
- <div class="mt-1 text-muted-foreground text-xs">
105
+ <div class="mt-2 text-muted-foreground text-xs">
108
106
  Deprecated: <span set:text={value.deprecationReason} />
109
107
  </div>
110
108
  )}
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import Description from "./Description.astro";
2
3
  import {
3
4
  constraints,
4
5
  resolveSchema,
@@ -54,7 +55,7 @@ const groups = SECTIONS.map((section) => ({
54
55
  const limits = constraints(resolved);
55
56
  const enumValues = Array.isArray(resolved.enum) ? resolved.enum : null;
56
57
  return (
57
- <div class="border-border border-t py-3 first:border-t-0">
58
+ <div class="border-border border-t py-4 first:border-t-0">
58
59
  <div class="flex flex-wrap items-baseline gap-x-2 gap-y-1">
59
60
  <code class="font-mono text-foreground text-sm">{param.name}</code>
60
61
  <span class="text-muted-foreground text-xs">{type}</span>
@@ -70,18 +71,15 @@ const groups = SECTIONS.map((section) => ({
70
71
  )}
71
72
  </div>
72
73
  {param.description && (
73
- <div
74
- class="mt-1 text-muted-foreground text-sm"
75
- set:text={param.description}
76
- />
74
+ <Description class="mt-2 text-muted-foreground text-sm" text={param.description} />
77
75
  )}
78
76
  {limits.length > 0 && (
79
- <div class="mt-1 text-muted-foreground text-xs">
77
+ <div class="mt-2 text-muted-foreground text-xs">
80
78
  {limits.join(" · ")}
81
79
  </div>
82
80
  )}
83
81
  {enumValues && (
84
- <div class="mt-1 flex flex-wrap items-center gap-1 text-xs">
82
+ <div class="mt-2 flex flex-wrap items-center gap-1.5 text-xs">
85
83
  <span class="text-muted-foreground">Allowed:</span>
86
84
  {enumValues.map((value) => (
87
85
  <code class="rounded bg-muted px-1 py-0.5 text-foreground">
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import Description from "./Description.astro";
2
3
  import type { SchemaLike } from "./helpers.ts";
3
4
  import SchemaTable from "./SchemaTable.astro";
4
5
 
@@ -46,10 +47,7 @@ const schema = chosen?.[1]?.schema ?? {};
46
47
  </div>
47
48
  {
48
49
  requestBody.description && (
49
- <div
50
- class="text-muted-foreground text-sm"
51
- set:text={requestBody.description}
52
- />
50
+ <Description class="text-muted-foreground text-sm" text={requestBody.description} />
53
51
  )
54
52
  }
55
53
  <div class="mt-3">
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  import { statusColor } from "../colors.ts";
3
+ import Description from "./Description.astro";
3
4
  import type { SchemaLike } from "./helpers.ts";
4
5
  import SchemaTable from "./SchemaTable.astro";
5
6
 
@@ -52,9 +53,9 @@ const items = Object.entries(responses);
52
53
  {status}
53
54
  </span>
54
55
  {response.description && (
55
- <span
56
- class="text-muted-foreground text-sm"
57
- set:text={response.description}
56
+ <Description
57
+ class="min-w-0 text-muted-foreground text-sm"
58
+ text={response.description}
58
59
  />
59
60
  )}
60
61
  </div>