Skip to content

feat(docs): 文档页底部显示最近 5 次更新(GitHub API)#279

Merged
longsizhuo merged 5 commits intomainfrom
feature/doc-history
Apr 14, 2026
Merged

feat(docs): 文档页底部显示最近 5 次更新(GitHub API)#279
longsizhuo merged 5 commits intomainfrom
feature/doc-history

Conversation

@longsizhuo
Copy link
Copy Markdown
Member

Summary

  • 新增 `/api/docs/history` Next.js 路由,服务端用 GITHUB_TOKEN 调 GitHub REST `repos/.../commits?path=...`,避免浏览器限流
  • 新增 `DocHistoryPanel` 组件挂在文档页(Giscus 与 License 之间),展示最近 5 条更新
  • Newspaper 风格:头像 + 作者 + 相对时间 + message 截断,点击跳 GitHub commit 详情
  • 双层缓存:Next `revalidate: 3600` + `Cache-Control: s-maxage=3600`
  • 只做展示,不做 inline diff(后续可加 `/api/docs/diff` + 渲染库)

Test plan

  • `pnpm lint && pnpm typecheck && pnpm test` 全过
  • curl 真实返回 3 条 commit(qwenvl/index.mdx)
  • Reviewer: 确认 `page.file.path` 字段名在 fumadocs 下可用(若不对会在客户端看到 404)

Copilot AI review requested due to automatic review settings April 14, 2026 18:51
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 14, 2026

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

Project Deployment Actions Updated (UTC)
involutionhell-github-io Ready Ready Preview, Comment Apr 14, 2026 7:53pm
website-preview Ready Ready Preview, Comment Apr 14, 2026 7:53pm

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a “recent updates” panel to documentation pages by introducing a server-side API route that queries GitHub commit history for a doc file, then rendering the latest 5 commits in a client component.

Changes:

  • Add /api/docs/history route that fetches recent commits for a given file path via GitHub REST API, with caching headers + revalidate.
  • Add DocHistoryPanel client component to display recent commits (avatar/author/relative time/message).
  • Mount the panel on doc pages between Giscus comments and the license notice.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.

File Description
app/docs/[...slug]/page.tsx Mounts DocHistoryPanel on the docs page and passes the doc file path.
app/components/DocHistoryPanel.tsx New client UI to fetch and render the latest commit history list.
app/api/docs/history/route.ts New API endpoint that queries GitHub commits by path and returns normalized history items with caching.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +33 to +41
const { searchParams } = new URL(req.url);
const path = searchParams.get("path");

if (!path) {
return NextResponse.json(
{ success: false, error: "缺少 path 参数" },
{ status: 400 },
);
}
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

path 直接来自 query string,目前只做了“是否为空”的校验就用于拼接 GitHub API 请求。建议至少限制为文档目录内的相对路径(例如只允许以 app/docs/ 或 docsBase 开头,拒绝包含 ..、反斜杠等),避免用服务端的 GitHub token 被动暴露任意仓库文件的 commit 信息。

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +52
const apiUrl = `https://api.github.com/repos/InvolutionHell/involutionhell/commits?path=${encodeURIComponent(path)}&per_page=5`;

Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里把客户端传来的 path 直接传给 commits?path=...,但页面侧传入的是 page.file.path(在 getDocContributorsByPath 里会再统一前缀成 app/docs/...)。如果 path 是 docs 相对路径(不含 app/docs/),GitHub 会查不到文件导致结果为空。建议在服务端统一把 path 规范化成 repo-root 相对路径(复用 lib/github 的 docsBase/normalizeDocsPath),并避免在此处硬编码 owner/repo。

Copilot uses AI. Check for mistakes.
Comment on lines +71 to +76
if (res.status === 403) {
return NextResponse.json(
{ success: false, error: "GitHub API 限流,请稍后重试" },
{ status: 429 },
);
}
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

将 GitHub 的所有 403 都映射为 429(限流)不准确:403 也可能是 token 权限不足/仓库不可访问等。建议读取响应体 message 或根据 x-ratelimit-remaining/x-ratelimit-reset 判断是否真的触发 rate limit;否则返回更贴近真实原因的 502/401/403 错误。

Copilot uses AI. Check for mistakes.
Comment on lines +91 to +92
avatarUrl:
c.author?.avatar_url ?? `https://github.com/${c.commit.author.name}.png`,
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c.author 为 null 时,这里用 c.commit.author.namehttps://github.com/<name>.png 作为头像兜底;但 commit.author.name 通常是展示名而不是 GitHub login(可能包含空格/中文),很容易 404。建议在 author 缺失时返回空头像/占位图,或把 avatarUrl 设为固定的默认头像资源,并把 authorLogin 明确为展示名。

Suggested change
avatarUrl:
c.author?.avatar_url ?? `https://github.com/${c.commit.author.name}.png`,
avatarUrl: c.author?.avatar_url ?? "",

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +6
import type { HistoryItem } from "@/app/api/docs/history/route";

Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DocHistoryPanel 是 client component,但这里从 app/api/.../route 里直接导入类型会把 UI 层和 Route Handler 文件耦合在一起(后续 route 文件引入 node-only 依赖时更容易踩到边界问题)。建议把 HistoryItem 提到独立的共享类型模块(例如 app/types/docs-history.ts / lib/types),client 与 server 都从该模块引用。

Suggested change
import type { HistoryItem } from "@/app/api/docs/history/route";
interface HistoryItem {
userName: string;
userImage?: string | null;
timestamp: string;
}

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +56
fetch(`/api/docs/history?path=${encodeURIComponent(path)}`)
.then((r) => r.json())
.then((json) => {
if (cancelled) return;
if (json.success) {
setItems(json.data);
} else {
setError(json.error ?? "无法加载历史");
}
})
.catch(() => {
if (!cancelled) setError("无法加载历史");
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

当前 erroritems 的渲染条件是独立的:当 path 变化触发重新 fetch 时,如果请求失败会 setError(...) 但不会清空旧的 items,从而出现“错误提示 + 旧历史列表”同时显示的情况。建议在 effect 开始时先重置 setItems(null); setError(null);,并在渲染历史列表时额外要求 error === null(或在出错时清空 items)。

Suggested change
fetch(`/api/docs/history?path=${encodeURIComponent(path)}`)
.then((r) => r.json())
.then((json) => {
if (cancelled) return;
if (json.success) {
setItems(json.data);
} else {
setError(json.error ?? "无法加载历史");
}
})
.catch(() => {
if (!cancelled) setError("无法加载历史");
setItems(null);
setError(null);
fetch(`/api/docs/history?path=${encodeURIComponent(path)}`)
.then((r) => r.json())
.then((json) => {
if (cancelled) return;
if (json.success) {
setItems(json.data);
setError(null);
} else {
setItems(null);
setError(json.error ?? "无法加载历史");
}
})
.catch(() => {
if (!cancelled) {
setItems(null);
setError("无法加载历史");
}

Copilot uses AI. Check for mistakes.
Copilot CR #279:
- route: 新增 normalizeDocsPath 做路径校验,只允许 app/docs/ 下相对路径,
  拒绝 ..、反斜杠、null 字节,消除 SSRF 风险
- route: 接受 'docs/...' 和 '/docs/...' 形式,统一补成仓库根相对 'app/docs/...'
- route: 403 用 x-ratelimit-remaining 区分限流 vs 权限不足,401 单独处理
- route: author 为 null 时 avatarUrl 返回空串而不是拼 github.com/<name>.png 容易 404
- 类型 HistoryItem 抽到 app/types/docs-history.ts,解耦 client 组件与 route handler
- DocHistoryPanel: path 变化先清空 items/error 避免 '错误 + 旧列表' 同时显示
- DocHistoryPanel: 空头像用 data URI 占位防 Image 报错
接着前一 commit 补落下的 route.ts 改动(SSRF 防护 + 403 区分限流/权限不足 + 401 单独处理 + 头像兜底)
…int 错误

PR #279 build 挂在 lint:
> 50 |     setItems(null);
     |     ^ Avoid calling setState() directly within an effect

把 items / error / loading 合并成 discriminated union + useReducer,
effect 里只 dispatch 一次,规避 lint 规则。
副收益:三种状态天然互斥,不会出现'错误提示 + 旧列表'并存的情况
(这正是上个 CR 试图用两次 setState 解决的问题,现在用状态机更干净)。
@longsizhuo longsizhuo merged commit 7311bc3 into main Apr 14, 2026
8 checks passed
@longsizhuo longsizhuo deleted the feature/doc-history branch April 14, 2026 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants