blume 2.2.2 → 2.2.3

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 (133) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/cli/{chunk-080mfch6.js → chunk-0wt5amrk.js} +15 -15
  3. package/dist/cli/{chunk-k45sq16q.js → chunk-0xyedh7k.js} +39 -23
  4. package/dist/cli/{chunk-k45sq16q.js.map → chunk-0xyedh7k.js.map} +3 -3
  5. package/dist/cli/{chunk-f260jdrn.js → chunk-0ynr0fr7.js} +5 -5
  6. package/dist/cli/{chunk-e1n41w9m.js → chunk-1ay3hzn0.js} +3 -3
  7. package/dist/cli/{chunk-b680sv30.js → chunk-2f6btenb.js} +10 -8
  8. package/dist/cli/chunk-2f6btenb.js.map +13 -0
  9. package/dist/cli/{chunk-gwy55cx3.js → chunk-426rytg6.js} +2 -2
  10. package/dist/cli/{chunk-9h8mh2tk.js → chunk-5f96yq2c.js} +1 -1
  11. package/dist/cli/{chunk-x60e15st.js → chunk-5xq12jz7.js} +17 -17
  12. package/dist/cli/{chunk-x7bh6d9j.js → chunk-6xag1c83.js} +4 -4
  13. package/dist/cli/{chunk-487b95qy.js → chunk-88q28zav.js} +41 -25
  14. package/dist/cli/{chunk-487b95qy.js.map → chunk-88q28zav.js.map} +3 -3
  15. package/dist/cli/{chunk-yg8ckc3g.js → chunk-88zkpg89.js} +2 -2
  16. package/dist/cli/{chunk-ppa6mg4d.js → chunk-8fm3b4vm.js} +6 -6
  17. package/dist/cli/{chunk-rtvgmg2j.js → chunk-8h2t2vyj.js} +2 -2
  18. package/dist/cli/{chunk-2ssh60qh.js → chunk-982qfeny.js} +6 -6
  19. package/dist/cli/{chunk-t2h8pwy1.js → chunk-9emfspcp.js} +4 -4
  20. package/dist/cli/{chunk-9jqmq5hv.js → chunk-akj2p7k1.js} +6 -6
  21. package/dist/cli/{chunk-beb0z983.js → chunk-b664x65a.js} +16 -16
  22. package/dist/cli/{chunk-kmswjxj9.js → chunk-b7n58hgj.js} +3 -3
  23. package/dist/cli/{chunk-fnxgtjeq.js → chunk-c0qddrxr.js} +1 -1
  24. package/dist/cli/{chunk-p834rkcn.js → chunk-g62vnvaw.js} +5 -5
  25. package/dist/cli/{chunk-zxd5jrgx.js → chunk-gc7knd2s.js} +76 -34
  26. package/dist/cli/{chunk-zxd5jrgx.js.map → chunk-gc7knd2s.js.map} +4 -4
  27. package/dist/cli/{chunk-r5nm9w0c.js → chunk-hwjspffr.js} +105 -36
  28. package/dist/cli/{chunk-r5nm9w0c.js.map → chunk-hwjspffr.js.map} +11 -10
  29. package/dist/cli/{chunk-40vk8kgf.js → chunk-p4fe7tax.js} +7 -7
  30. package/dist/cli/{chunk-34wap3ed.js → chunk-psg2za5z.js} +3 -3
  31. package/dist/cli/{chunk-hktq4j5r.js → chunk-pxp1y6yz.js} +16 -10
  32. package/dist/cli/{chunk-hktq4j5r.js.map → chunk-pxp1y6yz.js.map} +4 -4
  33. package/dist/cli/{chunk-qcyxkj44.js → chunk-qax1aekz.js} +43 -17
  34. package/dist/cli/chunk-qax1aekz.js.map +15 -0
  35. package/dist/cli/{chunk-h0qwdbem.js → chunk-r5ajrw1g.js} +2 -2
  36. package/dist/cli/{chunk-q1wwrxzw.js → chunk-rc2qk3qx.js} +3 -3
  37. package/dist/cli/chunk-rc2qk3qx.js.map +10 -0
  38. package/dist/cli/{chunk-69rjf786.js → chunk-ry2ymbxf.js} +5 -5
  39. package/dist/cli/{chunk-gxhcf2hs.js → chunk-t43g7v22.js} +2 -2
  40. package/dist/cli/{chunk-ydqzkhjn.js → chunk-x0jqyq1g.js} +6 -6
  41. package/dist/cli/{chunk-aafhwx74.js → chunk-x25s4gph.js} +1 -1
  42. package/dist/cli/{chunk-nqna8dzp.js → chunk-xkx216rd.js} +17 -17
  43. package/dist/cli/{chunk-etckvbrh.js → chunk-xv8nkt56.js} +14 -14
  44. package/dist/cli/{chunk-hn3g2b89.js → chunk-yrmw5n2b.js} +9 -9
  45. package/dist/cli/{chunk-4zxh25hc.js → chunk-zgca0vxs.js} +2 -2
  46. package/dist/cli/{chunk-c5bjxq8v.js → chunk-zj6bcx0n.js} +2 -2
  47. package/dist/cli/{chunk-1ch3ct3r.js → chunk-zjfwzrvd.js} +14 -14
  48. package/dist/cli/{chunk-j4nfgn2j.js → chunk-znwhf37a.js} +1 -1
  49. package/dist/cli/index.js +18 -18
  50. package/dist/types/ai/ask-context.d.ts +8 -0
  51. package/dist/types/ai/link-text.d.ts +5 -0
  52. package/dist/types/ai/relative-links.d.ts +26 -6
  53. package/dist/types/core/config-input.d.ts +35 -1
  54. package/dist/types/core/data.d.ts +14 -0
  55. package/dist/types/core/last-modified.d.ts +33 -18
  56. package/dist/types/core/links.d.ts +16 -0
  57. package/dist/types/core/schema.d.ts +16 -1
  58. package/dist/types/core/types.d.ts +10 -0
  59. package/dist/types/deploy/sitemap.d.ts +9 -0
  60. package/dist/types/search/documents.d.ts +3 -1
  61. package/dist/types/search/fold.d.ts +33 -0
  62. package/dist/types/search/orama-index.d.ts +4 -4
  63. package/dist/types/seo/jsonld.d.ts +33 -0
  64. package/docs/configuration/search.mdx +2 -2
  65. package/docs/content/frontmatter.mdx +1 -1
  66. package/docs/content/i18n.mdx +1 -1
  67. package/docs/content/sources/remote-mdx.mdx +1 -1
  68. package/docs/discoverability/open-graph.mdx +16 -1
  69. package/docs/discoverability/sitemap-and-robots.mdx +12 -0
  70. package/docs/discoverability/structured-data.mdx +44 -0
  71. package/package.json +1 -1
  72. package/src/ai/ask-context.ts +52 -14
  73. package/src/ai/ask-data.ts +4 -1
  74. package/src/ai/ask-tools.ts +17 -5
  75. package/src/ai/link-text.ts +6 -0
  76. package/src/ai/llms.ts +1 -7
  77. package/src/ai/markdown.ts +8 -4
  78. package/src/ai/relative-links.ts +155 -48
  79. package/src/ai/site-skill.ts +1 -4
  80. package/src/astro/generate.ts +7 -0
  81. package/src/astro/templates.ts +36 -20
  82. package/src/components/layout/PageLayout.astro +25 -12
  83. package/src/components/layout/RootLayout.astro +7 -0
  84. package/src/components/layout/locale-redirect.ts +38 -19
  85. package/src/components/layout/search/types.ts +12 -4
  86. package/src/core/config-input.ts +34 -1
  87. package/src/core/config.ts +5 -2
  88. package/src/core/data.ts +14 -0
  89. package/src/core/last-modified.ts +80 -34
  90. package/src/core/links.ts +43 -2
  91. package/src/core/manifest.ts +2 -0
  92. package/src/core/project-graph.ts +83 -43
  93. package/src/core/schema.ts +47 -9
  94. package/src/core/types.ts +10 -0
  95. package/src/deploy/sitemap.ts +127 -49
  96. package/src/search/documents.ts +34 -11
  97. package/src/search/fold.ts +175 -0
  98. package/src/search/orama-index.ts +40 -49
  99. package/src/seo/jsonld.ts +100 -23
  100. package/dist/cli/chunk-b680sv30.js.map +0 -13
  101. package/dist/cli/chunk-q1wwrxzw.js.map +0 -10
  102. package/dist/cli/chunk-qcyxkj44.js.map +0 -14
  103. /package/dist/cli/{chunk-080mfch6.js.map → chunk-0wt5amrk.js.map} +0 -0
  104. /package/dist/cli/{chunk-f260jdrn.js.map → chunk-0ynr0fr7.js.map} +0 -0
  105. /package/dist/cli/{chunk-e1n41w9m.js.map → chunk-1ay3hzn0.js.map} +0 -0
  106. /package/dist/cli/{chunk-gwy55cx3.js.map → chunk-426rytg6.js.map} +0 -0
  107. /package/dist/cli/{chunk-9h8mh2tk.js.map → chunk-5f96yq2c.js.map} +0 -0
  108. /package/dist/cli/{chunk-x60e15st.js.map → chunk-5xq12jz7.js.map} +0 -0
  109. /package/dist/cli/{chunk-x7bh6d9j.js.map → chunk-6xag1c83.js.map} +0 -0
  110. /package/dist/cli/{chunk-yg8ckc3g.js.map → chunk-88zkpg89.js.map} +0 -0
  111. /package/dist/cli/{chunk-ppa6mg4d.js.map → chunk-8fm3b4vm.js.map} +0 -0
  112. /package/dist/cli/{chunk-rtvgmg2j.js.map → chunk-8h2t2vyj.js.map} +0 -0
  113. /package/dist/cli/{chunk-2ssh60qh.js.map → chunk-982qfeny.js.map} +0 -0
  114. /package/dist/cli/{chunk-t2h8pwy1.js.map → chunk-9emfspcp.js.map} +0 -0
  115. /package/dist/cli/{chunk-9jqmq5hv.js.map → chunk-akj2p7k1.js.map} +0 -0
  116. /package/dist/cli/{chunk-beb0z983.js.map → chunk-b664x65a.js.map} +0 -0
  117. /package/dist/cli/{chunk-kmswjxj9.js.map → chunk-b7n58hgj.js.map} +0 -0
  118. /package/dist/cli/{chunk-fnxgtjeq.js.map → chunk-c0qddrxr.js.map} +0 -0
  119. /package/dist/cli/{chunk-p834rkcn.js.map → chunk-g62vnvaw.js.map} +0 -0
  120. /package/dist/cli/{chunk-40vk8kgf.js.map → chunk-p4fe7tax.js.map} +0 -0
  121. /package/dist/cli/{chunk-34wap3ed.js.map → chunk-psg2za5z.js.map} +0 -0
  122. /package/dist/cli/{chunk-h0qwdbem.js.map → chunk-r5ajrw1g.js.map} +0 -0
  123. /package/dist/cli/{chunk-69rjf786.js.map → chunk-ry2ymbxf.js.map} +0 -0
  124. /package/dist/cli/{chunk-gxhcf2hs.js.map → chunk-t43g7v22.js.map} +0 -0
  125. /package/dist/cli/{chunk-ydqzkhjn.js.map → chunk-x0jqyq1g.js.map} +0 -0
  126. /package/dist/cli/{chunk-aafhwx74.js.map → chunk-x25s4gph.js.map} +0 -0
  127. /package/dist/cli/{chunk-nqna8dzp.js.map → chunk-xkx216rd.js.map} +0 -0
  128. /package/dist/cli/{chunk-etckvbrh.js.map → chunk-xv8nkt56.js.map} +0 -0
  129. /package/dist/cli/{chunk-hn3g2b89.js.map → chunk-yrmw5n2b.js.map} +0 -0
  130. /package/dist/cli/{chunk-4zxh25hc.js.map → chunk-zgca0vxs.js.map} +0 -0
  131. /package/dist/cli/{chunk-c5bjxq8v.js.map → chunk-zj6bcx0n.js.map} +0 -0
  132. /package/dist/cli/{chunk-1ch3ct3r.js.map → chunk-zjfwzrvd.js.map} +0 -0
  133. /package/dist/cli/{chunk-j4nfgn2j.js.map → chunk-znwhf37a.js.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  locateMdxFailure
4
- } from "./chunk-zxd5jrgx.js";
4
+ } from "./chunk-gc7knd2s.js";
5
5
 
6
6
  // src/cli/build-failure.ts
7
7
  import { existsSync, readFileSync } from "node:fs";
@@ -154,4 +154,4 @@ var buildFailureLocator = (project) => (file, failure) => {
154
154
  export { astroBuildDiagnostics, buildFailureLocator };
155
155
 
156
156
  //# debugId=A5E390AFCF69E70164756E2164756E21
157
- //# sourceMappingURL=chunk-4zxh25hc.js.map
157
+ //# sourceMappingURL=chunk-zgca0vxs.js.map
@@ -7,7 +7,7 @@ import {
7
7
  packageSrc,
8
8
  registry,
9
9
  findItem
10
- } from "./chunk-9h8mh2tk.js";
10
+ } from "./chunk-5f96yq2c.js";
11
11
 
12
12
  // src/cli/commands/add.ts
13
13
  import { existsSync } from "node:fs";
@@ -96,4 +96,4 @@ export {
96
96
  };
97
97
 
98
98
  //# debugId=B640E77C94BA0A0364756E2164756E21
99
- //# sourceMappingURL=chunk-c5bjxq8v.js.map
99
+ //# sourceMappingURL=chunk-zj6bcx0n.js.map
@@ -8,28 +8,28 @@ import {
8
8
  import {
9
9
  commandsFor,
10
10
  detectProjectPackageManager
11
- } from "./chunk-r5nm9w0c.js";
11
+ } from "./chunk-hwjspffr.js";
12
12
  import"./chunk-zccbjcj0.js";
13
- import"./chunk-hktq4j5r.js";
14
- import"./chunk-zxd5jrgx.js";
15
- import"./chunk-b680sv30.js";
16
- import"./chunk-k45sq16q.js";
17
- import"./chunk-rtvgmg2j.js";
18
- import"./chunk-j4nfgn2j.js";
13
+ import"./chunk-pxp1y6yz.js";
14
+ import"./chunk-gc7knd2s.js";
15
+ import"./chunk-2f6btenb.js";
16
+ import"./chunk-0xyedh7k.js";
17
+ import"./chunk-8h2t2vyj.js";
18
+ import"./chunk-znwhf37a.js";
19
19
  import"./chunk-jq5n4avg.js";
20
- import"./chunk-487b95qy.js";
21
- import"./chunk-9h8mh2tk.js";
20
+ import"./chunk-88q28zav.js";
21
+ import"./chunk-5f96yq2c.js";
22
22
  import {
23
23
  updatePackageScripts,
24
24
  isEjectedProject
25
- } from "./chunk-e1n41w9m.js";
25
+ } from "./chunk-1ay3hzn0.js";
26
26
  import {
27
27
  eject
28
- } from "./chunk-40vk8kgf.js";
28
+ } from "./chunk-p4fe7tax.js";
29
29
  import {
30
30
  refuseIfDevRunning
31
- } from "./chunk-h0qwdbem.js";
32
- import"./chunk-q1wwrxzw.js";
31
+ } from "./chunk-r5ajrw1g.js";
32
+ import"./chunk-rc2qk3qx.js";
33
33
 
34
34
  // src/cli/commands/eject.ts
35
35
  import { defineCommand } from "citty";
@@ -95,4 +95,4 @@ export {
95
95
  };
96
96
 
97
97
  //# debugId=9A7F310B3D4EBEE364756E2164756E21
98
- //# sourceMappingURL=chunk-1ch3ct3r.js.map
98
+ //# sourceMappingURL=chunk-zjfwzrvd.js.map
@@ -390,4 +390,4 @@ var analyzeComponentOverrides = (source, filePath) => {
390
390
  export { emptyComponentOverrides, ComponentOverridesError, analyzeComponentOverrides };
391
391
 
392
392
  //# debugId=59653B23465A56F364756E2164756E21
393
- //# sourceMappingURL=chunk-j4nfgn2j.js.map
393
+ //# sourceMappingURL=chunk-znwhf37a.js.map
package/dist/cli/index.js CHANGED
@@ -725,24 +725,24 @@ var main = defineCommand({
725
725
  version: getBlumeVersion()
726
726
  },
727
727
  subCommands: {
728
- add: lazyCommand(commandMeta.add, () => import("./chunk-c5bjxq8v.js"), "addCommand"),
729
- audit: lazyCommand(commandMeta.audit, () => import("./chunk-2ssh60qh.js"), "auditCommand"),
730
- build: lazyCommand(commandMeta.build, () => import("./chunk-nqna8dzp.js"), "buildCommand"),
731
- check: lazyCommand(commandMeta.check, () => import("./chunk-beb0z983.js"), "checkCommand"),
732
- dev: lazyCommand(commandMeta.dev, () => import("./chunk-x60e15st.js"), "devCommand"),
733
- doctor: lazyCommand(commandMeta.doctor, () => import("./chunk-hn3g2b89.js"), "doctorCommand"),
734
- eject: lazyCommand(commandMeta.eject, () => import("./chunk-1ch3ct3r.js"), "ejectCommand"),
735
- eval: lazyCommand(commandMeta.eval, () => import("./chunk-9jqmq5hv.js"), "evalCommand"),
736
- init: lazyCommand(commandMeta.init, () => import("./chunk-etckvbrh.js"), "initCommand"),
737
- "mcp-stdio": lazyCommand(commandMeta["mcp-stdio"], () => import("./chunk-qcyxkj44.js"), "mcpStdioCommand"),
738
- migrate: lazyCommand(commandMeta.migrate, () => import("./chunk-yg8ckc3g.js"), "migrateCommand"),
739
- preview: lazyCommand(commandMeta.preview, () => import("./chunk-69rjf786.js"), "previewCommand"),
740
- skill: lazyCommand(commandMeta.skill, () => import("./chunk-p834rkcn.js"), "skillCommand"),
741
- sync: lazyCommand(commandMeta.sync, () => import("./chunk-080mfch6.js"), "syncCommand"),
742
- translate: lazyCommand(commandMeta.translate, () => import("./chunk-f260jdrn.js"), "translateCommand"),
743
- upgrade: lazyCommand(commandMeta.upgrade, () => import("./chunk-ydqzkhjn.js"), "upgradeCommand"),
744
- validate: lazyCommand(commandMeta.validate, () => import("./chunk-ppa6mg4d.js"), "validateCommand"),
745
- version: lazyCommand(commandMeta.version, () => import("./chunk-x7bh6d9j.js"), "versionCommand")
728
+ add: lazyCommand(commandMeta.add, () => import("./chunk-zj6bcx0n.js"), "addCommand"),
729
+ audit: lazyCommand(commandMeta.audit, () => import("./chunk-982qfeny.js"), "auditCommand"),
730
+ build: lazyCommand(commandMeta.build, () => import("./chunk-xkx216rd.js"), "buildCommand"),
731
+ check: lazyCommand(commandMeta.check, () => import("./chunk-b664x65a.js"), "checkCommand"),
732
+ dev: lazyCommand(commandMeta.dev, () => import("./chunk-5xq12jz7.js"), "devCommand"),
733
+ doctor: lazyCommand(commandMeta.doctor, () => import("./chunk-yrmw5n2b.js"), "doctorCommand"),
734
+ eject: lazyCommand(commandMeta.eject, () => import("./chunk-zjfwzrvd.js"), "ejectCommand"),
735
+ eval: lazyCommand(commandMeta.eval, () => import("./chunk-akj2p7k1.js"), "evalCommand"),
736
+ init: lazyCommand(commandMeta.init, () => import("./chunk-xv8nkt56.js"), "initCommand"),
737
+ "mcp-stdio": lazyCommand(commandMeta["mcp-stdio"], () => import("./chunk-qax1aekz.js"), "mcpStdioCommand"),
738
+ migrate: lazyCommand(commandMeta.migrate, () => import("./chunk-88zkpg89.js"), "migrateCommand"),
739
+ preview: lazyCommand(commandMeta.preview, () => import("./chunk-ry2ymbxf.js"), "previewCommand"),
740
+ skill: lazyCommand(commandMeta.skill, () => import("./chunk-g62vnvaw.js"), "skillCommand"),
741
+ sync: lazyCommand(commandMeta.sync, () => import("./chunk-0wt5amrk.js"), "syncCommand"),
742
+ translate: lazyCommand(commandMeta.translate, () => import("./chunk-0ynr0fr7.js"), "translateCommand"),
743
+ upgrade: lazyCommand(commandMeta.upgrade, () => import("./chunk-x0jqyq1g.js"), "upgradeCommand"),
744
+ validate: lazyCommand(commandMeta.validate, () => import("./chunk-8fm3b4vm.js"), "validateCommand"),
745
+ version: lazyCommand(commandMeta.version, () => import("./chunk-6xag1c83.js"), "versionCommand")
746
746
  }
747
747
  });
748
748
  loadEnvFiles(process.cwd());
@@ -52,6 +52,14 @@ export interface AskRetrievalOptions {
52
52
  */
53
53
  maxResults?: number;
54
54
  }
55
+ /**
56
+ * An excerpt's heading: the Markdown link the model cites the page with. A
57
+ * bracket or backslash in the title, or whitespace or a parenthesis in the
58
+ * route, would break the link, and a `%` in the route would read back as an
59
+ * escape (`read_page` decodes them), so those are escaped; the rest of the
60
+ * route stays as the reader sees it (`/ja/ポイント`).
61
+ */
62
+ export declare const excerptHeading: (doc: Pick<OramaDoc, "route" | "title">) => string;
55
63
  /**
56
64
  * Excerpt the region of `content` most relevant to `query`, not just its head.
57
65
  *
@@ -0,0 +1,5 @@
1
+ /**
2
+ * A title as Markdown link text. An unescaped bracket in it — `[Beta] Webhooks`
3
+ * — closes or nests the text early, and the line stops being a link.
4
+ */
5
+ export declare const linkText: (title: string) => string;
@@ -6,7 +6,9 @@ import type { BlumeProject } from "../core/project-graph.ts";
6
6
  * link lands on `/install`; the rendered page rewrites it to the route it
7
7
  * means (`markdown/relative-links.ts`), and the Markdown an agent reads gets
8
8
  * the same rewrite from the same resolver (`resolveRelativeHref`), so both
