fix(docs-site): gate language scope and geo output

This commit is contained in:
Jacobinwwey 2026-06-18 20:29:15 -05:00
parent dc5302d2ef
commit a2ecbcd866
17 changed files with 591 additions and 46 deletions

View file

@ -41,6 +41,9 @@ jobs:
env:
NODE_ENV: production
- name: Audit website build
run: npm run audit:build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:

View file

@ -1,15 +1,15 @@
# NotEMD GEO Roadmap
**Created:** 2026-06-12
**Updated:** 2026-06-17
**Status:** Phase 1-3 shipped; Phase 4 GitHub Pages reliability, language truth, and AI retrieval entry point complete in this batch.
**Updated:** 2026-06-19
**Status:** Phase 1-3 shipped; Phase 4 GitHub Pages reliability, language truth, AI retrieval entry point, and build-output gate complete in this batch.
**Scope:** Post-deployment GEO optimization for AI search engine visibility, documentation truth, and GitHub Pages reliability.
---
## Current Truth Snapshot
The website is a Docusaurus site under `website/`, deployed by `.github/workflows/deploy-docs.yml` on pushes to `main` that touch `website/**` or the workflow file. The workflow already uses `npm ci` and `npm run build` from the `website` working directory.
The website is a Docusaurus site under `website/`, deployed by `.github/workflows/deploy-docs.yml` on pushes to `main` that touch `website/**` or the workflow file. The workflow uses `npm ci`, `npm run build`, and `npm run audit:build` from the `website` working directory before uploading the Pages artifact.
The current public language surface is deliberately narrow:
@ -20,6 +20,28 @@ The current public language surface is deliberately narrow:
| Other locales | Not published | Do not add empty locale folders to `i18n.locales` |
| Plugin UI i18n | Separate runtime feature | Do not confuse runtime UI language support with website documentation coverage |
## 2026-06-19 Pages / Language Gate Follow-Up
### Findings
| Finding | Evidence | Impact |
|---|---|---|
| zh-CN homepage still linked untranslated fallback docs | Built `website/build/zh-CN/index.html` contained `/obsidian-NotEMD/zh-CN/docs/intro`, `/zh-CN/docs/getting-started/quick-start`, `/zh-CN/docs/providers/overview`, and `/zh-CN/docs/pillar-ai-knowledge` | Users could still enter English fallback pages through the Chinese homepage even though crawlers were protected by noindex/sitemap filtering |
| Navbar/footer docs link localized into fallback docs | Docusaurus `docSidebar` / `to` links produced zh-CN docs routes for untranslated pages | UI language truth diverged from crawler truth |
| Published zh-CN scope was duplicated | FAQ exception lived separately in sitemap filtering, DocItem noindex logic, and homepage links | Future translation promotion could easily update one surface and miss another |
| Deploy workflow lacked a build-output audit | `.github/workflows/deploy-docs.yml` ran `npm run build` only | GitHub Pages could publish route drift that source review did not catch |
### Fixes Landed In This Follow-Up
| Fix | Files | Result |
|---|---|---|
| Add shared published-language scope | `website/src/lib/publishedLanguageScope.js` | zh-CN published ids/paths have one owner |
| Route zh-CN homepage untranslated docs to canonical English URLs | `website/src/pages/index.js` | zh-CN homepage keeps FAQ localized and sends other critical doc links to English canonical pages |
| Keep sitemap and noindex on the same rule | `website/docusaurus.config.js`, `website/src/theme/DocItem/Layout/index.js` | Sitemap filtering and fallback robots metadata consume the same published scope |
| Add built-output audit | `website/scripts/audit-build.cjs`, `website/package.json` | `npm run audit:build` checks real generated pages, sitemaps, and `llms.txt` |
| Gate GitHub Pages deploy | `.github/workflows/deploy-docs.yml` | Pages upload is blocked unless build output satisfies the language/GEO contract |
| Record maintainer workflow | `docs/maintainer/github-pages-language-geo-workflow.*` | Future locale promotion has a concrete owner and command path |
## 2026-06-17 Pages / GEO Audit
### Findings
@ -113,8 +135,10 @@ Phase 4 exists because the previous roadmap optimized schema/content while leavi
| Root broken markdown links removed | Fixed | No `/obsidian-NotEMD/` root broken-link warning in `npm run build` |
| Published language policy matches code | Fixed | README + roadmap state `en` plus partial `zh-CN` |
| Untranslated zh-CN fallback docs fenced | Fixed | zh-CN sitemap keeps only root + FAQ; fallback docs emit `noindex,follow` |
| zh-CN homepage does not route users into fallback docs | Fixed | `npm run audit:build` rejects `/zh-CN/docs/intro`, quick-start, provider overview, and pillar fallback links |
| GitHub Pages deploy has built-output language/GEO gate | Fixed | `.github/workflows/deploy-docs.yml` runs `npm run audit:build` after build |
| AI retrieval entry point exists | Fixed | `website/build/llms.txt` |
| Test files excluded from this commit | Required | Commit file list must not include `src/tests/**` |
| Generated outputs excluded from source commit | Required | `website/build` and Slidev export artifacts remain ignored/uncommitted |
## Better GEO Strategy
@ -125,7 +149,8 @@ The next effective GEO strategy is truth-first and route-first, not locale-volum
3. **Use `llms.txt` as the compact answer-engine map.** It should point to canonical docs, explain language scope, and discourage answers based on generated exports or stale issue text.
4. **Keep schema accurate before making it richer.** Person, Organization, WebPage, TechArticle, FAQPage, citations, and concepts are useful only if URLs resolve and claims match source pages.
5. **Consolidate thin provider pages before adding more.** Stub-like provider pages dilute crawl quality; expand them with real setup, request semantics, and troubleshooting or merge them into stronger overview pages.
6. **Measure after deploy, then tune.** Re-run Google indexing checks and AI visibility tests only after the fixed Pages build is deployed.
6. **Gate the built output, not only the source.** `npm run audit:build` must pass before Pages upload because Docusaurus locale behavior can change the final route graph.
7. **Measure after deploy, then tune.** Re-run Google indexing checks and AI visibility tests only after the fixed Pages build is deployed.
### Critical Path Translation Plan
@ -151,19 +176,21 @@ Until this set is translated, zh-CN should stay explicitly partial.
| GitHub Pages root route missing | Fixed | Root homepage now exists for default and zh-CN locale |
| Docusaurus v4 markdown config breakage | Fixed | Broken markdown hook moved under `markdown.hooks` |
| Untranslated zh-CN fallback docs dilute language quality | Controlled | Exclude fallback docs from sitemap and emit `noindex,follow` until translated |
| zh-CN UI links users into fallback docs | Controlled | Homepage, navbar, and footer docs entry points now route untranslated critical docs to canonical English URLs; audit blocks regressions |
| Thin provider pages dilute crawl budget | Open | Expand or consolidate provider pages before adding more |
| Mermaid bundle size on non-diagram pages | Accepted | Docusaurus/theme tradeoff; not the current GEO blocker |
| Hand-maintained `llms.txt` can drift | Open | Update it when canonical docs, language status, or provider docs change |
| Test or generated export files accidentally entering main | Controlled in this batch | Stage production/docs files only; leave tests and generated exports out of the commit |
| Hand-maintained `llms.txt` can drift | Controlled | `npm run audit:build` checks the language-scope markers; update content when canonical docs, language status, or provider docs change |
| Test or generated export files accidentally entering main | Controlled in this batch | Stage production/docs/test contract files only; leave generated exports out of the commit |
## Success Metrics
| Timeframe | Metric | Target | Current |
|-----------|--------|--------|---------|
| Immediate | Docusaurus build | No root broken-link or deprecated markdown-hook warnings | Verified 2026-06-17 |
| Immediate | Root pages | `index.html` and `zh-CN/index.html` exist | Verified 2026-06-17 |
| Immediate | zh-CN fallback fence | Sitemap excludes untranslated docs; fallback docs noindex | Verified 2026-06-17 |
| Immediate | AI retrieval map | `llms.txt` exists in build output | Verified 2026-06-17 |
| Immediate | Docusaurus build | No root broken-link or deprecated markdown-hook warnings | Verified 2026-06-19 |
| Immediate | Root pages | `index.html` and `zh-CN/index.html` exist | Verified 2026-06-19 |
| Immediate | zh-CN fallback fence | Sitemap excludes untranslated docs; fallback docs noindex | Verified 2026-06-19 by `npm run audit:build` |
| Immediate | zh-CN homepage language routes | Untranslated critical docs route to canonical English URLs; FAQ stays localized | Verified 2026-06-19 by `npm run audit:build` |
| Immediate | AI retrieval map | `llms.txt` exists in build output and records language scope | Verified 2026-06-19 by `npm run audit:build` |
| 2 weeks post-deploy | Docusaurus site indexed | Google `site:` returns results | Unknown |
| 2 weeks post-deploy | First AI citation | 1+ ChatGPT/Perplexity/GLM mention | 0 baseline |
| 1 month post-deploy | Citation rate | 5-10% for core keywords | 0 baseline |
@ -189,7 +216,7 @@ Until this set is translated, zh-CN should stay explicitly partial.
| Date | Action |
|------|--------|
| 2026-06-17 | Verify fixed website build, root pages, zh-CN root, and `llms.txt` output |
| 2026-06-19 | Verify fixed website build, root pages, zh-CN root, canonical English fallback routing, zh-CN sitemap/noindex fence, and `llms.txt` output through `npm run audit:build` |
| After deploy | Submit sitemap and inspect canonical root/locale root in Search Console |
| 2026-07-01 | Retest GLM baseline in EN + ZH after the fixed Pages deployment |
| 2026-08-01 | Second retest. If citations remain 0, audit indexed pages and provider-page thinness |

View file

@ -7,6 +7,7 @@ This directory contains repository-level documentation for maintainers and contr
## User And Maintainer Entry Points
- [Language Hub](./i18n/README.md)
- [GitHub Pages Language And GEO Workflow](./maintainer/github-pages-language-geo-workflow.md)
- [Chapter Split + TOC Extraction](./chapter-split-toc.md)
- [Release Workflow](./maintainer/release-workflow.md)
- [Release Notes 1.8.2](./releases/1.8.2.md)

View file

@ -7,6 +7,7 @@
## 用户与维护者入口
- [语言中心](./i18n/README_zh.md)
- [GitHub Pages 语言与 GEO 工作流](./maintainer/github-pages-language-geo-workflow.zh-CN.md)
- [章节拆分 + TOC 提取](./chapter-split-toc.zh-CN.md)
- [发布流程](./maintainer/release-workflow.zh-CN.md)
- [1.8.2 发布说明](./releases/1.8.2.zh-CN.md)

View file

@ -627,6 +627,33 @@ Next direction:
6. any upstream Slidev skill PR should stay generic: full references, built-in/configured theme preference, closed frontmatter, readable transforms for large diagrams/tables/code, and browser-sampled build verification. NoteMD vault paths, local fork paths, layout audit internals, and the `architecture.zh-CN.md` fixture should remain project-local.
7. standalone acceptance should now be treated as a tracked maintainer evidence package, not as an implicit chat summary: if the strict report or archived output path changes, update `docs/maintainer/slidev-standalone-acceptance-2026-06-18.*` or create a new dated acceptance file in the same shape.
### Batch G: gate GitHub Pages language and GEO output
Priority: `P0/P1`
Background:
1. the 2026-06-17 Pages pass fixed missing root pages, stale language claims, sitemap filtering, and `llms.txt`, but it still relied mostly on source review plus `npm run build`;
2. the real generated zh-CN homepage still exposed `/obsidian-NotEMD/zh-CN/docs/intro`, `/zh-CN/docs/getting-started/quick-start`, `/zh-CN/docs/providers/overview`, and `/zh-CN/docs/pillar-ai-knowledge` as user-facing links even though those pages are untranslated fallback docs;
3. that means crawler protection was better than user-facing language truth: `noindex,follow` and sitemap filtering cannot compensate for a Chinese homepage that routes users into English fallback pages.
Current landed status:
1. `website/src/lib/publishedLanguageScope.js` now owns the published zh-CN doc scope: currently only `faq`;
2. `website/docusaurus.config.js` consumes that scope for sitemap filtering and uses canonical English URLs for the navbar/footer docs entry where untranslated zh-CN docs must not be implied;
3. `website/src/theme/DocItem/Layout/index.js` consumes the same scope for fallback-doc `noindex,follow`;
4. `website/src/pages/index.js` consumes the same scope so zh-CN homepage links FAQ to `/zh-CN/docs/faq` and routes untranslated critical docs to canonical English URLs;
5. `website/scripts/audit-build.cjs` now audits the built output for root pages, canonical/WebPage JSON-LD URLs, zh-CN homepage route truth, noindex fallback behavior, sitemap language scope, and `llms.txt` language markers;
6. `website/package.json` exposes `npm run audit:build`, and `.github/workflows/deploy-docs.yml` runs it after `npm run build` before uploading the Pages artifact;
7. `docs/maintainer/github-pages-language-geo-workflow.*`, `website/README.md`, and `GEO_ROADMAP.md` now describe the deploy gate and the promotion rule for future zh-CN translations.
Next direction:
1. do not add locales as GEO surface area until the critical path is translated and reviewed;
2. promote future zh-CN docs by updating the translation and `publishedLanguageScope.js` in the same batch, then running `npm --prefix website run build && npm --prefix website run audit:build`;
3. after the route/language gate stays stable, add a provider-page quality audit for thin pages; this is a content-depth problem, not a locale-routing problem;
4. keep `website/build` and generated Slidev export artifacts out of source commits.
## 6. Documentation Sync Rule
Any future change that updates the provider-settings/model-discovery lane must re-check, at minimum:
@ -650,6 +677,18 @@ Any future change that updates the Slidev export lane must re-check, at minimum:
8. `src/ui/NotemdSidebarView.ts`
9. `package.json`
Any future change that updates the GitHub Pages / GEO / website language lane must re-check, at minimum:
1. `GEO_ROADMAP.md`
2. `website/README.md`
3. `website/docusaurus.config.js`
4. `website/src/lib/publishedLanguageScope.js`
5. `website/src/pages/index.js`
6. `website/src/theme/DocItem/Layout/index.js`
7. `website/static/llms.txt`
8. `website/scripts/audit-build.cjs`
9. `.github/workflows/deploy-docs.yml`
## 7. Verification Gate
Any update that changes the truth claims in this document should still finish with:
@ -668,6 +707,11 @@ If the update touches Slidev export, also run:
After the rendered-layout gate lands, Slidev export closure must also include a non-empty layout-audit report for `docs/architecture.zh-CN.md` with zero `overflow` and zero `unreadable-scale` findings.
If the update touches GitHub Pages / GEO / website language scope, also run:
1. `npm --prefix website run build`
2. `npm --prefix website run audit:build`
## 8. Bottom Line
Current `main` no longer needs another “did the provider settings lane really land?” argument.
@ -679,4 +723,5 @@ The real current questions are now:
3. can Stage-C local-KB / file-selection / chapter-split work deepen mixed-corpus quality evidence instead of relitigating feature existence;
4. can the widened bounded provider discovery surface remain shared-core, lightweight, and honest as a maintenance lane rather than becoming the excuse for broader architectural claims;
5. can Slidev export keep using a real UI-equivalent workflow proof and add rendered-layout quality gates instead of regressing to “the CLI can build, so the buttons must work” as weak evidence;
6. can current truth documents keep tracking the real shipped branch boundary quickly enough that future sessions do not regress back to `1.9.0/1.9.1`-era wording.
6. can GitHub Pages keep route, sitemap, canonical, `llms.txt`, and visible language entry points aligned through a build-output gate instead of source-only review;
7. can current truth documents keep tracking the real shipped branch boundary quickly enough that future sessions do not regress back to `1.9.0/1.9.1`-era wording.

View file

@ -683,13 +683,18 @@ GitHub Pages 这条线当前最大的结构风险不是缺少更多页面,而
2. `website/static/llms.txt` 已提供 canonical answer-engine map
3. Docusaurus markdown link hook 已迁到当前配置位置;
4. 未翻译的 zh-CN fallback docs 已从 sitemap 排除,并标记 `noindex,follow`
5. website README 和 GEO roadmap 已同步真实语言边界。
5. `website/src/lib/publishedLanguageScope.js` 现在统一拥有已发布 zh-CN doc scope目前只有 `faq`
6. `website/src/pages/index.js` 会把 zh-CN 首页上未翻译 critical docs 的入口导向 canonical 英文 URL只把 FAQ 保持在 `/zh-CN/docs/faq`
7. `website/docusaurus.config.js``website/src/theme/DocItem/Layout/index.js` 复用同一份发布范围,分别用于 sitemap filtering 与 fallback doc 的 `noindex,follow`
8. `website/scripts/audit-build.cjs` 会审计 build 产物中的 root pages、canonical/WebPage JSON-LD URL、zh-CN 首页路由、noindex fallback、sitemap 语言范围与 `llms.txt` 语言声明;
9. `website/package.json` 暴露 `npm run audit:build``.github/workflows/deploy-docs.yml` 在 `npm run build` 后、上传 Pages artifact 前运行该审计;
10. `docs/maintainer/github-pages-language-geo-workflow.*`、website README 与 GEO roadmap 已同步真实语言边界和部署 gate。
下一步:
1. 把 zh-CN critical path 补齐到 homepage、FAQ、intro、installation、quick-start、configuration、provider overview 与 pillar page
2. 给 `website` 增加轻量 smoke gate检查 `build/index.html`、`build/zh-CN/index.html`、`build/llms.txt` 和 sitemap 是否存在;
3. 扩写或合并 provider thin pages优先补真实 provider setup、endpoint/header 语义、model discovery 与 troubleshooting
1. 按 intro、installation、quick-start、configuration、provider overview、pillar page 的顺序补齐 zh-CN critical pathhomepage 与 FAQ 已是当前已发布范围
2. 每个页面从 fallback 晋升为已发布页面时,必须在同批更新翻译与 `publishedLanguageScope.js`,并运行 `npm --prefix website run build && npm --prefix website run audit:build`
3. 扩写或合并 provider thin pages优先补真实 provider setup、endpoint/header 语义、model discovery 与 troubleshooting这是内容质量问题,不应混进 locale route gate
4. fixed Pages 部署后再进行 Search Console 与 AI visibility 复测,不要用本地未部署结果过早判断 GEO 成败。
### Batch G把 Slidev export 从一次性 smoke 提升为真实工作流门禁
@ -747,9 +752,12 @@ GitHub Pages 这条线当前最大的结构风险不是缺少更多页面,而
1. `GEO_ROADMAP.md`
2. `website/README.md`
3. `website/docusaurus.config.js`
4. `website/src/pages/index.js`
5. `website/static/llms.txt`
6. `.github/workflows/deploy-docs.yml`
4. `website/src/lib/publishedLanguageScope.js`
5. `website/src/pages/index.js`
6. `website/src/theme/DocItem/Layout/index.js`
7. `website/static/llms.txt`
8. `website/scripts/audit-build.cjs`
9. `.github/workflows/deploy-docs.yml`
未来任何会更新 Slidev export 轨道真值的改动,至少都应同步检查:
@ -771,12 +779,10 @@ GitHub Pages 这条线当前最大的结构风险不是缺少更多页面,而
2. `npm test -- --runInBand`
3. `npm run audit:i18n-ui`
4. `npm run audit:render-host`
5. `cd website && npm run build`
6. `test -f website/build/index.html`
7. `test -f website/build/zh-CN/index.html`
8. `test -f website/build/llms.txt`
9. `git diff --check`
10. clean 的 `git status --short --branch`
5. `npm --prefix website run build`
6. `npm --prefix website run audit:build`
7. `git diff --check`
8. clean 的 `git status --short --branch`
如果改动触及 Slidev export还必须额外执行
@ -785,6 +791,11 @@ GitHub Pages 这条线当前最大的结构风险不是缺少更多页面,而
等渲染后布局 gate 落地后Slidev export 收尾还必须基于 `docs/architecture.zh-CN.md` 输出非空 layout-audit report并且 `overflow``unreadable-scale` 均为零。
如果改动触及 GitHub Pages / GEO / website language scope还必须额外执行
1. `npm --prefix website run build`
2. `npm --prefix website run audit:build`
## 8. Bottom Line
当前 `main` 已经不再需要再做一次“provider settings 这条线到底有没有落地”的论证。
@ -795,6 +806,6 @@ GitHub Pages 这条线当前最大的结构风险不是缺少更多页面,而
2. 当前 bounded CLI 分层能否继续显式保持,而未来任何 path-based promotion 都坚持 contract-first而不是 convenience-first
3. Stage-C local-KB / file-selection / chapter-split 工作能否继续补强 mixed-corpus 质量证据,而不是反复重谈“功能是否存在”;
4. 当前更宽的 bounded provider discovery surface 能否继续保持 shared-core、lightweight 且边界诚实,并作为维护轨道而不是更大架构声明的借口;
5. GitHub Pages / GEO / website language 轨道能否坚持 route-first、truth-first而不是继续用空 locale 或过时 README 制造弱信号
5. GitHub Pages / GEO / website language 轨道能否坚持 route-first、truth-first并让 route、sitemap、canonical、`llms.txt` 与可见语言入口通过 build-output gate 保持一致
6. Slidev export 能否继续用真实 UI-equivalent workflow 验证并补上渲染后布局质量门而不是退回到“CLI 能 build 就算按钮可用”的弱证明;
7. 当前真值文档能否足够快地跟上真实发货分支边界,避免未来会话又退回到 `1.9.0/1.9.1` 时代的旧措辞。

View file

@ -0,0 +1,100 @@
# GitHub Pages Language And GEO Workflow
Language: **English** | [简体中文](./github-pages-language-geo-workflow.zh-CN.md)
This workflow records the current public documentation-site gate for `website/`. It is intentionally separate from runtime plugin i18n and from Slidev export acceptance.
## Current Contract
The website publishes one complete language surface and one partial language surface:
1. English is the canonical complete documentation surface under `https://jacobinwwey.github.io/obsidian-NotEMD/docs/...`.
2. Simplified Chinese currently publishes the homepage and FAQ only.
3. Untranslated zh-CN fallback docs may still be generated by Docusaurus, but they must be fenced with `noindex,follow` and excluded from the zh-CN sitemap.
4. zh-CN homepage entry points for untranslated docs must route to canonical English URLs, not to `/zh-CN/docs/...` fallback pages.
5. `llms.txt` must describe the same language boundary so answer engines do not infer full multilingual coverage.
## Implemented Gate
The blocking gate is:
```bash
cd website
npm run build
npm run audit:build
```
`npm run audit:build` executes `website/scripts/audit-build.cjs`. The script checks the built output, not just source files:
1. root pages exist: `build/index.html` and `build/zh-CN/index.html`;
2. root pages have the expected `lang`, canonical URL, and WebPage JSON-LD URL;
3. zh-CN homepage links untranslated critical docs to canonical English URLs;
4. zh-CN homepage keeps FAQ on `/zh-CN/docs/faq`;
5. untranslated zh-CN docs emit `noindex,follow`;
6. published zh-CN FAQ does not emit `noindex,follow`;
7. sitemap output includes canonical English docs and excludes untranslated zh-CN docs;
8. `llms.txt` records the language scope.
The GitHub Pages workflow now runs this audit before uploading the Pages artifact:
```text
.github/workflows/deploy-docs.yml
-> npm run build
-> npm run audit:build
-> upload-pages-artifact
```
## Source Ownership
The language scope is shared through:
```text
website/src/lib/publishedLanguageScope.js
```
Current values:
```text
publishedZhCnDocIds = faq
publishedZhCnDocPaths = /docs/faq
```
This module is consumed by:
1. `website/docusaurus.config.js` for sitemap filtering;
2. `website/src/theme/DocItem/Layout/index.js` for fallback-doc `noindex,follow`;
3. `website/src/pages/index.js` for zh-CN homepage routing.
This avoids a previous drift pattern where sitemap, noindex, and homepage links each carried a separate copy of the same exception.
For canonical English links rendered from a zh-CN page, use the canonical origin-relative path with `autoAddBaseUrl: false` and `data-noBrokenLinkCheck`. Docusaurus checks broken links against the current locale route table, so these intentional cross-locale links must not be "fixed" back to localized fallback routes. `npm run audit:build` is the guard that proves the final path is correct.
## What Was Wrong
The previous Pages state was better than the old roadmap, but still incomplete:
1. Docusaurus generated untranslated zh-CN fallback docs.
2. Those fallback docs were noindexed and removed from sitemap, which protected crawlers.
3. The zh-CN homepage, navbar, and footer still linked users into `/zh-CN/docs/intro` and similar fallback routes.
4. The deploy workflow only ran `npm run build`; it did not enforce the language route contract.
That means the site could pass build while still presenting a weak public language surface. `noindex` is not enough when the UI itself sends readers to the wrong locale route.
## Better GEO Direction
The better GEO strategy is still truth-first:
1. keep English canonical until specific zh-CN pages are fully translated and reviewed;
2. only promote a zh-CN doc path by adding it to `publishedLanguageScope.js` in the same batch as the translation;
3. require `npm run audit:build` before Pages deploy;
4. keep `llms.txt`, sitemap, homepage links, and noindex behavior aligned;
5. expand or consolidate thin provider pages before adding more locale surfaces.
Do not use empty locale folders or fallback English pages as GEO surface area. That produces weak hreflang signals and worse user trust.
## Next Direction
1. Translate and review the zh-CN critical path in this order: intro, installation, quick-start, configuration, provider overview, AI knowledge pillar.
2. Each translated page promotion must update `publishedLanguageScope.js` and rerun `npm --prefix website run build && npm --prefix website run audit:build`.
3. Add a small provider-page quality audit only after the language route gate stays stable; provider thinness is a content quality problem, not a routing problem.
4. Keep generated `website/build` output ignored and uncommitted.

View file

@ -0,0 +1,100 @@
# GitHub Pages 语言与 GEO 工作流
语言: [English](./github-pages-language-geo-workflow.md) | **简体中文**
本文记录 `website/` 当前的公开文档站门禁。它与插件 runtime i18n、Slidev export 验收是三条不同轨道。
## 当前契约
文档站当前只发布一个完整语言面和一个部分语言面:
1. 英文是完整 canonical 文档面,路径为 `https://jacobinwwey.github.io/obsidian-NotEMD/docs/...`
2. 简体中文当前只发布首页和 FAQ。
3. Docusaurus 仍可能生成未翻译的 zh-CN fallback docs但这些页面必须标记 `noindex,follow`,并从 zh-CN sitemap 排除。
4. zh-CN 首页上指向未翻译文档的入口必须回到 canonical 英文 URL不能进入 `/zh-CN/docs/...` fallback 页面。
5. `llms.txt` 必须声明同一语言边界,避免 answer engine 推断出不存在的完整多语言覆盖。
## 已落地门禁
阻断式门禁是:
```bash
cd website
npm run build
npm run audit:build
```
`npm run audit:build` 执行 `website/scripts/audit-build.cjs`。脚本审计的是 build 产物,而不只是源码:
1. root 页面存在:`build/index.html` 与 `build/zh-CN/index.html`
2. root 页面具有预期的 `lang`、canonical URL 与 WebPage JSON-LD URL
3. zh-CN 首页把未翻译 critical docs 链到 canonical 英文 URL
4. zh-CN 首页把 FAQ 保持在 `/zh-CN/docs/faq`
5. 未翻译 zh-CN docs 输出 `noindex,follow`
6. 已发布 zh-CN FAQ 不输出 `noindex,follow`
7. sitemap 包含 canonical 英文 docs并排除未翻译 zh-CN docs
8. `llms.txt` 记录语言范围。
GitHub Pages workflow 现在会在上传 Pages artifact 前运行这个审计:
```text
.github/workflows/deploy-docs.yml
-> npm run build
-> npm run audit:build
-> upload-pages-artifact
```
## Source Ownership
语言发布范围由这一处共享:
```text
website/src/lib/publishedLanguageScope.js
```
当前值:
```text
publishedZhCnDocIds = faq
publishedZhCnDocPaths = /docs/faq
```
这个模块被三处消费:
1. `website/docusaurus.config.js` 用于 sitemap 过滤;
2. `website/src/theme/DocItem/Layout/index.js` 用于 fallback doc 的 `noindex,follow`
3. `website/src/pages/index.js` 用于 zh-CN 首页路由。
这样可以避免旧问题sitemap、noindex 与首页链接各自维护一份 “FAQ 例外”,后续很容易漂移。
从 zh-CN 页面渲染 canonical 英文链接时,应使用 canonical origin-relative path并显式设置 `autoAddBaseUrl: false``data-noBrokenLinkCheck`。Docusaurus 的 broken-link 检查基于当前 locale route table这类有意的跨 locale 链接不能被“修”回本地化 fallback 路由。最终路径是否正确由 `npm run audit:build` 负责验证。
## 之前错在哪里
此前 Pages 状态已经比旧 roadmap 好,但仍未闭环:
1. Docusaurus 会生成未翻译的 zh-CN fallback docs。
2. 这些 fallback docs 已经 noindex 且从 sitemap 排除,这保护了 crawler。
3. 但 zh-CN 首页、navbar 和 footer 仍会把用户送进 `/zh-CN/docs/intro` 这类 fallback 路由。
4. 部署 workflow 只跑 `npm run build`,没有阻断语言路由契约漂移。
所以 build 能通过,并不等于公开语言面可靠。`noindex` 不能替代 UI 入口的正确路由。
## 更好的 GEO 方向
更好的 GEO 策略仍然是 truth-first
1. 在具体 zh-CN 页面翻译并 review 前,英文继续作为 canonical
2. 只有在同批完成翻译时,才把对应 doc path 加入 `publishedLanguageScope.js`
3. Pages deploy 前必须跑 `npm run audit:build`
4. `llms.txt`、sitemap、首页链接与 noindex 行为必须保持一致;
5. 增加更多 locale 之前,先扩写或合并 thin provider pages。
不要把空 locale folder 或 fallback English 页面当成 GEO 面积。这会制造弱 hreflang 信号,也会损害用户信任。
## 下一步
1. 按顺序翻译并 review zh-CN critical pathintro、installation、quick-start、configuration、provider overview、AI knowledge pillar。
2. 每个页面从 fallback 晋升为已发布页面时,都必须更新 `publishedLanguageScope.js`,并执行 `npm --prefix website run build && npm --prefix website run audit:build`
3. 语言路由门禁稳定后,再添加 provider-page quality auditprovider thinness 是内容质量问题,不是路由问题。
4. `website/build` 继续作为生成产物忽略,不提交到 main。

View file

@ -22,6 +22,12 @@ describe('mainline progress audit contract', () => {
const matrixDocZh = readDoc('docs/brainstorms/2026-05-20-unified-follow-through-matrix.zh-CN.md');
const standaloneAcceptanceDoc = readDoc('docs/maintainer/slidev-standalone-acceptance-2026-06-18.md');
const standaloneAcceptanceDocZh = readDoc('docs/maintainer/slidev-standalone-acceptance-2026-06-18.zh-CN.md');
const pagesLanguageGeoWorkflowDoc = readDoc('docs/maintainer/github-pages-language-geo-workflow.md');
const pagesLanguageGeoWorkflowDocZh = readDoc('docs/maintainer/github-pages-language-geo-workflow.zh-CN.md');
const docsReadme = readDoc('docs/README.md');
const docsReadmeZh = readDoc('docs/README.zh-CN.md');
const websiteReadme = readDoc('website/README.md');
const geoRoadmap = readDoc('GEO_ROADMAP.md');
test('records the current Stage-B2/C/D baseline and fixture anchor in both canonical progress docs', () => {
expect(progressDoc).toContain('### 2.7 Current `890b21b` Stage-B2/C/D follow-through baseline');
@ -267,4 +273,35 @@ describe('mainline progress audit contract', () => {
expect(standaloneAcceptanceDoc).toContain('Do not commit test/generated output to main');
expect(standaloneAcceptanceDocZh).toContain('测试/生成输出不要提交到 main');
});
test('locks the GitHub Pages language and GEO build-output gate', () => {
for (const content of [pagesLanguageGeoWorkflowDoc, pagesLanguageGeoWorkflowDocZh]) {
expect(content).toContain('website/scripts/audit-build.cjs');
expect(content).toContain('website/src/lib/publishedLanguageScope.js');
expect(content).toContain('npm run audit:build');
expect(content).toContain('noindex,follow');
expect(content).toContain('llms.txt');
expect(content).toContain('/zh-CN/docs/faq');
expect(content).toContain('canonical');
expect(content).toContain('autoAddBaseUrl: false');
expect(content).toContain('data-noBrokenLinkCheck');
}
expect(docsReadme).toContain('GitHub Pages Language And GEO Workflow');
expect(docsReadmeZh).toContain('GitHub Pages 语言与 GEO 工作流');
for (const content of [progressDoc, progressDocZh, websiteReadme, geoRoadmap]) {
expect(content).toContain('website/scripts/audit-build.cjs');
expect(content).toContain('website/src/lib/publishedLanguageScope.js');
expect(content).toContain('npm run audit:build');
expect(content).toContain('noindex,follow');
expect(content).toContain('llms.txt');
}
expect(progressDoc).toContain('build-output gate');
expect(progressDocZh).toContain('build-output gate');
expect(websiteReadme).toContain('zh-CN homepage sends untranslated docs to canonical English URLs');
expect(geoRoadmap).toContain('zh-CN homepage does not route users into fallback docs');
expect(geoRoadmap).toContain('.github/workflows/deploy-docs.yml');
});
});

View file

@ -23,13 +23,23 @@ This starts a local development server at `http://localhost:3000`.
```bash
npm run build
npm run audit:build
```
Generates static content into the `build` directory.
Generates static content into the `build` directory, then audits the built Pages output.
The audit checks the public contract that source review cannot prove by itself:
- root and zh-CN root pages exist;
- canonical and JSON-LD URLs match GitHub Pages routes;
- zh-CN homepage sends untranslated docs to canonical English URLs;
- zh-CN FAQ remains published under the zh-CN route;
- untranslated zh-CN fallback docs are `noindex,follow` and excluded from the zh-CN sitemap;
- `llms.txt` still states the real language boundary.
## Deployment
The site auto-deploys to GitHub Pages on push to `main` branch (via `.github/workflows/deploy-docs.yml`).
The site auto-deploys to GitHub Pages on push to `main` branch (via `.github/workflows/deploy-docs.yml`). The deployment workflow runs both `npm run build` and `npm run audit:build` before uploading the Pages artifact.
**Live URL**: https://jacobinwwey.github.io/obsidian-NotEMD/
@ -59,7 +69,12 @@ The site auto-deploys to GitHub Pages on push to `main` branch (via `.github/wor
- Lists canonical docs, provider/runtime topics, and the current language coverage boundary
- Keeps GEO strategy focused on verified source pages instead of empty locale expansion
### 6. FAQ with FAQPage Schema
### 6. Build Output Language Gate
- `website/scripts/audit-build.cjs`: blocks Pages deployment when build output violates the published language scope
- `website/src/lib/publishedLanguageScope.js`: single source for published zh-CN doc ids and paths
- Keeps sitemap filtering, fallback `noindex,follow`, and homepage routing aligned
### 7. FAQ with FAQPage Schema
- `docs/faq.mdx`: 12 Q&A pairs
- Simplified Chinese translation: `i18n/zh-CN/docusaurus-plugin-content-docs/current/faq.mdx`
- 43% of AI citations come from FAQ content (BrightEdge data)
@ -73,6 +88,8 @@ website/
├── static/
│ ├── llms.txt # AI crawler / answer-engine entry point
│ └── img/
├── scripts/
│ └── audit-build.cjs # Built-output Pages language/GEO gate
├── docs/ # English docs
│ ├── intro.mdx
│ ├── faq.mdx
@ -85,6 +102,8 @@ website/
├── src/
│ ├── pages/
│ │ └── index.js # Locale-aware root homepage
│ ├── lib/
│ │ └── publishedLanguageScope.js
│ ├── components/
│ │ └── TLDR/ # TLDR component
│ ├── theme/
@ -124,7 +143,14 @@ citations: # Adds Schema.org "citation"
Do not add a locale to `i18n.locales` just because a translation folder exists. Docusaurus publishes fallback English docs under a locale path when docs are untranslated, which creates weak hreflang signals for search and AI crawlers.
Current zh-CN policy: the localized homepage and FAQ are published, while untranslated zh-CN doc fallbacks are excluded from sitemap output and marked `noindex,follow` by the swizzled doc layout. A locale should only become fully indexable after the critical path pages are translated and reviewed.
Current zh-CN policy: the localized homepage and FAQ are published, while untranslated zh-CN doc fallbacks are excluded from sitemap output and marked `noindex,follow` by the swizzled doc layout. The zh-CN homepage must link untranslated docs to canonical English URLs, not to zh-CN fallback routes. A locale should only become fully indexable after the critical path pages are translated and reviewed.
When promoting a zh-CN doc from fallback to published content, update `website/src/lib/publishedLanguageScope.js` in the same change as the translation and rerun:
```bash
npm run build
npm run audit:build
```
## License

View file

@ -5,20 +5,21 @@
// See: https://docusaurus.io/docs/api/docusaurus-config
import {themes as prismThemes} from 'prism-react-renderer';
import {publishedZhCnDocPaths} from './src/lib/publishedLanguageScope.js';
const siteUrl = 'https://jacobinwwey.github.io';
const baseUrl = '/obsidian-NotEMD/';
const siteBaseUrl = `${siteUrl}${baseUrl}`;
const personId = `${siteBaseUrl}#person-jacobinwwey`;
const logoUrl = `${siteBaseUrl}img/logo.svg`;
const translatedZhCnDocUrls = new Set([
`${siteBaseUrl}zh-CN/docs/faq`,
]);
const publishedZhCnDocUrls = new Set(
Array.from(publishedZhCnDocPaths, (docPath) => `${siteBaseUrl}zh-CN${docPath}`),
);
function sitemapItemMatchesPublishedLanguageScope(item) {
const itemUrl = item.url.replace(/\/$/, '');
const zhCnDocsPrefix = `${siteBaseUrl}zh-CN/docs/`;
return !itemUrl.startsWith(zhCnDocsPrefix) || translatedZhCnDocUrls.has(itemUrl);
return !itemUrl.startsWith(zhCnDocsPrefix) || publishedZhCnDocUrls.has(itemUrl);
}
/** @type {import('@docusaurus/types').Config} */
@ -36,6 +37,9 @@ const config = {
// GitHub pages deployment config.
organizationName: 'Jacobinwwey',
projectName: 'obsidian-NotEMD',
customFields: {
canonicalBasePath: baseUrl,
},
onBrokenLinks: 'warn',
// Global JSON-LD Schema for entire site
@ -170,10 +174,11 @@ const config = {
},
items: [
{
type: 'docSidebar',
sidebarId: 'tutorialSidebar',
href: `${baseUrl}docs/intro`,
position: 'left',
label: 'Docs',
autoAddBaseUrl: false,
'data-noBrokenLinkCheck': true,
},
{
to: '/docs/faq',
@ -199,7 +204,9 @@ const config = {
items: [
{
label: 'Getting Started',
to: '/docs/intro',
href: `${baseUrl}docs/intro`,
autoAddBaseUrl: false,
'data-noBrokenLinkCheck': true,
},
{
label: 'FAQ',

View file

@ -6,6 +6,7 @@
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"audit:build": "node scripts/audit-build.cjs",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",

View file

@ -0,0 +1,157 @@
const fs = require('fs');
const path = require('path');
const websiteRoot = path.resolve(__dirname, '..');
const buildRoot = path.join(websiteRoot, 'build');
const siteRoot = 'https://jacobinwwey.github.io/obsidian-NotEMD/';
const basePath = '/obsidian-NotEMD/';
const zhRoot = `${siteRoot}zh-CN/`;
const zhBasePath = `${basePath}zh-CN/`;
const publishedZhCnDocPaths = new Set(['/docs/faq']);
const canonicalEnglishDocPaths = [
'/docs/intro',
'/docs/getting-started/quick-start',
'/docs/providers/overview',
'/docs/pillar-ai-knowledge',
];
function fail(message) {
throw new Error(`[audit-build] ${message}`);
}
function readBuildFile(relativePath) {
const filePath = path.join(buildRoot, relativePath);
if (!fs.existsSync(filePath)) {
fail(`Missing build file: ${relativePath}`);
}
return fs.readFileSync(filePath, 'utf8');
}
function assertContains(content, expected, context) {
if (!content.includes(expected)) {
fail(`${context} is missing ${expected}`);
}
}
function assertNotContains(content, forbidden, context) {
if (content.includes(forbidden)) {
fail(`${context} contains forbidden ${forbidden}`);
}
}
function listHtmlFiles(directory) {
const entries = fs.readdirSync(directory, {withFileTypes: true});
const files = [];
for (const entry of entries) {
const entryPath = path.join(directory, entry.name);
if (entry.isDirectory()) {
files.push(...listHtmlFiles(entryPath));
continue;
}
if (entry.name === 'index.html') {
files.push(entryPath);
}
}
return files;
}
function routeForZhCnDocIndex(filePath) {
const docsRoot = path.join(buildRoot, 'zh-CN', 'docs');
const relativePath = path.relative(docsRoot, filePath);
const routePath = relativePath.replace(/\\/g, '/').replace(/\/index\.html$/, '');
return routePath === 'index.html' ? '/docs' : `/docs/${routePath}`;
}
function auditHomepageRoutes() {
const englishHome = readBuildFile('index.html');
const zhHome = readBuildFile('zh-CN/index.html');
assertContains(englishHome, '<html lang="en-US"', 'English homepage');
assertContains(englishHome, `rel="canonical" href="${siteRoot}"`, 'English homepage');
assertContains(englishHome, `"url":"${siteRoot}"`, 'English homepage JSON-LD');
assertContains(zhHome, '<html lang="zh-CN"', 'zh-CN homepage');
assertContains(zhHome, `rel="canonical" href="${zhRoot}"`, 'zh-CN homepage');
assertContains(zhHome, `"url":"${zhRoot}"`, 'zh-CN homepage JSON-LD');
assertContains(zhHome, `href="${zhBasePath}docs/faq"`, 'zh-CN homepage');
for (const docPath of canonicalEnglishDocPaths) {
assertContains(zhHome, `href="${basePath}${docPath.slice(1)}"`, 'zh-CN homepage');
assertNotContains(zhHome, `href="${basePath}${docPath.slice(1)}" target="_blank"`, 'zh-CN homepage');
assertNotContains(zhHome, `href="${siteRoot}${docPath.slice(1)}"`, 'zh-CN homepage');
assertNotContains(zhHome, `href="${zhBasePath}${docPath.slice(1)}"`, 'zh-CN homepage');
assertNotContains(zhHome, `href="${zhBasePath}${basePath.slice(1)}${docPath.slice(1)}"`, 'zh-CN homepage');
}
}
function auditZhCnDocFallbacks() {
const zhDocsRoot = path.join(buildRoot, 'zh-CN', 'docs');
if (!fs.existsSync(zhDocsRoot)) {
fail('Missing zh-CN docs build directory');
}
for (const filePath of listHtmlFiles(zhDocsRoot)) {
const routePath = routeForZhCnDocIndex(filePath);
const html = fs.readFileSync(filePath, 'utf8');
if (publishedZhCnDocPaths.has(routePath)) {
assertNotContains(html, 'content="noindex,follow"', `published zh-CN doc ${routePath}`);
continue;
}
assertContains(html, 'content="noindex,follow"', `untranslated zh-CN fallback ${routePath}`);
}
}
function auditSitemaps() {
const sitemap = readBuildFile('sitemap.xml');
const zhSitemap = readBuildFile('zh-CN/sitemap.xml');
assertContains(sitemap, siteRoot, 'root sitemap');
assertContains(zhSitemap, zhRoot, 'zh-CN sitemap');
assertContains(zhSitemap, `${siteRoot}zh-CN/docs/faq`, 'zh-CN sitemap');
for (const docPath of canonicalEnglishDocPaths) {
assertContains(sitemap, `${siteRoot}${docPath.slice(1)}`, 'root sitemap');
assertNotContains(zhSitemap, `${siteRoot}zh-CN${docPath}`, 'zh-CN sitemap');
}
}
function auditAiRetrievalMap() {
const llmsText = readBuildFile('llms.txt');
assertContains(llmsText, '## Language Scope', 'llms.txt');
assertContains(llmsText, 'English is the canonical complete documentation surface.', 'llms.txt');
assertContains(llmsText, `${siteRoot}zh-CN/`, 'llms.txt');
assertContains(llmsText, `${siteRoot}zh-CN/docs/faq`, 'llms.txt');
assertContains(
llmsText,
'Do not treat fallback English pages under the zh-CN locale path as independently translated Chinese source pages.',
'llms.txt',
);
}
function auditRequiredFiles() {
for (const relativePath of [
'index.html',
'zh-CN/index.html',
'llms.txt',
'robots.txt',
'sitemap.xml',
'zh-CN/sitemap.xml',
]) {
readBuildFile(relativePath);
}
}
auditRequiredFiles();
auditHomepageRoutes();
auditZhCnDocFallbacks();
auditSitemaps();
auditAiRetrievalMap();
console.log('website build audit passed');

View file

@ -0,0 +1,11 @@
export const publishedZhCnDocIds = new Set(['faq']);
export const publishedZhCnDocPaths = new Set(['/docs/faq']);
export function isPublishedZhCnDocId(docId) {
return publishedZhCnDocIds.has(docId);
}
export function isPublishedZhCnDocPath(docPath) {
const normalizedPath = docPath.startsWith('/') ? docPath : `/${docPath}`;
return publishedZhCnDocPaths.has(normalizedPath.replace(/\/$/, ''));
}

View file

@ -4,6 +4,7 @@ import Head from '@docusaurus/Head';
import Layout from '@theme/Layout';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import useBaseUrl from '@docusaurus/useBaseUrl';
import {isPublishedZhCnDocPath} from '../lib/publishedLanguageScope';
import styles from './index.module.css';
const copyByLocale = {
@ -40,8 +41,8 @@ const copyByLocale = {
eyebrow: 'Obsidian 插件文档',
heading: 'Notemd',
lead: '把笔记处理成可积累的知识资产wiki 链接、概念笔记、研究总结、翻译、图表,以及可复用的一键工作流。',
primary: '阅读文档',
secondary: '快速开始',
primary: '阅读英文文档',
secondary: '英文快速开始',
faq: '常见问题',
sections: [
{
@ -68,6 +69,19 @@ export default function Home() {
const logoSrc = useBaseUrl('img/logo.svg');
const copy = copyByLocale[i18n.currentLocale] || copyByLocale.en;
const pageUrl = new URL(siteConfig.baseUrl, siteConfig.url).toString();
const canonicalBasePath = siteConfig.customFields?.canonicalBasePath || siteConfig.baseUrl;
const englishDocHref = (docPath) => `${canonicalBasePath}${docPath.replace(/^\//, '')}`;
const docLinkProps = (docPath) => {
if (i18n.currentLocale === 'zh-CN' && !isPublishedZhCnDocPath(docPath)) {
return {
href: englishDocHref(docPath),
autoAddBaseUrl: false,
'data-noBrokenLinkCheck': true,
};
}
return {to: docPath};
};
return (
<Layout title={copy.title} description={copy.description}>
@ -90,13 +104,16 @@ export default function Home() {
<h1>{copy.heading}</h1>
<p className={styles.lead}>{copy.lead}</p>
<div className={styles.actions}>
<Link className="button button--primary button--lg" to="/docs/intro">
<Link className="button button--primary button--lg" {...docLinkProps('/docs/intro')}>
{copy.primary}
</Link>
<Link className="button button--secondary button--lg" to="/docs/getting-started/quick-start">
<Link
className="button button--secondary button--lg"
{...docLinkProps('/docs/getting-started/quick-start')}
>
{copy.secondary}
</Link>
<Link className="button button--outline button--lg" to="/docs/faq">
<Link className="button button--outline button--lg" {...docLinkProps('/docs/faq')}>
{copy.faq}
</Link>
</div>
@ -105,7 +122,7 @@ export default function Home() {
</section>
<section className={styles.sectionGrid} aria-label={copy.primary}>
{copy.sections.map((section) => (
<Link className={styles.sectionCard} key={section.href} to={section.href}>
<Link className={styles.sectionCard} key={section.href} {...docLinkProps(section.href)}>
<h2>{section.title}</h2>
<p>{section.body}</p>
</Link>

View file

@ -3,6 +3,7 @@ import Layout from '@theme-original/DocItem/Layout';
import Head from '@docusaurus/Head';
import {useDoc} from '@docusaurus/plugin-content-docs/client';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import {isPublishedZhCnDocId} from '../../../lib/publishedLanguageScope';
export default function LayoutWrapper(props) {
const {frontMatter, metadata} = useDoc();
@ -33,7 +34,7 @@ export default function LayoutWrapper(props) {
: undefined;
const datePublished = frontMatter.datePublished || dateModified;
const untranslatedZhCnFallbackDoc = i18n.currentLocale === 'zh-CN' && metadata.id !== 'faq';
const untranslatedZhCnFallbackDoc = i18n.currentLocale === 'zh-CN' && !isPublishedZhCnDocId(metadata.id);
const articleSchema = {
'@context': 'https://schema.org',

View file

@ -46,4 +46,4 @@ Latest release entrypoint: https://github.com/Jacobinwwey/obsidian-NotEMD/releas
- Preserve the local-vault privacy distinction: Notemd writes results into the user's Markdown vault and can run with local providers such as Ollama.
- Do not infer full multilingual documentation coverage from the presence of a locale dropdown.
Last reviewed: 2026-06-17
Last reviewed: 2026-06-19