Skip to content

fix(docs): 非 /docs 绝对链接按站点路由校验,并修掉 18 条真实 404 (#3490) - #3506

Merged
yinlianghui merged 2 commits into
mainfrom
claude/issue-3490-site-route-links
Aug 7, 2026
Merged

fix(docs): 非 /docs 绝对链接按站点路由校验,并修掉 18 条真实 404 (#3490)#3506
yinlianghui merged 2 commits into
mainfrom
claude/issue-3490-site-route-links

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes#3490

前提复核(先证后改)

origin/main(f995a45)上逐条复核 issue 的清单,全部属实:非 /docs 绝对 href 共 20 条 = 17 条死链 + 3 条 /img/guide/dashboard-filters/*.png(真实存在于 apps/site/public/,是好的);跑出 collection 的相对链接 1 条。apps/site/app 的路由段枚举结果与 issue 描述一字不差:

/ /api/search /docs/[[...slug]] /llms-full.txt
/llms.txt /playground /llms.mdx/docs/[[...slug]] /og/docs/[...slug]

没有 specprotocolexamples;next.config.mjs 只有 /docs/:path*.mdx 一条重写;无 vercel.json

根因

门禁问的是「磁盘上有没有这个文件」,而唯一该问的是「站点服务不服务这个 URL」。两条豁免由此而来——非 /docs 绝对 href 直接放行,相对 href 只按磁盘路径判定。18 条真实 404 就积在这两条豁免后面,lychee --offline 判绿也是同一个原因。

门禁改动(方向 3,按 PM 裁定收敛)

  1. /docs 绝对 href 按真值来源判定存在性:从 apps/site/app 枚举 App-Router 路由段(含 route group (home)、私有 _foo、动态 [slug]、catch-all [...slug]、可选 catch-all [[...slug]]),外加 apps/site/public 下的静态文件。
  2. 跑出 collection 的相对链接一律拒绝:fumadocs 的 source.resolveHref 只在 docs 页面索引里查,packages/** 不在其中,href 原样落到浏览器——文件真在磁盘上也照样 404。
  3. 每条失败都带上是哪一项检查拒的(relative / docs-route / site-route / escapes-collection),各给各的修复提示。
  4. routeExists() 拿不到路由表时抛错而不是放行绝对 href——静默 true 正是这 18 条积起来的方式。

职责扩大的取舍(脚本头部已写明)

脚本从「只读 content/docs」变成「也读 apps/site」。这是有意购买,不是顺手:

  • 不这么做就根本没有针对这一类的门禁。content/docs 有 16 个失效的相对链接,且 check-doc-links.mjs 对相对链接一律放行 #3479 已经证明「没门禁就会攒」(33 处死引用),这 18 条正是攒在被移除的那条豁免后面。
  • 耦合的是结构不是内容:apps/site/app 只被枚举,从不 import、从不执行,路由变更自动被感知——不会像手维护的前缀白名单那样悄悄漂移。
  • 成本要说明白:给站点加路由现在成了「让某条 docs 链接合法」的前提,于是一个 docs PR 可能因为 apps/site 在它脚下挪动而变红。这个失败是正确的(那条链接确实会 404),但两棵树从此不再互相独立。

没有买的:next.config.mjs 的 rewrite/redirect 不建模(唯一那条 /docs/:path*.mdx 落在更严格的 /docs 分支里,根本走不到这里);扫描面仍然只有 content/docs——examples/** 与根 README.md 按 PM 裁定留作后续单,不在本单扩。

逐条处置表(18 条)

/docs/... 页面有等价物的就指过去,没有的用 GitHub 绝对 URL(content/docs/plugins/*.mdx 的 "Package README" 已有先例)。

#文件:行原 href新目标依据
Aguide/data-source.md:202../../../packages/data-objectstack/README.md#cross-object-atomic-batch-batchtransactionhttps://github.com/.../blob/main/packages/data-objectstack/README.md#cross-object-atomic-batch-batchtransactionA 类:跑出 collection。锚点就在该 README 里,照 plugin-dashboard.mdx:222 的 blob+锚点写法
1guide/component-registry.md:462/spec/component-package.md/docs/guide/plugin-development"Custom Plugin Development" 就是讲从脚手架到发布地造组件/控件
2guide/component-registry.md:466/api/core.../tree/main/packages/core站上没有按包分的 API 路由;包 README 是真实的深度参考
3guide/component-registry.md:467/api/react.../tree/main/packages/react同上
4guide/component-registry.md:468/spec/component.md/docs/api/schema-reference"Schema Type Reference" 即组件元数据参考
5guide/expressions.md:639/protocol/overview/docs/guide/schema-overview"Schema Overview — 所有可用 schema 的总览"
6guide/expressions.md:643/api/core.../tree/main/packages/core表达式求值器在 core 包
7guide/expressions.md:644/protocol/form/docs/plugins/plugin-form
8guide/expressions.md:645/protocol/view/docs/plugins/plugin-view
9guide/fields.md:69/protocolobjectstack/tree/main/packages/specObjectStack Protocol 就是 @objectstack/spec
10guide/objectos-integration.mdx:662/examples/crm.../tree/main/examples/console-starter仓库里没有examples/crm;console-starter 才是接 ObjectStack 后端、插件全开的那个例子
11guide/objectos-integration.mdx:663/examples/kitchen-sink/docs/guide/schema-catalog同样不存在;Schema Catalog 才是「所有 schema 都在这」的那一页
12guide/plugins.md:515/spec/component-package.md/docs/guide/plugin-development#1
13guide/schema-rendering.md:414/protocol/overview/docs/guide/schema-overview#5
14guide/schema-rendering.md:418/spec/schema-rendering/docs/core/schema-renderer渲染器的技术参考页
15guide/schema-rendering.md:419/spec/architecture/docs/guide/architecture"Architecture Overview" 现成就有
16guide/schema-rendering.md:420/api/core.../tree/main/packages/core#2
17guide/schema-rendering.md:421/api/react.../tree/main/packages/react#3

改标签的三处(#2/#3#10#11)是有意为之:链接文案写着 "CRM Application" 却指向 console-starter,本身就是另一个小谎。

同批的 3 条 /img/guide/dashboard-filters/*.png不动——它们本来就对,新检查把它们按 public/ 静态资源判绿。

验证(先预测方向,再跑)

预测:扩展后的门禁在未修改的 content 上应恰好报 18 条(17 site-route + 1 escapes-collection),且 3 条 /img 不得出现。 实跑一致:

Found 18 broken docs links (13 distinct targets):
- [site-route] content/docs/guide/component-registry.md:462 -> /spec/component-package.md
... (17 条 site-route)
- [escapes-collection] content/docs/guide/data-source.md:202 -> ../../../packages/data-objectstack/README.md#...
EXIT=1

修完后 node scripts/check-doc-links.mjsDocs links are valid.(exit 0)。

反向验证。 预测方向为「红」:把两条豁免原样接回去,新增的拒绝类断言应全部转红,而直接对 collectSiteRoutes/siteUrlExists 的单元测试不经过豁免、应保持绿。实跑与预测完全一致——6 failed | 28 passed,红的正是那 6 条拒绝类断言(3 条 site-route、1 条 public 缺失、1 条抛错、1 条 escapes-collection + reason 分类),路由表语义与真实 apps/site 那两个 describe 一条没红。

其他:

命令结果
pnpm exec vitest run scripts/Test Files 14 passed (14) / Tests 216 passed (216)(本文件 22 → 34)
pnpm type-check:scriptsexit 0
pnpm exec eslint (改动的 mjs/ts)无输出
pnpm turbo run build --filter=@object-ui/siteTasks: 28 successful, 28 total — 文档照常编译
node scripts/check-control-bytes.mjsOK(另按 [\x00-\x08\x0b\x0c\x0e-\x1f] 自扫改动文件,无命中)

两处需要 reviewer 注意的范围外沿

  1. content/docs/guide/ci-cd-pipeline.md:该页第 248 行与第 290 行的表格格描述的正是本脚本的行为("resolved against the files actually on disk"、"Internal/docs/... routes"),被本次改动证伪。按 AGENTS.md Add automated testing infrastructure and CI/CD workflows #2(docs-driven)做了最小事实订正,没有做别的改动。
  2. 删掉 check-doc-links.test.ts 第 7 行的 @ts-expect-error:[ci] scripts/ 在零 tsconfig 覆盖内:turbo type-check 从不检查 scripts/__tests__/*.ts——一批门禁 pin 测试自身无类型门 #3494 打开 scripts/allowJs 推断后,fix(docs): check-doc-links 解析相对链接,并修掉它现在能看见的 16 个失效目标 (#3479) #3489 带进来的这条指令变成 unused,而 unused 本身就是 tsc 报错——main 因此在 pnpm type-check:scripts已经是红的(与本 PR 无关,已由 PM 记为 main 的 Type Check job 全红:#3489 与 #3498 的语义冲突,check-doc-links.test.ts@ts-expect-error 在 allowJs 下变成 TS2578 #3504)。按 PM 协调只删这一行,不加替代注释,以便与热修 PR 无论谁先合都能干净合并。

Generated by Claude Code

…s, and fix the 18 live 404s
`check-doc-links.mjs` asked "does a file exist?" when the only question that
matters is "does the site serve this URL?". Two waivers followed from that: a
non-`/docs` absolute href was waved through unchecked, and a relative href was
judged purely as a path on disk. 18 live 404s had collected behind them, all
green under `lychee --offline` for the same reason.
Gate (objectui#3490):
- Absolute non-`/docs` hrefs now resolve against a route truth source
enumerated from `apps/site/app` (App-Router segments, incl. route groups,
dynamic and catch-all forms) plus static files under `apps/site/public`.
- Relative hrefs that resolve outside `content/docs` are rejected: fumadocs
can only resolve inside its page index, so such a link reaches the browser
verbatim and 404s even though the file is really there.
- Each failure is labelled with the check that rejected it, and `routeExists`
throws rather than pass an absolute href when given no route table — a
silent `true` is how these 18 accumulated.
The script now reads `apps/site` as well as `content/docs`. That widens its
responsibility and couples a `content/` gate to an `apps/` layout; the header
argues the trade-off, including the cost (a docs PR can go red because the
site moved under it — correct, but real).
Content: all 18 links repointed at real targets — `/docs/...` pages where an
equivalent exists, otherwise absolute GitHub URLs (the "Package README" form
already used across `content/docs/plugins/*.mdx`). The three
`/img/guide/dashboard-filters/*.png` links in the same batch were already
good and are accepted by the new check.
Also drops the now-stale `@ts-expect-error` in the pin test: #3494 turned on
`allowJs` inference for `scripts/`, which made the directive #3489 carried
unused — and an unused one is itself a tsc error, so `main` was red on
`pnpm type-check:scripts` (objectui#3504).
@vercel

vercelBot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredAug 7, 2026 2:28am

Request Review

…e-route-links
# Conflicts:
#	scripts/__tests__/check-doc-links.test.ts
@yinlianghui
yinlianghui added this pull request to the merge queueAug 7, 2026
Merged via the queue into main with commit a2c8f2aAug 7, 2026
17 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3490-site-route-links branch August 7, 2026 03:11
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Aug 10, 2026
…bjectstack-ai#3509)
`examples/todo` and `examples/crm` have never existed on `main` (examples/
holds README.md, byo-backend-console, console-starter, hello-world,
schema-catalog), so both "Next Steps" links were live GitHub 404s.
Retarget each to the page that actually answers the bullet, and retitle the
label to name that page honestly rather than dressing it up as an example app
that does not exist (precedent: objectstack-ai#3506's "Example: CRM Application" ->
"Example: ObjectStack Console Starter").
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Aug 10, 2026
* docs(examples): add a README for console-starter
examples/console-starter was the only example directory without a README,
and PR objectstack-ai#3506 retargets a docs link at its GitHub tree page. Content is
measured from src/ and the configs: the app-shell building blocks App.tsx
composes, the workspace packages that make `pnpm -w build` a hard
prerequisite, the VITE_SERVER_URL backend requirement, and what the app
actually does with no backend listening.
Fixesobjectstack-ai#3520
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
* docs(examples): drop the self-invalidating file count from the console-starter README
"This directory holds ten files" stops being true the moment this README
lands in it. State the fact that matters instead: no schema JSON here at all.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
---------
Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Aug 10, 2026
…bjectstack-ai#3571)
三条链接的目标在磁盘上都不存在,且这两个文件不在任何门禁的扫描面内
(check-doc-links 的 SCAN_ROOTS 是 content/docs + examples + 根 README;
lychee 扫 content/docs、docs、README.md),所以一直静默失效。
- CONTRIBUTING.md:153 `./docs/spec/architecture.md`(`docs/spec/` 目录根本不存在)
→ `./content/docs/guide/architecture.md`。未按 issue 建议指向 `docs/ARCHITECTURE.md`:
该文件的 H1 是 "Console Streamlining - Architecture Guide",讲的是 app-shell/providers
那次重构的迁移路径,而链接所在的 "Architecture Overview" 一节讲的是 monorepo 包结构与
设计原则 —— `content/docs/guide/architecture.md`(标题即 "Architecture Overview",
含 Package Structure / Core Philosophy 两节)才是这句 "for details" 真正承诺的内容。
链接文字同时改成目标文档的真实标题,避免用旧标签包装另一个目标。
- CONTRIBUTING.md:469 `./docs/README.md` → 删除整句。仓库里没有、也从未有过
"Documentation Guide" 这份文档;它承诺的细节就在它所处的 Documentation 一节里
(Writing Documentation / Documentation Guidelines / Link Conventions / Validating Links),
前一句也已自成完整陈述。按 objectstack-ai#3506/objectstack-ai#3509 的先例,如实删除而不是改指别的文档。
- ROADMAP.md:1892 扩展名错 `.mdx` → `.md`。真实文件是
`content/docs/guide/plugin-development.md`;保持相对文件链接的写法,与同一列表里
`./CONTRIBUTING.md`、`./QUICK_REFERENCE.md` 一致。
本 PR 只做第 1 步。issue 的第 2 步(把这两个文件与 `docs/**` 纳入 SCAN_ROOTS)
刻意留下,不碰 scripts/check-doc-links.mjs。
Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants

@yinlianghui@claude