Supabase 文档应用工程方向指南:apps/docs 的"Docs-only、收缩面、Markdown 一对一保真"三大原则与落地规范
本文基于 Supabase monorepo 中 apps/docs(官方文档站)的工程方向文档 .agents/skills/ask-the-docs/reference/docs-app-direction.md 撰写,系统梳理该文档站"Docs-only 项目、持续收缩表面、Markdown 一对一保真"的三条重构愿景、工作规范,以及对新功能开发的五条硬性约束,并结合 internals/generate-guides-markdown.ts 等源码给出可验证的实现依据。读完本文,你将能在为 Supabase 文档站贡献内容或组件时,正确选择"最小持久足迹"的实现形态,避免踩中联邦内容、搜索、Sentry 等已知脆弱区域。
文档定位:一份写给"要动 apps/docs 的人"的方向声明
docs-app-direction.md 位于 Agent 技能库 ask-the-docs 的参考资料中(文档原文),回答的核心问题是:文档应用往哪里走,以及新工作应该与什么对齐。它明确界定了自己与其他参考文档的分工:
- 当前"已损坏/脆弱系统"的现状 → 见 known-issues.md;
- 功能设计最佳实践 → 见 adding-features.md;
- 既有"接缝(seams)"分布在哪 → 见 app-map.md;
- 联邦文档(federated docs)这一已知负债的完整机制 → 见 federated-docs.md。
理解这份文档的关键在于:它不是架构说明书,而是评审标准——任何触碰 apps/docs 的变更提案,都将被置于下述愿景与规范之下衡量。
重构愿景一:Docs-only project(只做文档的项目)
方向文档给出的第一条长期目标:文档项目的职责应当收缩为"只处理文档",其他功能(如特定 API 能力)应迁移到子项目中。
这一点在当前仓库结构中有直接对应。从源码结构看,apps/docs 虽然名义上是文档应用,但历史上承载了不止渲染内容的职责:apps/docs/package.json 中的 prebuild 链在构建前串起 GraphQL codegen、参考文档 codegen、示例拷贝、联邦内容抓取、Markdown 导出、gz 归档生成等一串步骤(见 package.json 的 prebuild 脚本),其中 codegen:graphql 会经由 resources/ 目录下的脚本维护 /api/graphql 端点——这类"应用内 API"正是方向文档所说的"应迁移到子项目"的功能类型。
对开发者的实际含义:评审时如果一个改动让文档站"顺带"多承担一类非文档职能,它与方向相悖。
重构愿景二:持续的表面收缩(Ongoing surface reduction)
方向文档指出:大量遗留关键代码已被重构,剩余工作的方向是继续减少表面(surface area)而不是增加它。
这里的"表面"在配套的 adding-features.md 中被进一步定义为一种"成本透镜":每个变更要么增加 Reach(用户可见价值),要么增加 Surface(需要持续维护的代码、配置、词汇量)。当设计讨论僵持时,应重新表述为"这个用户可见的改进是否值得其维护成本",若不能自信地回答"值得",应先削减范围而不是为设计辩护。
与这个愿景相呼应的具体约束包括(均来自 adding-features.md 的"选择最小可行形态"一节,按偏好降序):
- 纯内容变更(MDX 编辑、partial、数据文件),无新代码;
- 配置既有组件——给现有原语传新 prop、扩展配置对象;
- 新增"被既有组件消费的数据形态"——一个由已注册 MDX 组件读取的带类型
*.data.ts模块; - 薄的组合组件——小文件编排
<Link>、<GlassPanel>、<Heading>等既有原语,只在 MDX 组件映射表中增加一条注册; - 设计系统中新增原语——最后手段,需要对照
packages/ui/ui-patterns论证必要性。
此外,adding-features.md 还给出了一份"先盘点再动手"的对照表:渲染 MDX 自定义组件先看 features/docs/MdxBase.shared.tsx 的组件映射;组件的 Markdown 导出看 internals/generate-guides-markdown.ts 的 schema 注册表与 internals/markdown-schema/ 下的 handler;可复用内容块看 <$Partial path="..." /> 与 content/_partials/;内部/外部链接逻辑看 lib/internal-links.ts 的 withDocsBasePath / addBaseUrlPrefix;构建步骤看 package.json 的 prebuild / postbuild 链;CI 检查看 .github/workflows/ 下既有工作流——"能扩展现有管道就不新增管道"。
重构愿景三:Markdown 一对一保真(One-to-one markdown fidelity)
这是三条愿景中最技术化的一条:对 generate-guides-markdown 的改进目标是渲染出的 guide 页面与面向 LLM 的 markdown 导出之间形成精确对应。任何在 MDX 中渲染的新组件,都应该能以相同语义序列化为 markdown。
这条愿景不是空话,仓库中已有完整的实现骨架,可以从源码直接验证:
1. 统一的处理函数契约。 generate-guides-markdown.ts 中定义了:
/**
* A handler converts a single MDX component into a markdown string. ...
* Any component not in the schema is treated as `({ children }) => children`,
* i.e. the wrapper is dropped and its children are kept as-is.
*/
type ComponentHandler = (ctx: { props: Props; children: string; node: JsxNode }) => string
每个 handler 接收 { props, children, node }——props 来自 JSX 属性、children 是已递归序列化的子节点 markdown、node 是需要检查 AST 结构时的逃生舱。
2. 按组件命名的 handler 目录。 apps/docs/internals/markdown-schema/ 下目前已有 28 个 handler 文件(Accordion.ts、Admonition.ts、ContentListings.ts、ErrorCodes.ts、Panel.ts、TabPanel.ts 等),文件名与 JSX 元素名严格一致——这正是 adding-features.md 中"文件命名与导出名匹配"约定的落地。所有 handler 在 generate-guides-markdown.ts 顶部统一 import 并汇入 SCHEMA 对象,构成单一注册点。
3. 保真的已知缺口(aspirational, not enforced)。 known-issues.md 明确指出"一对一保真目前是理想目标而非强制约束",并列出三类实际缺口:
- 没有
markdown-schemahandler 的 MDX 组件会被默认 handler 解包(unwrap)为其子节点——从上面源码可以看到默认实现就是const defaultHandler: ComponentHandler = ({ children }) => children,而解包结果可能与实际渲染输出不一致; <$Partial>的递归内联是静默的——缺失的 partial 直接被丢弃(inlinePartials中对读取失败catch后不报错,见 generate-guides-markdown.ts);- 交互式、JSX 表达式密集的组件本质上无法被 markdown 忠实表达。
由此得出方向文档规则 2 的可操作版本:任何改变 guide 渲染方式的改动,要么同步改变其 markdown 序列化方式,要么能清楚说明为什么不用;而"要出现在 markdown 输出中的新组件,必须永远在 internals/markdown-schema/ 添加 handler 并在 generate-guides-markdown.ts 注册"。
4. 双管道共享同一数据注册表。 从源码结构看,同一内容需要渲染两次(MDX 运行时输出 HTML + markdown 导出),apps/docs 的负载级规则是两条管道解引用同一个 ID 键控的数据注册表:MDX 组件通过 id prop 读数据,markdown handler 用同一个 id 读同一份数据,zod schema 作为唯一真源。参考实现是 ContentListings(数据在 apps/docs/data/content-listings/,运行时组件在 apps/docs/components/ContentListings/,markdown handler 在 ContentListings.ts)。
工作规范(Working norms)
方向文档的第二部分给出三条日常工作规范:
1. 在 apps/docs/ 内工作,而不是仓库根目录。 理由更简单:终端命令更短、调试范围更清晰,且多数 docs 命令假设你已经 cd 进应用目录。从 package.json 可以看到印证:lint:mdx(supa-mdx-lint content)、build:guides-markdown、test:local:unwatch 等都是应用内命令,与根目录的 pnpm lint --filter=docs、pnpm typecheck 形成两级体系。
2. 鼓励使用 AI。 官方明确鼓励用 AI 解释复杂或遗留代码段落,并认为其对机械性、非创造性任务有效。这一条的元含义是:这份 ask-the-docs 技能参考集本身就是为 LLM/Agent 消费而写的一手资料。
3. 历史上下文。 对早于当前重构波次的决策,需咨询任职时间更长的维护者;而当前大多数代码有清晰的溯源(provenance)——这也是"表面收缩"策略的直接收益:重构后的代码比遗留代码更容易被新人和 Agent 安全地修改。
对新工作的五条约束(What this means for new work)
这是方向文档的执行核心。任何触碰 apps/docs 的变更提案,应逐条对照:
1. 复用既有接缝,先于新增接缝
"Markdown 导出、MDX 管道、lint、codegen、federated fetch 都是新表面会被挑战的地方。" 这里的"接缝"在 app-map.md 中有完整地图,结合 package.json 的脚本即可落地理解各接缝的真实位置:
| 接缝 | 源码位置 |
|---|---|
| Markdown 导出 | internals/generate-guides-markdown.ts(guides)、internals/generate-reference-markdown.ts(reference),由 build:markdown 串联 |
| MDX 运行时管道 | features/docs/(页面骨架与 MdxBase 渲染器)、components/(MDX 内组件) |
| Lint | supa-mdx-lint(MDX 内容 lint,配置见 supa-mdx-lint.config.toml,规则集见 supa-mdx-lint/ 目录)、ESLint |
| Codegen | codegen:graphql / codegen:references / codegen:examples(prebuild 链) |
| Federated fetch | lib/octokit.ts 的 getGitHubFileContents(),经 GitHub App 凭据(DOCS_GITHUB_APP_*)抓取外部仓库 markdown |
对应动作是"写代码前先盘点"("Inventory before you write"):上面表格中的每一项,若已存在近似方案,默认是扩展它,而不是在旁边另建一个。
2. 把 markdown 保真纳入设计
如前所述,改变 guide 渲染就必须考虑 markdown 序列化。具体新增一个"需要在 markdown 中表达"的组件的标准流程是(来自 app-map.md "The two pipelines"):
- 在
apps/docs/components/写 React 组件; - 在
apps/docs/internals/markdown-schema/<SameName>.ts添加同名 handler; - 在 generate-guides-markdown.ts 的
SCHEMA对象中注册; - 若组件是纯视觉、应当从 markdown 中丢弃,则省略 handler——导出器会自动把未知 JSX 解包为其 children。
第 4 条与源码中的 defaultHandler 行为一一对应,也是"保真缺口"的来源:省略 handler 意味着该组件的"导出语义"隐式等于"其 children",需要开发者确认这与渲染语义一致。
3. 不要构建在坏掉的部分上
方向文档点名三处"in flux"的设施:搜索、Sentry 埋点、联邦链接处理。新特性不应依赖它们当前的形态。known-issues.md 给出了各自的细节依据:
- 搜索:依赖独立的脚本与 embeddings(
scripts/search/generate-embeddings.ts一类),而非整合后的 markdown 文件(build:guides-markdown输出),与主 markdown 生成管道解耦;结论是"不要基于当前搜索基础设施构建新特性,预期它会在重构中被重写"; - Sentry:slug/404 类错误多由爬虫扫描路径、遗留 URL、站外失效入链造成,错误追踪体系相对年轻;结论是"不要在没有本地复现、referrer 核查等佐证前,把嘈杂的 Sentry 信号当作真实 bug 的证据";
- 联邦链接处理:
pageMap是手工维护的——上游改名/删除文件会导致断链,直到维护者更新本仓库的路由文件;每个联邦 section 各自实现urlTransform,逻辑互不相同;wrappers 固定到docs_v*.*.*发布 tag,上游打 tag 失败会直接让构建抛错。
4. 不要扩展技术债务
方向文档列举的三笔既有债务:组件散落(component scatter)、并行管道(parallel pipelines)、自造渲染路径(bespoke render paths)——只能消化,不能追加。仓库内的证据:
- 组件散落:
apps/docs的 React 组件分散在components/、features/docs/、features/ui/、layouts/等多个目录,没有"这就是文档组件"的规范位置;规范做法是"跟随最近的同类先例放置新组件,而不是发明新位置",且不要试图在功能 PR 里顺手重构散落; - 自造渲染路径:SDK 与 CLI 参考页(JavaScript SDK、CLI 等)不能走标准 MDX 管道——历史上曾尝试迁移,但页面长度超出预览环境中 AST 解析器的内存上限而失败,它们改由
spec/驱动、输出到features/docs/generated/**。方向文档因此警告:在解决内存上限之前,不要提议"把所有参考页统一到 MDX 下";同时新参考内容的默认形态应是更小、模块化的页面,而非继续加长。
5. 只重构功能所必需的部分
把无关清理塞进功能 PR 会模糊评审焦点,且"反正也会被砍掉";若清理确实有价值,请另开独立的 refactor PR。配套的执行细则在 adding-features.md 的"Pre-flight"清单中:提交 PR 前确认已盘点既有原语/管道、选择了最小可行形态、共享内容的多管道渲染一致、文件与导出名匹配、无不必要 helper/import/alias、排版委托给共享原语、客户端与 MDX 代码没有 import internals/(internals/ 专供构建期 markdown 生成,双侧共用的函数应放 lib/)、diff 只含与功能相关的改动,且 pnpm format、pnpm typecheck 与相关 lint job 全部通过。评审预期是多轮、逐层加深的,这是通往最小表面设计的路径,而非吹毛求疵。
联邦文档:理解"负债"与"方向"的最佳切面
五条约束中最抽象的"不要构建在坏掉的部分上",以联邦内容为例最具体。结合 federated-docs.md,apps/docs 在构建/请求时从七个外部仓库抓取 markdown 并渲染进原生文档外壳:pg_graphql(/guides/graphql/*)、vecs(/guides/ai/python/*)、setup-cli(CI 章节)、terraform-provider-supabase(部署/terraform 章节)、wrappers(数据库扩展,混合模式,固定到 docs_v*.*.* release tag)、splinter(advisors 动态列表)、agent-skills。抓取层使用 GitHub App 认证(DOCS_GITHUB_APP_ID、DOCS_GITHUB_APP_INSTALLATION_ID、DOCS_GITHUB_APP_PRIVATE_KEY),默认按天重新验证,抓取失败最多重试 5 次。
维护者的立场是明确的"known liability"(已知负债):不要在没有强力理由时新增联邦源,不要把联邦模式扩展到新的内容类型。判断"该联邦还是该复制进 content/guides/"的标准是:若规范文档在别的仓库且高频变化、由那个仓库的维护者拥有内容生命周期,则联邦;若需要编辑控制、一致的语气、链接稳定性,或与 Supabase 产品 UI 和交叉链接紧密耦合,则放进 content/guides/。
验证与自查入口
落实上述规范时,可用的验证命令(本地/根目录两级,见 app-map.md 与 package.json):
| 命令 | 执行位置 | 用途 |
|---|---|---|
pnpm format |
仓库根 | Prettier,开 PR 前必跑 |
pnpm typecheck |
仓库根 | 跨包 TS 检查 |
pnpm lint --filter=docs |
仓库根 | apps/docs 的 ESLint |
pnpm lint:mdx |
apps/docs |
全量 content/ 树的 MDX lint(supa-mdx-lint) |
pnpm build --filter=docs |
仓库根 | 包含 markdown 生成,失败会阻塞发布 |
pnpm test:local:unwatch <path> |
apps/docs |
lib/ 与 data/ schema 的 Vitest 套件,需先本地起 Supabase 并重置数据库 |
结语
apps/docs 的方向文档把一套"反膨胀"工程价值观写成了可执行标准:项目职责向文档收敛、代码表面只做减法、HTML 与 LLM-facing markdown 两条管道必须同步保真。对贡献者而言,最实用的三句话是:新工作先盘点既有接缝再动手;任何改变渲染的改动都要交代 markdown 序列化;搜索、Sentry、联邦链接处理与参考页内存上限是当前不可依赖、不可扩展的区域。这份方向声明与 app-map.md、adding-features.md、known-issues.md、federated-docs.md 共同构成 Supabase 文档站维护的完整参考体系,可作为文档基础设施类项目的治理文档范本。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00