9
- * point at one page. Root-relative links (`[x](/guide)`,
9
+ * point at one page. A raw HTML `<a href>` ships as written, so a relative
10
+ * one lands where the browser takes it from the page's URL
11
+ * (`resolveRawRelativeHref`). Root-relative links (`[x](/guide)`,
10
12
  * `<Card href="/guide">`, `![logo](/logo.png)`) get what the rendered page
11
13
  * gives them too (`markdown/base-links.ts`,
12
14
  * `components/content/base-href.ts`): a page link the `deployment.base` +
@@ -18,18 +20,36 @@ import type { BlumeProject } from "../core/project-graph.ts";
18
20
  * (`resolveRootFileHref`). Inline links, images, component
19
21
  * `href`s, and reference definitions are rewritten; fenced and inline code,
20
22
  * relative images and asset links, and external URLs are left as written.
23
+ * The assistant's snapshot takes the links that name a page by its path or
24
+ * file alone, as the routes it keys pages by (`assistant`).
21
25
  */
22
26
  /** The page a Markdown text was written for. */
23
27
  export interface LinkedPage {
28
+ /**
29
+ * A staged page's path under its staging dir, which its links resolve from
30
+ * when it has no `sourcePath` (a remote or CMS source).
31
+ */
32
+ entryId?: string;
24
33
  route: string;
25
34
  sourcePath?: string;
26
35
  }
27
36
  /** Rewrite a page's relative page links to the routes they mean. */
28
37
  export type RelativeLinkRewriter = (text: string, page: LinkedPage) => string;
38
+ /** Who reads the rewritten links. */
39
+ export interface RelativeLinkOptions {
40
+ /**
41
+ * The assistant's snapshot, rather than a page an agent fetches by URL.
42
+ * Only a link that names a page by its relative path or its content file
43
+ * changes, to the route the snapshot keys that page by: without the
44
+ * `deployment.base`, since the chat panel mounts every link it renders
45
+ * under it, and a locale's fallback copy as the page it renders, since the
46
+ * snapshot holds that page instead. Root page links and images stay as
47
+ * written.
48
+ */
49
+ assistant?: boolean;
50
+ }
29
51
  /**
30
- * Build the rewriter for a project: the file → route lookup a `.md`/`.mdx`
31
- * link resolves through is built once. A file shared by every locale
32
- * publishes once per locale, and the default locale's route stands for it;
33
- * fallback copies render another locale's file, so they never stand for one.
52
+ * Build the rewriter for a project, for an agent reading a page by URL or,
53
+ * with `assistant`, for the assistant's snapshot.
34
54
  */
35
- export declare const relativeLinkRewriter: (project: BlumeProject) => RelativeLinkRewriter;
55
+ export declare const relativeLinkRewriter: (project: BlumeProject, options?: RelativeLinkOptions) => RelativeLinkRewriter;
@@ -981,6 +981,12 @@ export interface OgPaletteConfig {
981
981
  }
982
982
  /** Per-page Open Graph image generation. */
983
983
  export interface OgConfig {
984
+ /**
985
+ * Default social image for pages without their own `seo.image`, in place of
986
+ * the generated card (which then defaults off): one path or URL, or one per
987
+ * locale, like any localizable label.
988
+ */
989
+ image?: string | Record<string, string>;
984
990
  /**
985
991
  * Card subtitle. Defaults to the site description; a string overrides it,
986
992
  * `false` renders the card without one.
@@ -1097,6 +1103,31 @@ export interface SoftwareConfig {
1097
1103
  }
1098
1104
  /** Discoverability: OG images, feeds, sitemap, robots, and structured data. */
1099
1105
  export interface SeoConfig {
1106
+ /**
1107
+ * Structured-data entities shared with a parent site (`example.com/docs`),
1108
+ * referenced by absolute `@id` instead of defined again.
1109
+ */
1110
+ jsonLd?: {
1111
+ /** The WebSite's `@id`, which every page names as `isPartOf`. */
1112
+ websiteId?: string;
1113
+ /** The Organization's `@id`; `organization` still adds its details. */
1114
+ organizationId?: string;
1115
+ /** Credited on pages without their own `authors`. */
1116
+ author?: {
1117
+ "@id": string;
1118
+ };
1119
+ /** Every page's publisher, over the Organization. */
1120
+ publisher?: {
1121
+ "@id": string;
1122
+ };
1123
+ /** Emit the WebSite node. Defaults to true unless `websiteId` is set. */
1124
+ website?: boolean;
1125
+ };
1126
+ /**
1127
+ * Date pages without a front matter `date` by the git commit that added
1128
+ * their source file, following exact renames. Defaults to `false`.
1129
+ */
1130
+ datePublished?: false | "git";
1100
1131
  /**
1101
1132
  * Meta tags written into every page's head, name to content: site
1102
1133
  * verification tokens, `theme-color`, an app banner, and anything else
@@ -1136,7 +1167,10 @@ export interface SeoConfig {
1136
1167
  /** RSS/Atom feeds. */
1137
1168
  rss?: RssConfig;
1138
1169
  /** Generate sitemap.xml (requires `deployment.site`). Defaults to `true`. */
1139
- sitemap?: boolean;
1170
+ sitemap?: boolean | {
1171
+ /** List each page's translations as hreflang links. Defaults to `false`. */
1172
+ alternates?: boolean;
1173
+ };
1140
1174
  /**
1141
1175
  * The documented product, added to the homepage's JSON-LD as a
1142
1176
  * `SoftwareApplication` node. `true` takes every default (name and
@@ -104,6 +104,12 @@ export interface BlumeRoute {
104
104
  /** True when this route renders fallback content for a missing translation. */
105
105
  fallback: boolean;
106
106
  hidden: boolean;
107
+ /**
108
+ * The translations the page head links as hreflang alternates: the
109
+ * indexable ones, so a `noindex` or hidden translation is left out (see
110
+ * `hreflangAlternates`).
111
+ */
112
+ hreflang: RouteAlternate[];
107
113
  id: string;
108
114
  /** Whether the page is part of the search index. */
109
115
  indexable: boolean;
@@ -117,6 +123,12 @@ export interface BlumeRoute {
117
123
  */
118
124
  monolingual: boolean;
119
125
  path: string;
126
+ /**
127
+ * The git commit that added the page's source, as an ISO date, when
128
+ * `seo.datePublished` is `"git"` and front matter carries no date; else
129
+ * `null`.
130
+ */
131
+ published: string | null;
120
132
  title: string;
121
133
  /** Resolved docs version (`""` for the current docs). */
122
134
  version: string;
@@ -242,6 +254,7 @@ export interface BlumeDataConfig {
242
254
  * kept out of this snapshot, which pages serialize into HTML.
243
255
  */
244
256
  og: {
257
+ image?: string | Record<string, string>;
245
258
  /**
246
259
  * Site-wide card subtitle: `seo.og.description` (`false` omits it) over
247
260
  * the site description. A page with its own description shows that
@@ -284,6 +297,7 @@ export interface BlumeDataConfig {
284
297
  };
285
298
  /** Deployment site URL, or `null` when none is configured/detected. */
286
299
  site: string | null;
300
+ jsonLd: ResolvedConfig["seo"]["jsonLd"];
287
301
  structuredData: boolean;
288
302
  theme: ResolvedConfig["theme"];
289
303
  title: string;
@@ -7,22 +7,31 @@ export interface ResolvedLastModified {
7
7
  export declare const gitEnv: () => NodeJS.ProcessEnv;
8
8
  /** Normalize the `lastModified` config (`false | "git" | "frontmatter"`) into `{ enabled, source }`. */
9
9
  export declare const resolveLastModifiedConfig: (value: false | "git" | "frontmatter") => ResolvedLastModified;
10
+ /** Each file's git dates, keyed by path. */
11
+ export interface GitDates {
12
+ /** The committer ISO date of the newest commit that changed the file. */
13
+ modified: Map<string, string>;
14
+ /** The committer ISO date of the commit that added the file. */
15
+ published: Map<string, string>;
16
+ }
10
17
  /**
11
- * Parse `git log --format=%x00%cI --name-status -M100%` output into a map of
12
- * repo-root-relative path → the committer ISO date of the newest commit that
13
- * changed the file. Each commit emits a NUL-prefixed date line followed by a
14
- * status line per path it touched (`M\tpath`, `R100\told\tnew`); since git
15
- * logs newest-first, the first date seen for a path wins. Blank lines are
16
- * ignored.
18
+ * Parse `git log --format=%x00%cI --name-status -M100%` output into each
19
+ * repo-root-relative path's dates. Each commit emits a NUL-prefixed date line
20
+ * followed by a status line per path it touched (`M\tpath`, `R100\told\tnew`);
21
+ * since git logs newest-first, the first date seen for a path is when it last
22
+ * changed, and the add (`A\tpath`) that ends its history is when it was
23
+ * published. Blank lines are ignored.
17
24
  *
18
25
  * A rename (`-M100%` reports only exact ones, where the content is unchanged)
19
26
  * follows the file the way `git log --follow` does for one path: it dates
20
27
  * nothing itself, and the older commits that name the old path date the file
21
28
  * under its current name, so renaming `page.md` to `page.mdx` keeps the page's
22
- * date. A file whose visible history starts at a rename (a shallow clone cut
23
- * off the commits before it) takes the rename's date.
29
+ * dates. A file whose visible history starts at a rename (a shallow clone cut
30
+ * off the commits before it) takes the rename's date. Only exact renames are
31
+ * followed: `--follow`'s default 50% similarity would hand a new page the
32
+ * history of a similar one deleted in the same commit.
24
33
  */
25
- export declare const parseGitLog: (output: string) => Map<string, string>;
34
+ export declare const parseGitLog: (output: string) => GitDates;
26
35
  /**
27
36
  * `path` with every symlink resolved, the spelling git prints paths in: a
28
37
  * project opened through a link (`/tmp/site` on macOS, where `/tmp` links to
@@ -41,17 +50,16 @@ export declare const realPath: (path: string) => string;
41
50
  */
42
51
  export declare const gitRepositoryRoot: (root: string) => string | null;
43
52
  /**
44
- * Resolve each source file's last-modified date from git history, keyed by
45
- * absolute source path. Runs a single `git log` over the given content roots
46
- * (each local source's own root, which may diverge from `content.root`),
47
- * following renames within them (see `parseGitLog`; a file moved in from
48
- * outside every root dates from the move), and maps repo-root-relative paths
49
- * back to the given absolute paths
50
- * (monorepo-safe via `rev-parse --show-toplevel`). Returns an empty map if git
51
- * is unavailable or the project isn't a repo — the feature then simply shows
53
+ * Resolve each source file's git dates (see `parseGitLog`), keyed by absolute
54
+ * source path. Runs a single `git log` over the given content roots (each
55
+ * local source's own root, which may diverge from `content.root`), following
56
+ * renames within them (a file moved in from outside every root dates from the
57
+ * move), and maps repo-root-relative paths back to the given absolute paths
58
+ * (monorepo-safe via `rev-parse --show-toplevel`). Returns empty maps if git
59
+ * is unavailable or the project isn't a repo — the features then simply show
52
60
  * no dates.
53
61
  */
54
- export declare const gitLastModifiedTimes: (root: string, contentRoots: string[], sourcePaths: string[], repositoryRoot?: string | null) => Map<string, string>;
62
+ export declare const gitFileDates: (root: string, contentRoots: string[], sourcePaths: string[], repositoryRoot?: string | null) => GitDates;
55
63
  /**
56
64
  * Whether the repository containing `root` is a shallow clone. Returns false
57
65
  * when git is unavailable or the project isn't a repo — those cases already
@@ -67,3 +75,10 @@ export declare const isShallowGitRepository: (root: string) => boolean;
67
75
  * shallow.
68
76
  */
69
77
  export declare const lastModifiedShallowWarning: (root: string, undatedCount: number) => Diagnostic[];
78
+ /**
79
+ * The warning for `seo.datePublished: "git"` in a shallow clone, where the
80
+ * oldest commit git can see is where the clone was cut, not where each page
81
+ * began. Git then dates no page rather than every page wrongly. Empty when
82
+ * every page has a front matter date.
83
+ */
84
+ export declare const datePublishedShallowWarning: (undatedCount: number) => Diagnostic[];
@@ -33,6 +33,16 @@ export interface RelativeLinkBase {
33
33
  * name (`./node.js` for `node.js.mdx`, `./v1.2`) is still a page link.
34
34
  */
35
35
  export declare const resolveRelativeHref: (href: string, from: RelativeLinkBase, resolveFile?: (path: string) => string | undefined, hasRoute?: (route: string) => boolean) => string | undefined;
36
+ /**
37
+ * Where a relative page link in a raw HTML `<a href>` written on `route`
38
+ * lands, with the authored `?query#hash` kept, or `undefined` when `href`
39
+ * isn't a relative page link (see {@link resolveRelativeHref}). The Markdown
40
+ * pipeline leaves a raw element as written, so the browser resolves it
41
+ * against the page's slashless URL, as `blume validate` reads it: its parent
42
+ * directory, even on an index page, and a `.md` target is that path's
43
+ * Markdown copy rather than a file link.
44
+ */
45
+ export declare const resolveRawRelativeHref: (href: string, route: string, hasRoute: (route: string) => boolean) => string | undefined;
36
46
  /**
37
47
  * Where a root-relative link to a content file (`/guide/new.md`, the way
38
48
  * VitePress, Docsify, and TypeDoc write one) lands: the route of the source
@@ -96,6 +106,12 @@ export declare const buildFileRouteIndex: (pages: readonly PageRecord[], i18n: L
96
106
  * tree's file at the same place.
97
107
  */
98
108
  export declare const routeOfLinkedFile: (index: FileRouteIndex, from: FileLinkBase, path: string) => string | undefined;
109
+ /**
110
+ * The route a file link written on a staged page lands on: the linked
111
+ * entry's, by its path under the staging dir (`entryId`), as the rendered
112
+ * link resolves it.
113
+ */
114
+ export declare const routeOfLinkedEntry: (index: FileRouteIndex, entryId: string, path: string) => string | undefined;
99
115
  /**
100
116
  * Validate every link discovered in the content graph: internal page links and
101
117
  * anchors against the route map, asset links against the public dir, and
@@ -4048,6 +4048,18 @@ export declare const blumeConfigSchema: z.ZodObject<{
4048
4048
  }>>>>;
4049
4049
  }, z.core.$strict>>>;
4050
4050
  seo: z.ZodPrefault<z.ZodObject<{
4051
+ datePublished: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<false>, z.ZodLiteral<"git">]>>;
4052
+ jsonLd: z.ZodPrefault<z.ZodObject<{
4053
+ author: z.ZodOptional<z.ZodObject<{
4054
+ "@id": z.ZodURL;
4055
+ }, z.core.$strict>>;
4056
+ organizationId: z.ZodOptional<z.ZodURL>;
4057
+ publisher: z.ZodOptional<z.ZodObject<{
4058
+ "@id": z.ZodURL;
4059
+ }, z.core.$strict>>;
4060
+ website: z.ZodOptional<z.ZodBoolean>;
4061
+ websiteId: z.ZodOptional<z.ZodURL>;
4062
+ }, z.core.$strict>>;
4051
4063
  metatags: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
4052
4064
  og: z.ZodDefault<z.ZodObject<{
4053
4065
  description: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<false>]>>;
@@ -4071,6 +4083,7 @@ export declare const blumeConfigSchema: z.ZodObject<{
4071
4083
  }>>;
4072
4084
  weight: z.ZodOptional<z.ZodNumber>;
4073
4085
  }, z.core.$strict>]>>>;
4086
+ image: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>;
4074
4087
  logo: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<false>]>>;
4075
4088
  palette: z.ZodOptional<z.ZodObject<{
4076
4089
  accent: z.ZodOptional<z.ZodString>;
@@ -4104,7 +4117,9 @@ export declare const blumeConfigSchema: z.ZodObject<{
4104
4117
  limit: z.ZodDefault<z.ZodNumber>;
4105
4118
  types: z.ZodDefault<z.ZodArray<z.ZodString>>;
4106
4119
  }, z.core.$strict>>;
4107
- sitemap: z.ZodDefault<z.ZodBoolean>;
4120
+ sitemap: z.ZodDefault<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
4121
+ alternates: z.ZodDefault<z.ZodBoolean>;
4122
+ }, z.core.$strict>]>>;
4108
4123
  software: z.ZodPipe<z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodObject<{
4109
4124
  applicationCategory: z.ZodDefault<z.ZodString>;
4110
4125
  description: z.ZodOptional<z.ZodString>;
@@ -245,6 +245,11 @@ export interface PageRecord {
245
245
  unknownProps?: ElementUse[];
246
246
  /** Resolved "last updated" ISO date, when the feature is enabled. */
247
247
  lastModified?: string;
248
+ /**
249
+ * The git commit that added the source file, as an ISO date, when
250
+ * `seo.datePublished` is `"git"` and front matter carries no `date`.
251
+ */
252
+ published?: string;
248
253
  /** Absolute paths of files this page `<include>`s, transitively. Drives the
249
254
  * dev-server invalidation edge from a partial to the pages that splice it. */
250
255
  includes?: string[];
@@ -461,6 +466,11 @@ export interface RouteManifestEntry {
461
466
  aiExclude?: boolean;
462
467
  /** Resolved "last updated" ISO date, when the feature is enabled. */
463
468
  lastModified?: string;
469
+ /**
470
+ * The git commit that added the source file, as an ISO date, when
471
+ * `seo.datePublished` is `"git"` and front matter carries no `date`.
472
+ */
473
+ published?: string;
464
474
  }
465
475
  /** The generated runtime contract between core and the Astro project. */
466
476
  export interface BlumeManifest {
@@ -1,4 +1,5 @@
1
1
  import type { BlumeProject } from "../core/project-graph.ts";
2
+ import type { RouteAlternate } from "../core/types.ts";
2
3
  /** One emitted sitemap artifact: its dist-root filename and XML body. */
3
4
  export interface SitemapFile {
4
5
  name: string;
@@ -6,6 +7,14 @@ export interface SitemapFile {
6
7
  }
7
8
  /** The build log line for an emitted sitemap set: one file, or an index. */
8
9
  export declare const describeSitemapFiles: (files: readonly SitemapFile[]) => string;
10
+ /**
11
+ * Each route's hreflang set, keyed by route: its indexable translations. The
12
+ * page head links it, and so does the sitemap under `seo.sitemap.alternates`,
13
+ * so the two never disagree, and neither points crawlers at a page they're
14
+ * told not to index. A page that can't be indexed joins no set — except an
15
+ * i18n fallback copy, which links the translations it stands in for.
16
+ */
17
+ export declare const hreflangAlternates: (project: BlumeProject) => Map<string, RouteAlternate[]>;
9
18
  /**
10
19
  * Build the sitemap files from the route manifest plus the routes the manifest
11
20
  * can't see: custom `.astro` pages (most importantly a custom landing `/`) and
@@ -79,7 +79,9 @@ interface BuildOptions {
79
79
  * same serializers the `.md` mirror, llms-full.txt and MCP `get_page` use: a
80
80
  * page whose body is a `<CardGroup>` of `<Card>`s is prose to a reader and bare
81
81
  * JSX to anything reading the source, and section landing pages are exactly
82
- * that shape.
82
+ * that shape. Its page links point at the pages they mean, as they do there
83
+ * too, as the routes the assistant's snapshot keys those pages by (see
84
+ * `relativeLinkRewriter`'s `assistant`): the model cites the links it reads.
83
85
  *
84
86
  * `audience` resolves `<Visibility>` blocks before extraction: `"web"`
85
87
  * (default) keeps web-only content and drops agents-only blocks — the site
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Latin folding, shared by the search index and everything that finds a query
3
+ * in a page's text afterwards: the search dialog's highlights and excerpts,
4
+ * and the assistant's excerpts. The index matches Prüfung for `prufung`, so
5
+ * the rest must too, or a page found by folding shows its opening with
6
+ * nothing marked.
7
+ */
8
+ /** `text` without its soft hyphens. */
9
+ export declare const stripSoftHyphens: (text: string) => string;
10
+ /**
11
+ * Fold lowercased text's Latin letters to the plain letters an unaccented
12
+ * query types: accents drop (café → cafe), letters NFD leaves whole are
13
+ * spelled out (Łódź → lodz, ışık and IŞIK → isik, Straße → strasse), and
14
+ * compatibility forms unpack (the fi ligature → fi, fullwidth API → api).
15
+ * Soft hyphens go too. Marks on any other script, and marks on no letter
16
+ * (the keycap in 1️⃣), stay.
17
+ */
18
+ export declare const foldLatin: (lowered: string) => string;
19
+ /**
20
+ * {@link foldLatin} for the Latin letters of a lowercased term, whatever
21
+ * script the rest of it is in: a segmenter keeps caféनमस्ते together as one
22
+ * term. Only the Latin runs fold. Marks are spelling elsewhere (Thai vowels
23
+ * and tones; the breve that separates Cyrillic й from и), and NFD would split
24
+ * a Hangul syllable into its jamo.
25
+ */
26
+ export declare const foldLatinTerm: (term: string) => string;
27
+ /**
28
+ * A regex source that finds `term` in unfolded text whatever accents, soft
29
+ * hyphens or spelled-out letters its Latin words carry there: `prufung`
30
+ * matches Prüfung, `strasse` matches Straße. Compile it with the `iu` flags.
31
+ * Characters outside Latin match as themselves.
32
+ */
33
+ export declare const accentInsensitive: (term: string) => string;
@@ -34,10 +34,10 @@ export interface OramaDoc {
34
34
  * Orama client loader (browser), the MCP server, and assistant grounding (Node),
35
35
  * so ranking is identical wherever docs are queried. `locale` — the site's
36
36
  * `i18n.defaultLocale` — swaps in a word-segmenting tokenizer for every
37
- * non-Latin script, all of which Orama's default tokenizer reduces to zero
37
+ * non-Latin script, all of which {@link latinTokenizer} reduces to zero
38
38
  * tokens. A non-Latin default's tokenizer serves every page: Latin words
39
39
  * survive segmentation intact, so English pages on a segmented index stay
40
- * searchable. A Latin default keeps Orama's tokenizer for its Latin-script
40
+ * searchable. A Latin default keeps {@link latinTokenizer} for its Latin-script
41
41
  * pages, and each page in a non-Latin locale (the Japanese and Hindi
42
42
  * translations of an English site) is indexed with its own locale's tokenizer.
43
43
  */
@@ -68,7 +68,7 @@ export interface OramaQueryFilters {
68
68
  * On an index whose translations have tokenizers of their own, a query scoped
69
69
  * to a locale is tokenized with that locale's tokenizer. An unscoped query
70
70
  * searches each locale's pages with its own tokenizer, and the Latin-script
71
- * pages with Orama's, then merges the matches by score: every search runs over
72
- * the one index, so their scores share its term statistics.
71
+ * pages with {@link latinTokenizer}, then merges the matches by score: every
72
+ * search runs over the one index, so their scores share its term statistics.
73
73
  */
74
74
  export declare const queryOramaIndex: (db: AnyOrama, term: string, limit: number, filters?: OramaQueryFilters) => Promise<OramaDoc[]>;
@@ -53,8 +53,41 @@ export interface StructuredDataIdentity {
53
53
  organization?: OrganizationIdentity;
54
54
  software?: SoftwareIdentity;
55
55
  }
56
+ /**
57
+ * `seo.jsonLd`: entities the docs share with a site they live inside
58
+ * (`example.com/docs`), referenced by `@id` instead of defined again.
59
+ */
60
+ export interface JsonLdEntities {
61
+ /** Credited on pages without their own `authors`. */
62
+ author?: {
63
+ "@id": string;
64
+ };
65
+ /** Cited as every page's publisher in place of the Organization's id. */
66
+ publisher?: {
67
+ "@id": string;
68
+ };
69
+ /** The Organization's `@id`, defined here or elsewhere. */
70
+ organizationId?: string;
71
+ /** Emit the WebSite node. Defaults to true unless `websiteId` is set. */
72
+ website?: boolean;
73
+ /** The WebSite's `@id`, which every page names as `isPartOf`. */
74
+ websiteId?: string;
75
+ }
76
+ /** A front matter `authors` entry: a name, or a name with a profile URL. */
77
+ export type AuthorInput = string | {
78
+ name: string;
79
+ url?: string;
80
+ };
56
81
  /** Inputs for a page's JSON-LD, all known at render time in RootLayout. */
57
82
  export interface StructuredDataInput {
83
+ jsonLd?: JsonLdEntities;
84
+ /**
85
+ * The page's `og:image`, emitted as the page node's `image`. Absolute when
86
+ * `siteUrl` is set, otherwise root-relative like every other URL here.
87
+ */
88
+ image?: string | null;
89
+ /** The page's own byline, which wins over `jsonLd.author`. */
90
+ authors?: AuthorInput | AuthorInput[];
58
91
  siteName: string;
59
92
  /** Absolute site origin, or null when `deployment.site` is unset. */
60
93
  siteUrl: string | null;
@@ -149,7 +149,7 @@ search: orama(),
149
149
 
150
150
  #### Non-Latin scripts
151
151
 
152
- Orama's standard tokenizer keeps only basic Latin letters, digits and a handful of accented vowels, so text in any other script — Japanese, Chinese, Korean and Thai, but equally Russian, Greek, Hebrew and Hindi — would otherwise produce no matches at all. Blume handles this for you: pages in a locale that resolves to a non-Latin script are indexed with a word-segmenting tokenizer (built on the browser- and Node-native `Intl.Segmenter`). Declaring your site's languages is all it takes:
152
+ The standard tokenizer keeps only Latin letters and digits, so text in any other script — Japanese, Chinese, Korean and Thai, but equally Russian, Greek, Hebrew and Hindi — would otherwise produce no matches at all. Blume handles this for you: pages in a locale that resolves to a non-Latin script are indexed with a word-segmenting tokenizer (built on the browser- and Node-native `Intl.Segmenter`). Declaring your site's languages is all it takes:
153
153
 
154
154
  ```ts blume.config.ts lineNumbers
155
155
  i18n: {
@@ -158,7 +158,7 @@ i18n: {
158
158
  }
159
159
  ```
160
160
 
161
- The same tokenizers serve the search dialog, the MCP server's `search_docs` tool, and assistant grounding. The script is what decides, not the language name — `az-Cyrl` is segmented while `sr-Latn` is not. With a non-Latin default, every page shares the default locale's tokenizer: Latin words survive segmentation intact, so pages in English stay searchable alongside the default language. With a Latin default, Latin-script pages keep the standard tokenizer and each translation in a non-Latin script gets its own locale's, so an English site's Japanese and Hindi pages are searchable from their own language and from a search across every language. Latin-script languages that lean heavily on diacritics (Vietnamese, or Serbian in Latin script) also fare worse on the standard tokenizer, which folds only a few accented vowels and splits words on the rest.
161
+ The same tokenizers serve the search dialog, the MCP server's `search_docs` tool, and assistant grounding. The script is what decides, not the language name — `az-Cyrl` is segmented while `sr-Latn` is not. With a non-Latin default, every page shares the default locale's tokenizer: Latin words survive segmentation intact, so pages in English stay searchable alongside the default language. With a Latin default, Latin-script pages keep the standard tokenizer and each translation in a non-Latin script gets its own locale's, so an English site's Japanese and Hindi pages are searchable from their own language and from a search across every language. The standard tokenizer strips accents and spells out letters that have none to strip (ø as o, ł as l, ß as ss), so `cafe` finds café and `lodz` finds Łódź, and the search dialog and the assistant's excerpts find the accented word in the page too. Where accents tell words apart (Swedish ändra and andra, Vietnamese tones), a query also matches words that differ from it only in those accents.
162
162
 
163
163
  Japanese and Chinese go one step further. Segmenting alone indexes a compound term as its parts — 資金決済法 as 資金, 決済 and 法 — which lets a page mentioning each part somewhere outrank the page the term is actually about. Han, Hiragana and Katakana are therefore indexed as overlapping character pairs, and queries on those indexes prefer pages carrying a term's pairs together, loosening to any-pair matching when no page carries them all, so typing a whole sentence still returns its closest pages. Korean and Thai keep their segmented words.
164
164