Skip to content

fix(docs): run the awaited fumadocs-mdx write after next typegen in type-check - #313

Merged
hotlong merged 8 commits into
mainfrom
claude/pm-dispatch-objectos-ju9td1
Oct 6, 2026
Merged

hotlong merged 8 commits into
mainfrom
claude/pm-dispatch-objectos-ju9td1

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #311

What changed

apps/docs type-check now runs next typegen && fumadocs-mdx && tsc --noEmit. Before, it ran fumadocs-mdx && next typegen && tsc --noEmit.

The awaited fumadocs-mdx CLI is now the last process to write .source/* before tsc reads it.

AGENTS.md is not in this PR. Its Commands line documents the old order. AGENTS.md is governed, human-merge only, so the one-line update (staged locally as 4afa327) goes with the seat's pending governed AGENTS.md change. That keeps this fix from waiting on a maintainer merge.

Why

The root cause in #311 checks out against the installed fumadocs-mdx 15.0.7 and next 16.2.6:

  • dist/next/index.js:14-20: createMDX() calls init(isDev, core) and does not await it. init runs initOrReload (:84-88, :120), which ends in core.emit({ write: true }).
  • dist/core-DlDe_Eze.js:232-236: emit rewrites every .source/*.ts with fs.writeFile, which truncates the file first.
  • next/dist/bin/next:170-172: typegen runs .then(()=>process.exit(0)), which kills a pending write mid-flight.
  • fumadocs-mdx/dist/bin.js:4-11: the CLI awaits postInstall, which awaits emit. It never loads next.config.mjs; it only checks that the file exists.

next typegen does not read .source. With .source deleted it still exits 0 and prints ✓ Types generated successfully.

Red, then green

The harness is a throwaway NODE_OPTIONS=--require preload, not committed and not in node_modules. It makes .source/*.ts writes truncate at once and land after a delay, which widens the window. With no delay set, it does nothing. The red runs are on 5d2f837. The green runs are on tree e06625d, which is this PR's tree (the commit message was rewritten afterwards; the tree did not change).

Run Order Harness Result
deterministic old fixed 3000 ms 3/3 red, TS2306
loop old random 0-1500 ms 20/20 red, TS2306
loop old none, cold .source 25/25 green; the natural race did not fire locally
deterministic new fixed 3000 ms 3/3 green
loop new random 0-1500 ms 20/20 green
loop new none, cold .source 20/20 green

In all 20 green jitter runs, typegen still exited with writes pending, so the race fired every time and the CLI absorbed it.

A red run, on the old order, at 3000 ms:

Generating route types...
[race-harness pid=11956 bin/next typegen] truncated .source/server.ts, writing after 3000ms
✓ Types generated successfully
[race-harness pid=11956 bin/next typegen] EXIT code=0 with 3 .source write(s) still pending: .source/server.ts, .source/dynamic.ts, .source/browser.ts
lib/source.ts(1,22): error TS2306: File '/home/user/objectos-issue-311/apps/docs/.source/server.ts' is not a module.

This shows the same signature as CI run 37478051451: one [MDX] generated files line instead of two, and server.ts at 0 bytes after the run.

Other entry points

  • postinstall (fumadocs-mdx): not exposed. The CLI awaits its writes. Under the 3000 ms harness it printed [MDX] generated files in 3052ms and left server.ts at its full 99755 bytes.
  • next build: not exposed in the same way. The process outlives the write, so no truncated .source is left behind. It does have a related window inside the one process: Turbopack can read .source/server.ts while createMDX's rewrite is truncating it. Measured with the harness, build passed at 50 ms and 200 ms delays and failed at 1000 ms and 5000 ms. The failure is loud (Export docs doesn't exist in target module), not a bad artifact. On a natural build, [MDX] generated files prints before Creating an optimized production build. See Acceptance notes.
  • next dev: not exposed in the same way. The process is long-lived and init(dev=true) goes on to start the watcher. In a probe with a 20 s delay, a request sent while the files were truncated returned 200. The first compile took 104 s, so it outlasted the window, and no error was logged.
  • preview / deploy: these run next build through opennextjs-cloudflare build, so next build covers them. CI packages with --skipNextBuild.

Gates

All ran on 4afa327, with exit codes captured before any pipe. The pushed head 5e666c3 carries the same apps/docs/package.json change and drops only the AGENTS.md line, which no gate reads; CI re-runs every gate on it.

  • node apps/docs/scripts/gen-zh-hant.mjs --check: exit 0, ✓ zh-Hant: 60 generated file(s) match the zh-Hans sources byte for byte.
  • pnpm turbo run type-check --continue --force: exit 0, Tasks: 1 successful, 1 total
  • NEXT_PRIVATE_STANDALONE=true pnpm turbo run build --force: exit 0, Tasks: 1 successful, 1 total
  • pnpm turbo run test --force: exit 0, Tasks: 1 successful, 1 total
  • check-translation-ownership.mjs --actor hotlong --files changed.txt, with the workflow's --name-status --no-renames list: exit 0. It also exits 0 with TRANSLATION_BOT_LOGIN set: ✓ 2 file(s) changed, no translation artifacts touched. (On the pushed head the diff is 1 file.)
  • check-node-floor.mjs --self-test and the check itself: exit 0. They ran because they read apps/docs/package.json.
  • Not run locally, left to CI: Locale surface, Positioning and Search answers every locale. This diff changes no content and no route.

Acceptance notes

  • The build-side window is not closed here. The only fix inside this repo is _FUMADOCS_MDX=1, fumadocs-mdx's private recursion guard, which would stop createMDX from rewriting during next build. That depends on an internal variable, so it is left as a decision. The real fix belongs upstream: createMDX should await init, or emit should write to a temp file and rename it. A report from public facts is welcome. Carrier: none.
  • The build log shows Failed to load dynamic font ... self-signed certificate in certificate chain. That is the sandbox's TLS proxy blocking OG font fetches. The build still exits 0.

维护者速读(草稿)

  • 改了什么:文档站 type-check 的三步顺序调换,next typegen 先跑,fumadocs-mdx 后跑。AGENTS.md 的对应说明属受管文件,另走维护者合并,不在本 PR。
  • 为什么改:main 上一次 CI 红在类型检查,原因是 next typegen 退出时 fumadocs 的后台写入刚清空文件还没写完;红了的 main 不部署。
  • 风险与代价(含回滚):只改脚本顺序,无产物差异;回滚即把顺序换回(会重新引入偶发红)。next build 内的同源窗口未关,见 Acceptance notes。
  • 席位意见:接受。build 侧窗口按 A 处理:保留为已知残余,不依赖内部变量,不打补丁。
  • 你要做的:无;如需关闭 build 侧窗口,需决定是否依赖 fumadocs 内部变量。

Generated by Claude Code

objectstack-fleet Bot and others added 8 commits October 6, 2026 10:36
…ds or fewer

Adds a `seoTitle:` frontmatter line to every English page whose built
`<title>` read as one or two words plus the ` | ObjectOS` suffix, so the
tab/result title carries the terms a reader searches for while the H1,
sidebar and breadcrumb keep the short noun (#166's mechanism).

- 67 pages: each gains exactly one line; no `title`, `description` or
  body line changes.
- Every title leads with the page's own specific term, lifted from its
  description and headings, and renders at 52–60 characters including the
  suffix (measured on the built HTML).
- The three duplicated titles (Approvals, Dashboards, Notifications —
  each used by a build/configure page and a use page) are now distinct.
- English only. Locale siblings are translation artifacts that a
  non-translator commit may not modify (AGENTS.md, Translation workflow;
  check-translation-ownership.mjs); the translation pass carries the new
  key over, and the output report lists the 163 siblings now missing it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…AQ headings, title-weighted per-locale search, consistency pass

- Light-mode muted foreground to hsl(0 0% 40%); code comments recoloured in
  both shiki themes.
- Tables get an always-drawn scrollbar and a scroll-driven trailing fade.
- FAQ and License FAQ questions become headings.
- /api/search builds one locale's index on that locale's first search and
  weights title > heading > text; check-search-locales gains own-title-buried;
  smoke-docs asks /api/search in every locale with a nonce control.
- Consistency: one data-residency table, one license-validation sentence,
  ObjectSchema.create, "license" spelling, Configure title, glossary order plus
  AI seat and Position, logo to the docs home, a translated llms.txt example,
  three unsourced configure/ai claims removed (with their locale siblings),
  release-following lines pointed at a populated feed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…comments

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ix apiMethods primitives

The Views page declared views inside a defineObject call under an
object-level `view` key. ObjectStack provides no defineObject, and
ObjectSchema.create rejects `view` as an unknown key. Its views are a
defineView container ({ list, listViews, form, formViews }, each view
bound through `data`) registered on the stack with `views: [...]`, which
both samples now show; both parse with @objectstack/spec 17.6.0.

The REST API page's allowed apiMethods values drop the eight retired ones
the spec strips at parse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ype-check

`next typegen` loads next.config.mjs, and fumadocs-mdx 15.0.7's createMDX()
starts init() without awaiting it (dist/next/index.js:14-20). init rewrites
every .source/*.ts with fs.writeFile (dist/core-DlDe_Eze.js:232-236), which
truncates first. typegen ends in process.exit(0), so it can exit inside that
window and leave .source/server.ts empty for tsc: TS2306, CI run 37478051451.

Running the fumadocs-mdx CLI after typegen makes the CLI, which awaits its
writes, the last writer before tsc. typegen does not read .source.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants