blume 0.5.3 → 0.6.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 (132) hide show
  1. package/dist/cli/index.js +3349 -7024
  2. package/dist/cli/index.js.map +39 -69
  3. package/dist/types/core/config.d.ts +0 -8
  4. package/dist/types/core/data.d.ts +6 -2
  5. package/dist/types/core/i18n-ui.d.ts +50 -0
  6. package/dist/types/core/schema.d.ts +379 -485
  7. package/dist/types/core/types.d.ts +8 -6
  8. package/docs/advanced/meta.ts +1 -8
  9. package/docs/advanced/skills.mdx +28 -0
  10. package/docs/configuration/ai.mdx +58 -0
  11. package/docs/configuration/index.mdx +13 -17
  12. package/docs/configuration/seo.mdx +59 -1
  13. package/docs/configuration/theming.mdx +15 -18
  14. package/docs/content/components.mdx +2 -53
  15. package/docs/content/i18n.mdx +0 -4
  16. package/docs/content/meta.mdx +3 -17
  17. package/docs/content/navigation.mdx +41 -4
  18. package/docs/content/syntax.mdx +1 -1
  19. package/docs/index.mdx +0 -1
  20. package/docs/reference/cli.mdx +12 -13
  21. package/package.json +6 -6
  22. package/skills/blume/SKILL.md +71 -0
  23. package/skills/blume-update-docs/SKILL.md +52 -0
  24. package/skills/blume-update-docs/references/audit-checklist.md +46 -0
  25. package/src/ai/agent-readability.ts +97 -0
  26. package/src/ai/ask-context.ts +131 -8
  27. package/src/ai/ask-data.ts +4 -1
  28. package/src/astro/generate.ts +19 -12
  29. package/src/astro/integration.ts +0 -21
  30. package/src/astro/templates.ts +33 -21
  31. package/src/cli/commands/build.ts +15 -0
  32. package/src/cli/commands/dev.ts +31 -20
  33. package/src/cli/commands/validate.ts +0 -2
  34. package/src/cli/dev-lock.ts +94 -21
  35. package/src/cli/index.ts +0 -2
  36. package/src/components/BlumePage.astro +0 -6
  37. package/src/components/Icon.astro +1 -12
  38. package/src/components/content/AccordionItem.astro +3 -6
  39. package/src/components/content/Badge.astro +1 -3
  40. package/src/components/content/Callout.astro +3 -9
  41. package/src/components/content/Card.astro +2 -3
  42. package/src/components/content/ColorItem.astro +2 -2
  43. package/src/components/content/Column.astro +1 -1
  44. package/src/components/content/GithubInfo.astro +11 -10
  45. package/src/components/content/Prompt.astro +1 -1
  46. package/src/components/content/Step.astro +3 -4
  47. package/src/components/content/Tab.astro +2 -3
  48. package/src/components/content/TypeTable.astro +13 -8
  49. package/src/components/content/Update.astro +1 -1
  50. package/src/components/islands/AskAI.astro +66 -2
  51. package/src/components/islands/ask-ai.tsx +289 -53
  52. package/src/components/layout/Header.astro +27 -4
  53. package/src/components/layout/Logo.astro +5 -1
  54. package/src/components/layout/NavSelector.astro +1 -1
  55. package/src/components/layout/NavTree.astro +15 -15
  56. package/src/components/layout/PageActions.astro +73 -30
  57. package/src/components/layout/PageLayout.astro +42 -0
  58. package/src/components/layout/ReferenceLayout.astro +1 -0
  59. package/src/components/layout/RootLayout.astro +79 -4
  60. package/src/components/layout/Search.astro +5 -5
  61. package/src/components/layout/nav-utils.ts +9 -4
  62. package/src/components/openapi/ApiOverview.astro +4 -50
  63. package/src/components/openapi/ApiTagOperations.astro +42 -0
  64. package/src/core/builtin-tags.ts +1 -3
  65. package/src/core/config.ts +5 -28
  66. package/src/core/data.ts +6 -2
  67. package/src/core/graph.ts +8 -6
  68. package/src/core/i18n-ui.ts +5 -0
  69. package/src/core/links.ts +5 -19
  70. package/src/core/meta.ts +1 -1
  71. package/src/core/nav-diagnostics.ts +7 -0
  72. package/src/core/navigation.ts +38 -17
  73. package/src/core/project-graph.ts +0 -5
  74. package/src/core/schema.ts +133 -95
  75. package/src/core/sources/filesystem.ts +5 -1
  76. package/src/core/sources/resolve.ts +0 -13
  77. package/src/core/sources/watch.ts +43 -11
  78. package/src/core/types.ts +8 -6
  79. package/src/deploy/robots.ts +37 -4
  80. package/src/openapi/parse.ts +197 -14
  81. package/src/openapi/render-mdx.ts +44 -10
  82. package/src/openapi/scalar.ts +1 -1
  83. package/src/openapi/source.ts +19 -2
  84. package/src/search/documents.ts +9 -2
  85. package/src/theme/entry.ts +45 -17
  86. package/src/theme/icons.ts +18 -109
  87. package/src/theme/palette.ts +25 -51
  88. package/src/theme/twoslash.ts +6 -1
  89. package/dist/types/core/bridge.d.ts +0 -24
  90. package/dist/types/core/package-json.d.ts +0 -12
  91. package/dist/types/migrate/mintlify/assets.d.ts +0 -8
  92. package/dist/types/migrate/mintlify/config.d.ts +0 -16
  93. package/dist/types/migrate/mintlify/i18n.d.ts +0 -7
  94. package/dist/types/migrate/shared.d.ts +0 -153
  95. package/docs/advanced/bridge.mdx +0 -76
  96. package/docs/advanced/migrate.mdx +0 -124
  97. package/src/astro/static-assets.ts +0 -124
  98. package/src/cli/commands/migrate.ts +0 -39
  99. package/src/components/content/ApiField.astro +0 -75
  100. package/src/components/content/ParamField.astro +0 -39
  101. package/src/components/content/RequestField.astro +0 -23
  102. package/src/components/content/ResponseField.astro +0 -23
  103. package/src/components/content/Warning.astro +0 -9
  104. package/src/core/assets.ts +0 -31
  105. package/src/core/bridge.ts +0 -102
  106. package/src/core/sources/mintlify.ts +0 -190
  107. package/src/migrate/fumadocs/config.ts +0 -155
  108. package/src/migrate/fumadocs/content.ts +0 -376
  109. package/src/migrate/fumadocs/frontmatter.ts +0 -18
  110. package/src/migrate/fumadocs/groups.ts +0 -237
  111. package/src/migrate/fumadocs/index.ts +0 -355
  112. package/src/migrate/fumadocs/meta.ts +0 -244
  113. package/src/migrate/migrate.ts +0 -53
  114. package/src/migrate/mintlify/assets.ts +0 -46
  115. package/src/migrate/mintlify/config.ts +0 -954
  116. package/src/migrate/mintlify/content.ts +0 -120
  117. package/src/migrate/mintlify/frontmatter.ts +0 -126
  118. package/src/migrate/mintlify/i18n.ts +0 -51
  119. package/src/migrate/mintlify/icons.ts +0 -128
  120. package/src/migrate/mintlify/index.ts +0 -459
  121. package/src/migrate/mintlify/snippets.ts +0 -315
  122. package/src/migrate/mintlify/transform.ts +0 -82
  123. package/src/migrate/nextra/content.ts +0 -46
  124. package/src/migrate/nextra/frontmatter.ts +0 -40
  125. package/src/migrate/nextra/index.ts +0 -389
  126. package/src/migrate/nextra/meta.ts +0 -266
  127. package/src/migrate/shared.ts +0 -801
  128. package/src/migrate/starlight/config.ts +0 -455
  129. package/src/migrate/starlight/content.ts +0 -75
  130. package/src/migrate/starlight/frontmatter.ts +0 -111
  131. package/src/migrate/starlight/i18n.ts +0 -54
  132. package/src/migrate/starlight/index.ts +0 -131
@@ -18,6 +18,7 @@ interface Props {
18
18
  dark?: string;
19
19
  alt: string;
20
20
  href: string;
21
+ text?: string;
21
22
  } | null;
22
23
  navigation: Navigation;
23
24
  route: string;
@@ -57,6 +58,19 @@ const {
57
58
  const LogoSlot = resolveSlot(layout.Logo, Logo);
58
59
  const SearchSlot = resolveSlot(layout.Search, Search);
59
60
 
61
+ // The mobile nav-toggle opens a drawer of navigation. On docs pages that drawer
62
+ // is the sidebar tree (`hasSidebar`); on chrome-only pages (a landing page via
63
+ // PageLayout) it's a tabs-only drawer the layout renders — so the button is also
64
+ // needed whenever there are tabs to reveal.
65
+ const showNavToggle = hasSidebar || navigation.tabs.length > 0;
66
+ // Where the header's inline tab bar appears. With a sidebar it shares the `md`
67
+ // breakpoint with the docs drawer; without one, the tabs-only drawer is the sole
68
+ // mobile nav below `lg`, so the inline tabs wait until `lg` to avoid duplicating
69
+ // the drawer's links between `md` and `lg`.
70
+ const tabsNavClass = hasSidebar
71
+ ? "hidden gap-1 md:flex"
72
+ : "hidden gap-1 lg:flex";
73
+
60
74
  const iconButton =
61
75
  "inline-flex size-9 cursor-pointer items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted hover:text-foreground";
62
76
 
@@ -68,14 +82,23 @@ const iconButton =
68
82
  // styles fade while the non-animated ones snap, so we disable all transitions
69
83
  // for the swap (next-themes' disableTransitionOnChange trick), force a
70
84
  // synchronous style flush, then restore them on the next tick.
71
- const clickScript = `document.addEventListener("click",(e)=>{const t=e.target.closest("[data-blume-theme-toggle]");if(t){const d=document.documentElement;const next=d.dataset.theme==="dark"?"light":"dark";const s=document.createElement("style");s.appendChild(document.createTextNode("*,*::before,*::after{transition:none!important}"));document.head.appendChild(s);d.dataset.theme=next;localStorage.setItem("blume-theme",next);window.getComputedStyle(d).opacity;setTimeout(()=>document.head.removeChild(s),1);}const n=e.target.closest("[data-blume-nav-toggle]");if(n){document.documentElement.toggleAttribute("data-blume-nav-open");}const b=e.target.closest("[data-blume-banner-dismiss]");if(b){const bar=b.closest("[data-blume-banner]");const k=bar&&bar.getAttribute("data-banner-key");if(k){localStorage.setItem("blume-banner:"+k,"1");}document.documentElement.setAttribute("data-blume-banner-hidden","");}});`;
85
+ //
86
+ // The mobile drawer sits below the sticky header, but the banner above the
87
+ // header has no fixed height (its text wraps), so the drawer's top offset can't
88
+ // be static: on open we measure the header's on-screen bottom edge into
89
+ // `--blume-drawer-top` (read by the drawer's `top`/`height`), lock page scroll
90
+ // so the offset can't drift while open, and re-measure when a banner dismiss or
91
+ // a resize/rotation moves the header. Crossing into the desktop breakpoint
92
+ // (64rem, Tailwind `lg`) closes the drawer so the scroll lock can't outlive it.
93
+ const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-blume-header]");if(h){document.documentElement.style.setProperty("--blume-drawer-top",h.getBoundingClientRect().bottom+"px");}};document.addEventListener("click",(e)=>{const t=e.target.closest("[data-blume-theme-toggle]");if(t){const d=document.documentElement;const next=d.dataset.theme==="dark"?"light":"dark";const s=document.createElement("style");s.appendChild(document.createTextNode("*,*::before,*::after{transition:none!important}"));document.head.appendChild(s);d.dataset.theme=next;localStorage.setItem("blume-theme",next);window.getComputedStyle(d).opacity;setTimeout(()=>document.head.removeChild(s),1);}const n=e.target.closest("[data-blume-nav-toggle]");if(n){const d=document.documentElement;const open=d.toggleAttribute("data-blume-nav-open");if(open){dr();}d.style.overflow=open?"hidden":"";}const b=e.target.closest("[data-blume-banner-dismiss]");if(b){const bar=b.closest("[data-blume-banner]");const k=bar&&bar.getAttribute("data-banner-key");if(k){localStorage.setItem("blume-banner:"+k,"1");}document.documentElement.setAttribute("data-blume-banner-hidden","");requestAnimationFrame(dr);}});addEventListener("resize",()=>{const d=document.documentElement;if(!d.hasAttribute("data-blume-nav-open")){return;}if(matchMedia("(min-width:64rem)").matches){d.removeAttribute("data-blume-nav-open");d.style.overflow="";}else{dr();}});})();`;
72
94
  ---
73
95
 
74
96
  <header
75
97
  class="sticky top-0 z-40 flex h-16 items-center gap-3 bg-background/90 px-4 backdrop-blur md:px-6"
98
+ data-blume-header
76
99
  >
77
100
  {
78
- hasSidebar && (
101
+ showNavToggle && (
79
102
  <button
80
103
  aria-label="Toggle navigation"
81
104
  class={`${iconButton} lg:hidden`}
@@ -98,7 +121,7 @@ const clickScript = `document.addEventListener("click",(e)=>{const t=e.target.cl
98
121
  }
99
122
  {
100
123
  navigation.tabs.length > 0 && (
101
- <nav aria-label="Sections" class="hidden gap-1 md:flex">
124
+ <nav aria-label="Sections" class={tabsNavClass}>
102
125
  {navigation.tabs.map((tab) => (
103
126
  <a
104
127
  aria-current={
@@ -117,7 +140,6 @@ const clickScript = `document.addEventListener("click",(e)=>{const t=e.target.cl
117
140
  )
118
141
  }
119
142
  <div class="flex-1"></div>
120
- <slot name="ask" />
121
143
  {
122
144
  localeSwitch && localeSwitch.length > 1 && (
123
145
  <LanguageSwitcher
@@ -168,6 +190,7 @@ const clickScript = `document.addEventListener("click",(e)=>{const t=e.target.cl
168
190
  <span class="inline-flex dark:hidden"><Icon name="sun" size={18} /></span>
169
191
  <span class="hidden dark:inline-flex"><Icon name="moon" size={18} /></span>
170
192
  </button>
193
+ <slot name="ask" />
171
194
  </div>
172
195
  </header>
173
196
  <script is:inline set:html={clickScript} />
@@ -19,6 +19,10 @@ const logoLight = logo?.light;
19
19
  const logoDark = logo?.dark ?? logo?.light;
20
20
  const logoAlt = logo?.alt ?? "";
21
21
  const brandHref = logo?.href ?? "/";
22
+ // Wordmark beside the mark: the configured `logo.text`, or the site title when
23
+ // it's omitted. An explicit `""` hides it (an image-only logo whose mark already
24
+ // carries the wordmark).
25
+ const brandText = logo?.text ?? site.title;
22
26
  ---
23
27
 
24
28
  <a
@@ -46,5 +50,5 @@ const brandHref = logo?.href ?? "/";
46
50
  </>
47
51
  ))
48
52
  }
49
- {site.title}
53
+ {brandText && <span>{brandText}</span>}
50
54
  </a>
@@ -1,5 +1,5 @@
1
1
  ---
2
- // A top-level navigation selector (Mintlify-style): a dropdown that switches
2
+ // A top-level navigation selector: a dropdown that switches
3
3
  // between partitions of the site — a product, a version, or any grouped set of
4
4
  // destinations (`navigation.selectors` in the config). Zero-JS, built on
5
5
  // <details>/<summary> like the language switcher.
@@ -129,13 +129,13 @@ const initialId =
129
129
  {panel.route ? (
130
130
  <a
131
131
  aria-current={panel.route === currentRoute ? "page" : undefined}
132
- class="min-w-0 flex-1 truncate font-semibold text-foreground text-sm hover:underline"
132
+ class="flex-1 truncate font-semibold text-foreground text-sm hover:underline"
133
133
  href={panel.route}
134
134
  >
135
135
  {panel.label}
136
136
  </a>
137
137
  ) : (
138
- <span class="min-w-0 flex-1 truncate font-semibold text-foreground text-sm">
138
+ <span class="flex-1 truncate font-semibold text-foreground text-sm">
139
139
  {panel.label}
140
140
  </span>
141
141
  )}
@@ -151,7 +151,7 @@ const initialId =
151
151
  ))}
152
152
  </blume-nav>
153
153
  ) : (
154
- <ul class="m-0 list-none p-0">
154
+ <ul class="m-0 list-none space-y-px p-0">
155
155
  {items.map((item, index) => {
156
156
  if (item.kind === "page") {
157
157
  return (
@@ -161,7 +161,7 @@ const initialId =
161
161
  class="block rounded-[0.65rem] px-2.5 py-1.5 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground aria-[current=page]:bg-muted aria-[current=page]:font-medium aria-[current=page]:text-foreground"
162
162
  href={item.route}
163
163
  >
164
- <span class="flex min-w-0 items-center gap-2">
164
+ <span class="flex items-center gap-2">
165
165
  {item.icon && (
166
166
  <Icon
167
167
  class="shrink-0 text-muted-foreground"
@@ -169,7 +169,7 @@ const initialId =
169
169
  size={14}
170
170
  />
171
171
  )}
172
- <span class="min-w-0 flex-1 truncate">{item.label}</span>
172
+ <span class="flex-1 truncate">{item.label}</span>
173
173
  {item.badge && <span class={badgeClassFor(item.badge)}>{item.badge}</span>}
174
174
  {item.deprecated && (
175
175
  <span class={deprecatedClass}>deprecated</span>
@@ -192,7 +192,7 @@ const initialId =
192
192
  return (
193
193
  <li class={spacing}>
194
194
  <button
195
- class="flex w-full min-w-0 items-center gap-2 rounded-[0.65rem] px-2.5 py-1.5 text-left font-medium text-foreground text-sm transition-colors hover:bg-muted"
195
+ class="flex w-full items-center gap-2 rounded-[0.65rem] px-2.5 py-1.5 text-left font-medium text-foreground text-sm transition-colors hover:bg-muted"
196
196
  data-nav-to={id}
197
197
  type="button"
198
198
  >
@@ -203,7 +203,7 @@ const initialId =
203
203
  size={14}
204
204
  />
205
205
  )}
206
- <span class="min-w-0 flex-1 truncate">{item.label}</span>
206
+ <span class="flex-1 truncate">{item.label}</span>
207
207
  {item.badge && <span class={badgeClassFor(item.badge)}>{item.badge}</span>}
208
208
  <Icon
209
209
  class="shrink-0 text-muted-foreground"
@@ -222,13 +222,13 @@ const initialId =
222
222
  return (
223
223
  <li class={spacing}>
224
224
  <details class="group" open={open}>
225
- <summary class="mb-1 flex cursor-pointer list-none items-center gap-1.5 rounded-[0.65rem] px-2.5 py-1.5 font-medium text-foreground text-sm transition-colors hover:bg-muted [&::-webkit-details-marker]:hidden">
225
+ <summary class="flex cursor-pointer list-none items-center gap-1.5 rounded-[0.65rem] px-2.5 py-1.5 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground [&::-webkit-details-marker]:hidden">
226
226
  {item.route ? (
227
227
  <a
228
228
  aria-current={
229
229
  item.route === currentRoute ? "page" : undefined
230
230
  }
231
- class="-my-1 flex min-w-0 flex-1 items-center gap-1.5 rounded py-1 text-foreground transition-colors aria-[current=page]:font-semibold"
231
+ class="-my-1 flex flex-1 items-center gap-1.5 rounded py-1 transition-colors aria-[current=page]:text-foreground aria-[current=page]:font-semibold"
232
232
  href={item.route}
233
233
  >
234
234
  {item.icon && (
@@ -238,7 +238,7 @@ const initialId =
238
238
  size={14}
239
239
  />
240
240
  )}
241
- <span class="min-w-0 flex-1 truncate">{item.label}</span>
241
+ <span class="flex-1 truncate">{item.label}</span>
242
242
  {item.badge && (
243
243
  <span class={badgeClassFor(item.badge)}>{item.badge}</span>
244
244
  )}
@@ -252,7 +252,7 @@ const initialId =
252
252
  size={14}
253
253
  />
254
254
  )}
255
- <span class="min-w-0 flex-1 truncate">{item.label}</span>
255
+ <span class="flex-1 truncate">{item.label}</span>
256
256
  {item.badge && (
257
257
  <span class={badgeClassFor(item.badge)}>{item.badge}</span>
258
258
  )}
@@ -279,11 +279,11 @@ const initialId =
279
279
  // `flat` (default): a non-collapsible header with its items beneath.
280
280
  return (
281
281
  <li class={spacing}>
282
- <p class="mb-2 flex min-w-0 items-center gap-1.5 px-2.5 font-medium text-foreground text-sm">
282
+ <p class="mb-2 flex items-center gap-1.5 px-2.5 font-medium text-foreground text-sm">
283
283
  {item.route ? (
284
284
  <a
285
285
  aria-current={item.route === currentRoute ? "page" : undefined}
286
- class="-ml-1 flex min-w-0 flex-1 items-center gap-1.5 rounded px-1 py-0.5 text-foreground transition-colors hover:bg-muted aria-[current=page]:bg-muted"
286
+ class="-ml-1 flex flex-1 items-center gap-1.5 rounded px-1 py-0.5 text-foreground transition-colors hover:bg-muted aria-[current=page]:bg-muted"
287
287
  href={item.route}
288
288
  >
289
289
  {item.icon && (
@@ -293,7 +293,7 @@ const initialId =
293
293
  size={14}
294
294
  />
295
295
  )}
296
- <span class="min-w-0 truncate">{item.label}</span>
296
+ <span class="truncate">{item.label}</span>
297
297
  {item.badge && (
298
298
  <span class={`ml-auto ${badgeClassFor(item.badge)}`}>{item.badge}</span>
299
299
  )}
@@ -307,7 +307,7 @@ const initialId =
307
307
  size={14}
308
308
  />
309
309
  )}
310
- <span class="min-w-0 truncate">{item.label}</span>
310
+ <span class="truncate">{item.label}</span>
311
311
  {item.badge && (
312
312
  <span class={`ml-auto ${badgeClassFor(item.badge)}`}>{item.badge}</span>
313
313
  )}
@@ -7,7 +7,6 @@ import Icon from "../Icon.astro";
7
7
  interface Props {
8
8
  route: string;
9
9
  editUrl?: string | null;
10
- askEnabled?: boolean;
11
10
  /** Show the "Export to PDF" action (native browser print). */
12
11
  exportPdf?: boolean;
13
12
  /** Show the "Export to EPUB" action (client-side, current page). */
@@ -20,17 +19,11 @@ interface Props {
20
19
  strings?: UIStrings["actions"];
21
20
  }
22
21
 
23
- const {
24
- route,
25
- editUrl,
26
- askEnabled,
27
- exportPdf,
28
- exportEpub,
29
- mcpUrl,
30
- mcpName,
31
- strings,
32
- } = Astro.props;
33
- const a = strings ?? EN_UI.actions;
22
+ const { route, editUrl, exportPdf, exportEpub, mcpUrl, mcpName, strings } =
23
+ Astro.props;
24
+ // Merge over the English defaults so a label missing from a translation (or from
25
+ // a not-yet-regenerated snapshot) still renders instead of coming out blank.
26
+ const a = { ...EN_UI.actions, ...strings };
34
27
  const mdPath = route === "/" ? "/index.md" : `${route}.md`;
35
28
 
36
29
  const GITHUB_LOGO = `<svg class="size-4 shrink-0" fill="currentColor" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">${GITHUB_MARK}</svg>`;
@@ -41,6 +34,8 @@ const LOGOS: Record<string, string> = {
41
34
  '<svg class="size-4 shrink-0" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z"/></svg>',
42
35
  claude:
43
36
  '<svg class="size-4 shrink-0" fill="currentColor" viewBox="0 0 12 12" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" fill-rule="evenodd" d="M2.3545 7.9775L4.7145 6.654L4.7545 6.539L4.7145 6.475H4.6L4.205 6.451L2.856 6.4145L1.6865 6.366L0.5535 6.305L0.268 6.2445L0 5.892L0.0275 5.716L0.2675 5.5555L0.6105 5.5855L1.3705 5.637L2.5095 5.716L3.3355 5.7645L4.56 5.892H4.7545L4.782 5.8135L4.715 5.7645L4.6635 5.716L3.4845 4.918L2.2085 4.074L1.5405 3.588L1.1785 3.3425L0.9965 3.1115L0.9175 2.6075L1.2455 2.2465L1.686 2.2765L1.7985 2.307L2.245 2.65L3.199 3.388L4.4445 4.3045L4.627 4.4565L4.6995 4.405L4.709 4.3685L4.627 4.2315L3.9495 3.0085L3.2265 1.7635L2.9045 1.2475L2.8195 0.938C2.78711 0.819128 2.76965 0.696687 2.7675 0.5735L3.1415 0.067L3.348 0L3.846 0.067L4.056 0.249L4.366 0.956L4.867 2.0705L5.6445 3.5855L5.8725 4.0345L5.994 4.4505L6.0395 4.578H6.1185V4.505L6.1825 3.652L6.301 2.6045L6.416 1.257L6.456 0.877L6.644 0.422L7.0175 0.176L7.3095 0.316L7.5495 0.6585L7.516 0.8805L7.373 1.806L7.0935 3.2575L6.9115 4.2285H7.0175L7.139 4.1075L7.6315 3.4545L8.4575 2.4225L8.8225 2.0125L9.2475 1.5605L9.521 1.345H10.0375L10.4175 1.9095L10.2475 2.4925L9.7155 3.166L9.275 3.737L8.643 4.587L8.248 5.267L8.2845 5.322L8.3785 5.312L9.8065 5.009L10.578 4.869L11.4985 4.7115L11.915 4.9055L11.9605 5.103L11.7965 5.5065L10.812 5.7495L9.6575 5.9805L7.938 6.387L7.917 6.402L7.9415 6.4325L8.716 6.5055L9.047 6.5235H9.858L11.368 6.636L11.763 6.897L12 7.216L11.9605 7.4585L11.353 7.7685L10.533 7.574L8.6185 7.119L7.9625 6.9545H7.8715V7.0095L8.418 7.5435L9.421 8.4485L10.6755 9.6135L10.739 9.9025L10.578 10.13L10.408 10.1055L9.3055 9.277L8.88 8.9035L7.917 8.0935H7.853V8.1785L8.075 8.503L9.2475 10.2635L9.3085 10.8035L9.2235 10.98L8.9195 11.0865L8.5855 11.0255L7.8985 10.063L7.191 8.9795L6.6195 8.008L6.5495 8.048L6.2125 11.675L6.0545 11.86L5.69 12L5.3865 11.7695L5.2255 11.396L5.3865 10.658L5.581 9.696L5.7385 8.931L5.8815 7.981L5.9665 7.665L5.9605 7.644L5.8905 7.653L5.1735 8.6365L4.0835 10.109L3.2205 11.0315L3.0135 11.1135L2.655 10.9285L2.6885 10.5975L2.889 10.303L4.083 8.785L4.803 7.844L5.268 7.301L5.265 7.222H5.2375L2.066 9.28L1.501 9.353L1.2575 9.125L1.288 8.752L1.4035 8.6305L2.3575 7.9745L2.3545 7.9775Z"/></svg>',
37
+ codex:
38
+ '<svg class="size-4 shrink-0" fill="currentColor" fill-rule="evenodd" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z"/></svg>',
44
39
  cursor:
45
40
  '<svg class="size-4 shrink-0" viewBox="0 0 466.73 532.09" xmlns="http://www.w3.org/2000/svg"><path d="M457.43,125.94L244.42,2.96c-6.84-3.95-15.28-3.95-22.12,0L9.3,125.94c-5.75,3.32-9.3,9.46-9.3,16.11v247.99c0,6.65,3.55,12.79,9.3,16.11l213.01,122.98c6.84,3.95,15.28,3.95,22.12,0l213.01-122.98c5.75-3.32,9.3-9.46,9.3-16.11v-247.99c0-6.65-3.55-12.79-9.3-16.11h-.01ZM444.05,151.99l-205.63,356.16c-1.39,2.4-5.06,1.42-5.06-1.36v-233.21c0-4.66-2.49-8.97-6.53-11.31L24.87,145.67c-2.4-1.39-1.42-5.06,1.36-5.06h411.26c5.84,0,9.49,6.33,6.57,11.39h-.01Z" fill="currentColor"/></svg>',
46
41
  scira:
@@ -110,7 +105,10 @@ const menuRowClass =
110
105
  size={14}
111
106
  />
112
107
  </summary>
113
- <div class="absolute right-0 left-0 z-50 mt-1 rounded-blume border border-border bg-background p-1 shadow-xl">
108
+ <div
109
+ class="absolute top-full right-2 z-50 mt-1 w-max min-w-[14rem] max-w-[22rem] rounded-blume border border-border bg-background p-1 shadow-xl"
110
+ data-blume-menu
111
+ >
114
112
  {exportPdf && (
115
113
  <button class={menuRowClass} data-blume-export-pdf type="button">
116
114
  <Icon name="file" size={16} />
@@ -130,15 +128,6 @@ const menuRowClass =
130
128
  )
131
129
  }
132
130
 
133
- {
134
- askEnabled && (
135
- <button class={rowClass} data-blume-ask-ai type="button">
136
- <Icon name="message-circle" size={16} />
137
- {a.askAI}
138
- </button>
139
- )
140
- }
141
-
142
131
  <details class="group relative">
143
132
  <summary
144
133
  class={`${rowClass} cursor-pointer list-none [&::-webkit-details-marker]:hidden`}
@@ -151,7 +140,10 @@ const menuRowClass =
151
140
  size={14}
152
141
  />
153
142
  </summary>
154
- <div class="absolute right-0 left-0 z-50 mt-1 rounded-blume border border-border bg-background p-1 shadow-xl">
143
+ <div
144
+ class="absolute top-full right-2 z-50 mt-1 w-max min-w-[14rem] max-w-[22rem] rounded-blume border border-border bg-background p-1 shadow-xl"
145
+ data-blume-menu
146
+ >
155
147
  {
156
148
  PROVIDERS.map((provider, index) => (
157
149
  <>
@@ -186,7 +178,10 @@ const menuRowClass =
186
178
  size={14}
187
179
  />
188
180
  </summary>
189
- <div class="absolute right-0 left-0 z-50 mt-1 rounded-blume border border-border bg-background p-1 shadow-xl">
181
+ <div
182
+ class="absolute top-full right-2 z-50 mt-1 w-max min-w-[14rem] max-w-[22rem] rounded-blume border border-border bg-background p-1 shadow-xl"
183
+ data-blume-menu
184
+ >
190
185
  <button class={menuRowClass} data-mcp-copy-url type="button">
191
186
  <Icon name="link" size={16} />
192
187
  <span class="flex-1" data-mcp-url-label>
@@ -199,6 +194,12 @@ const menuRowClass =
199
194
  {a.copyClaudeCode}
200
195
  </span>
201
196
  </button>
197
+ <button class={menuRowClass} data-mcp-copy-codex type="button">
198
+ <span set:html={LOGOS.codex} />
199
+ <span class="flex-1" data-mcp-codex-label>
200
+ {a.copyCodex}
201
+ </span>
202
+ </button>
202
203
  <hr class="my-1 border-border border-t" />
203
204
  <a class={menuRowClass} data-mcp-cursor rel="noreferrer">
204
205
  <span set:html={LOGOS.cursor} />
@@ -276,6 +277,46 @@ hr { border: 0; border-top: 1px solid #ddd; margin: 2em 0; }`;
276
277
 
277
278
  const root = document.querySelector("[data-blume-page-actions]");
278
279
  if (root) {
280
+ // The action dropdowns are native <details>. Make them behave like a proper
281
+ // menu: only one open at a time, and flip above the trigger when opening
282
+ // downward would run past the viewport bottom.
283
+ const dropdowns = [...root.querySelectorAll<HTMLDetailsElement>("details")];
284
+ const placeMenu = (details: HTMLDetailsElement) => {
285
+ const menu = details.querySelector<HTMLElement>("[data-blume-menu]");
286
+ const summary = details.querySelector("summary");
287
+ if (!(menu && summary)) {
288
+ return;
289
+ }
290
+ const rect = summary.getBoundingClientRect();
291
+ const margin = 8;
292
+ const flipUp =
293
+ rect.bottom + menu.offsetHeight + margin > window.innerHeight &&
294
+ rect.top - menu.offsetHeight - margin > 0;
295
+ menu.classList.toggle("top-full", !flipUp);
296
+ menu.classList.toggle("mt-1", !flipUp);
297
+ menu.classList.toggle("bottom-full", flipUp);
298
+ menu.classList.toggle("mb-1", flipUp);
299
+ };
300
+ for (const details of dropdowns) {
301
+ details.addEventListener("toggle", () => {
302
+ if (!details.open) {
303
+ return;
304
+ }
305
+ for (const other of dropdowns) {
306
+ if (other !== details) {
307
+ other.open = false;
308
+ }
309
+ }
310
+ placeMenu(details);
311
+ });
312
+ }
313
+ window.addEventListener("resize", () => {
314
+ const open = dropdowns.find((details) => details.open);
315
+ if (open) {
316
+ placeMenu(open);
317
+ }
318
+ });
319
+
279
320
  const md = root.getAttribute("data-md") ?? "";
280
321
  const copiedLabel = root.getAttribute("data-i18n-copied") || "Copied!";
281
322
  const absolute = new URL(md, location.origin).href;
@@ -296,12 +337,6 @@ hr { border: 0; border-top: 1px solid #ddd; margin: 2em 0; }`;
296
337
  window.scrollTo({ behavior: "smooth", top: 0 })
297
338
  );
298
339
 
299
- root
300
- .querySelector("[data-blume-ask-ai]")
301
- ?.addEventListener("click", () =>
302
- window.dispatchEvent(new CustomEvent("blume:open-ask-ai"))
303
- );
304
-
305
340
  const mcpUrl = root.getAttribute("data-mcp-url");
306
341
  if (mcpUrl) {
307
342
  const rawName = root.getAttribute("data-mcp-name") || "docs";
@@ -360,6 +395,14 @@ hr { border: 0; border-top: 1px solid #ddd; margin: 2em 0; }`;
360
395
  root.querySelector("[data-mcp-claude-label]")
361
396
  )
362
397
  );
398
+ root
399
+ .querySelector("[data-mcp-copy-codex]")
400
+ ?.addEventListener("click", () =>
401
+ copy(
402
+ `codex mcp add ${id} --url ${mcpUrl}`,
403
+ root.querySelector("[data-mcp-codex-label]")
404
+ )
405
+ );
363
406
  }
364
407
 
365
408
  const label = root.querySelector("[data-blume-copy-label]");
@@ -190,6 +190,48 @@ const bannerScript = banner?.dismissible
190
190
  </Header>
191
191
  <main id="blume-content"><slot /></main>
192
192
  <slot name="footer" />
193
+ {
194
+ // Mobile nav for chrome-only pages: the header's inline tab bar is hidden
195
+ // below `lg` and there's no sidebar drawer, so surface the tabs in a
196
+ // slide-in drawer instead. It reuses the header's `data-blume-nav-toggle`
197
+ // machinery (drawer top, scroll lock, resize-close) — the hamburger button
198
+ // lives in the header; this is the panel it opens.
199
+ navigation.tabs.length > 0 && (
200
+ <>
201
+ <aside
202
+ aria-label="Navigation"
203
+ class="fixed top-[var(--blume-drawer-top,4rem)] start-0 z-[35] h-[calc(100dvh-var(--blume-drawer-top,4rem))] w-64 max-w-[80vw] -translate-x-[105%] overflow-y-auto border-border border-e bg-background px-5 pt-4 pb-6 transition-transform rtl:translate-x-[105%] [:where([data-blume-nav-open])_&]:translate-x-0! lg:hidden"
204
+ >
205
+ <nav aria-label="Sections">
206
+ <ul class="m-0 list-none p-0">
207
+ {navigation.tabs.map((tab) => (
208
+ <li>
209
+ <a
210
+ aria-current={
211
+ route === tab.path ||
212
+ (tab.path !== "/" && route.startsWith(tab.path))
213
+ ? "page"
214
+ : undefined
215
+ }
216
+ class="block rounded-[0.65rem] px-2.5 py-1.5 font-medium text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground aria-[current=page]:bg-muted aria-[current=page]:text-foreground"
217
+ href={tab.path}
218
+ >
219
+ {tab.label}
220
+ </a>
221
+ </li>
222
+ ))}
223
+ </ul>
224
+ </nav>
225
+ </aside>
226
+ <button
227
+ aria-label="Close navigation"
228
+ class="fixed inset-0 z-[30] hidden cursor-pointer border-0 bg-black/40 [:where([data-blume-nav-open])_&]:block lg:hidden"
229
+ data-blume-nav-toggle
230
+ type="button"
231
+ />
232
+ </>
233
+ )
234
+ }
193
235
  {
194
236
  clientDataJson && (
195
237
  <script
@@ -18,6 +18,7 @@ interface Props {
18
18
  dark?: string;
19
19
  alt: string;
20
20
  href: string;
21
+ text?: string;
21
22
  } | null;
22
23
  favicon?: {
23
24
  href: string;
@@ -18,6 +18,7 @@ import Favicon from "./Favicon.astro";
18
18
  import Fonts from "./Fonts.astro";
19
19
  import { bannerInitScript, themeInitScript } from "./head-scripts.ts";
20
20
  import Header from "./Header.astro";
21
+ import Icon from "../Icon.astro";
21
22
  import {
22
23
  findBreadcrumbs,
23
24
  flattenPages,
@@ -39,6 +40,7 @@ interface Props {
39
40
  dark?: string;
40
41
  alt: string;
41
42
  href: string;
43
+ text?: string;
42
44
  } | null;
43
45
  favicon?: {
44
46
  href: string;
@@ -341,11 +343,84 @@ const bannerScript = banner?.dismissible
341
343
  >
342
344
  <slot name="ask" slot="ask" />
343
345
  </HeaderSlot>
344
- <div class:list={["mx-auto grid grid-cols-1 items-start", gridClass]}>
346
+ <div
347
+ class:list={["mx-auto grid grid-cols-1 items-start", gridClass]}
348
+ data-blume-doc-grid
349
+ >
345
350
  <aside
346
351
  aria-label="Primary"
347
- class="fixed top-16 start-0 z-[35] h-[calc(100dvh-4rem)] w-64 max-w-[80vw] -translate-x-[105%] overflow-y-auto border-border border-e bg-background px-5 pt-4 pb-6 transition-transform rtl:translate-x-[105%] [:where([data-blume-nav-open])_&]:translate-x-0! lg:sticky lg:z-auto lg:w-auto lg:max-w-none lg:translate-x-0! lg:border-e-0 lg:bg-transparent lg:px-4"
352
+ class="fixed top-[var(--blume-drawer-top,4rem)] start-0 z-[35] h-[calc(100dvh-var(--blume-drawer-top,4rem))] w-64 max-w-[80vw] -translate-x-[105%] overflow-y-auto border-border border-e bg-background px-5 pt-4 pb-6 transition-transform rtl:translate-x-[105%] [:where([data-blume-nav-open])_&]:translate-x-0! lg:sticky lg:top-16 lg:z-auto lg:h-[calc(100dvh-4rem)] lg:w-auto lg:max-w-none lg:translate-x-0! lg:border-e-0 lg:bg-transparent lg:px-4"
348
353
  >
354
+ {
355
+ // Pinned links (blog, changelog, contact…) sit above every section on
356
+ // all breakpoints, outside the tab-scoped sidebar so they never change
357
+ // with the active tab. External hrefs open in a new tab.
358
+ navigation.featured.length > 0 && (
359
+ <nav aria-label="Featured" class="mb-4 border-border border-b pb-4">
360
+ <ul class="m-0 list-none p-0">
361
+ {navigation.featured.map((link) => {
362
+ const external = /^https?:\/\//u.test(link.href);
363
+ return (
364
+ <li>
365
+ <a
366
+ class="block rounded-[0.65rem] px-2.5 py-1.5 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground"
367
+ href={link.href}
368
+ rel={external ? "noreferrer" : undefined}
369
+ target={external ? "_blank" : undefined}
370
+ >
371
+ <span class="flex items-center gap-2">
372
+ {link.icon && (
373
+ <Icon
374
+ class="shrink-0 text-muted-foreground"
375
+ name={link.icon}
376
+ size={14}
377
+ />
378
+ )}
379
+ <span class="flex-1 truncate">{link.label}</span>
380
+ {external && (
381
+ <Icon
382
+ class="shrink-0 text-muted-foreground opacity-60"
383
+ name="arrow-up-right"
384
+ size={12}
385
+ />
386
+ )}
387
+ </span>
388
+ </a>
389
+ </li>
390
+ );
391
+ })}
392
+ </ul>
393
+ </nav>
394
+ )
395
+ }
396
+ {
397
+ // The header's tab bar is hidden below `md`, so the drawer is the only
398
+ // way to move between sections on mobile: list the tabs above the
399
+ // page tree, separated by a divider. Hidden from `md` up, where the
400
+ // header tabs take over.
401
+ navigation.tabs.length > 0 && (
402
+ <nav aria-label="Sections" class="mb-4 border-border border-b pb-4 md:hidden">
403
+ <ul class="m-0 list-none p-0">
404
+ {navigation.tabs.map((tab) => (
405
+ <li>
406
+ <a
407
+ aria-current={
408
+ page.route === tab.path ||
409
+ (tab.path !== "/" && page.route.startsWith(tab.path))
410
+ ? "page"
411
+ : undefined
412
+ }
413
+ class="block rounded-[0.65rem] px-2.5 py-1.5 font-medium text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground aria-[current=page]:bg-muted aria-[current=page]:text-foreground"
414
+ href={tab.path}
415
+ >
416
+ {tab.label}
417
+ </a>
418
+ </li>
419
+ ))}
420
+ </ul>
421
+ </nav>
422
+ )
423
+ }
349
424
  <nav>
350
425
  {
351
426
  MobileNavSlot ? (
@@ -363,7 +438,7 @@ const bannerScript = banner?.dismissible
363
438
  }
364
439
  </nav>
365
440
  </aside>
366
- <main class="min-w-0 px-6 pt-6 pb-10 lg:px-8 xl:px-10" id="blume-content">
441
+ <main class="px-6 pt-6 pb-10 lg:px-8 xl:px-10" id="blume-content">
367
442
  <BreadcrumbsSlot crumbs={crumbs} wide={isApiOperation} />
368
443
  <TableOfContentsSlot
369
444
  headings={tocHeadings}
@@ -398,6 +473,7 @@ const bannerScript = banner?.dismissible
398
473
  <aside
399
474
  aria-label={strings.toc.title}
400
475
  class="sticky top-16 hidden h-[calc(100dvh-4rem)] overflow-y-auto px-4 pt-6 pb-10 text-sm xl:block"
476
+ data-blume-toc
401
477
  >
402
478
  <TableOfContentsSlot
403
479
  headings={tocHeadings}
@@ -405,7 +481,6 @@ const bannerScript = banner?.dismissible
405
481
  variant="desktop"
406
482
  />
407
483
  <PageActions
408
- askEnabled={askEnabled}
409
484
  editUrl={editUrl}
410
485
  exportEpub={exportEpub}
411
486
  exportPdf={exportPdf}
@@ -36,12 +36,12 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
36
36
  >
37
37
  <button
38
38
  aria-label={s.button}
39
- class="inline-flex h-9 min-w-0 cursor-pointer items-center gap-2 rounded-full border border-border bg-background px-3 text-muted-foreground text-sm transition-colors hover:border-foreground hover:text-foreground sm:min-w-48"
39
+ class="inline-flex h-9 cursor-pointer items-center gap-2 rounded-full border border-border bg-background px-3 text-muted-foreground text-sm transition-colors hover:border-foreground hover:text-foreground sm:min-w-48"
40
40
  data-blume-search-open
41
41
  type="button"
42
42
  >
43
43
  <Icon name="search" size={16} />
44
- <span class="min-w-0 flex-1 text-start max-sm:hidden">{s.button}</span>
44
+ <span class="flex-1 text-start max-sm:hidden">{s.button}</span>
45
45
  <kbd class="font-mono text-[0.7rem] max-sm:hidden" data-blume-search-kbd
46
46
  >⌘K</kbd
47
47
  >
@@ -459,7 +459,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
459
459
  : "Ask AI";
460
460
  el.innerHTML = `
461
461
  <span class="mt-0.5 shrink-0 text-accent">${svg("sparkles")}</span>
462
- <span class="min-w-0 flex-1">
462
+ <span class="flex-1">
463
463
  <span class="block truncate font-normal text-foreground text-sm">${title}</span>
464
464
  <span class="block truncate text-muted-foreground text-sm">Get an instant answer from AI</span>
465
465
  </span>`;
@@ -479,7 +479,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
479
479
  : "";
480
480
  el.innerHTML = `
481
481
  <span class="mt-0.5 shrink-0 text-muted-foreground">${svg("file")}</span>
482
- <span class="min-w-0 flex-1">
482
+ <span class="flex-1">
483
483
  <span class="block truncate font-normal text-foreground text-sm">${hit.title}</span>
484
484
  ${excerpt}
485
485
  </span>`;
@@ -494,7 +494,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
494
494
  el.className = ROW_CLASS;
495
495
  el.innerHTML = `
496
496
  <span class="mt-0.5 shrink-0 text-muted-foreground">${svg("file")}</span>
497
- <span class="min-w-0 flex-1">
497
+ <span class="flex-1">
498
498
  <span class="block truncate font-normal text-foreground text-sm">${escapeHtml(label)}</span>
499
499
  </span>`;
500
500
  const item: Selectable = { el, kind: "link", url };