Add content check workflow#2
Conversation
|
Codex Review: Here are some suggestions. https://github.com/InvolutionHell/involutionhell.github.io/blob/7bc2b45f5284ddfb3fdb008014a0e96926a4a4c0/.github/workflows/content-check.yml#L34-L35 The new content-check job runs Reply with About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you open a pull request for review, mark a draft as ready, or comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex fix this CI failure" or "@codex address that feedback". |
There was a problem hiding this comment.
Codex Review: Here are some suggestions.
Reply with @codex fix comments to fix any unresolved comments.
About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you open a pull request for review, mark a draft as ready, or comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex fix this CI failure" or "@codex address that feedback".
| - run: pnpm install --frozen-lockfile | ||
| - run: pnpm run check:content |
There was a problem hiding this comment.
[P1] Avoid CI step that always crashes
The new content-check job runs pnpm run check:content, which currently crashes in this repo with TypeError: The "code" argument must be of type number. Received an instance of Object from the Contentlayer CLI. Because the command exits with an exception even after generating documents, this workflow will fail for every pull request that touches docs regardless of content validity. CI will therefore block all such PRs until the underlying command is adjusted (e.g., by upgrading Contentlayer or invoking a different check that exits cleanly).
Useful? React with 👍 / 👎.
- UserPaperItem 加 itemKey 字段(可选) - page.tsx 提取所有 itemKey 批量调 /api/user-center/zotero/items, Zotero 拉到的元信息作为主数据源,手填字段作为离线 fallback - EditProfileForm papers 区增加 itemKey 输入框(提示可选),保存条件放宽为 itemKey 或 title 至少有一个 - 完全向后兼容:历史 pinned_papers(只有 title/authors 的)照常渲染 配套后端 commit involutionhell-backend 1fb697f(Zotero API 代理 + Caffeine 1h) V2 全部完成:#1 编辑页 / #2 docs/history 迁 Java / #3 活跃度热力图 / #4 关注系统 / #5 GitHub repos 同步 / #6 保留 analyticsEvent / #7 Zotero itemKey
* feat(profile): 个人主页 /u/[username] SSR + Bento 布局
MVP 范围(UX + PM 对齐后砍刀):
- 路由:/u/[username] SSR(ISR 300s)
- 数据源:后端 GET /api/user-center/profile/{username}
- 布局:12-col bento,左 col-span-5 Identity,右 col-span-7 小卡网格
- 间距:gap-8 统一,用户明确要求松散
- 三种卡片:PROJ / PAPER / DOC,前两者读 preferences,DOC 读 leaderboard
- hover 展开用 max-height 原地展开;mobile 改 tap toggle
砍掉(V2):
- sticky 左大块(滚动竞争)
- 绝对定位浮层(移动端 mess)
- 编辑页(复用后端 PATCH /preferences)
- Zotero 实时关联(pinned_papers 直接存 title/author/year)
配套后端改动见 involutionhell-backend main commit 8efae54
(新增 GET /api/user-center/profile/{username} + SaToken 白名单)
同步新增:
- docs/architecture/frontend-backend-separation.md 固化前后端分离约定
* feat(profile): 入口收口 + URL 支持 github_id 双路径
- UserMenu 登录态增加"我的主页"链接(基于 githubId)
- AuthNav 透传 githubId 到 UserMenu
- ContributorRow 弹窗加"VIEW DOSSIER"跳站内个人主页,GitHub 外链保留
- page.tsx 改按 user.githubId 从 leaderboard JSON 匹配贡献数据
(之前按 name 字符串匹配踩坑:leaderboard.name="longsizhuo" vs user.username="github_114939201")
- 新增 EditLinkIfOwner 客户端小组件,只在访问者 == 主页主人时渲染编辑入口
* feat(profile): 编辑页 /u/[username]/edit 支持 bio/tagline/links/projects/papers
- 新 page.tsx 壳 + EditProfileForm 客户端表单
- useAuth 读当前用户,URL username 与 githubId / username 都不匹配时显示"只能编辑自己"
- GET /api/user-center/preferences 拉现有值 → 表单 state
- 提交走 PATCH /api/user-center/preferences(后端合并 JSONB 顶层 key 保留其他)
- RepeatableList 通用泛型组件支持 links/projects/papers 增删
- editorial 风格:border 无圆角 / SEC 编号 / 硬阴影 submit 按钮
* refactor(api): /api/docs/history 迁到 Java 后端
删 Next API Route,next.config 加 rewrite 把 /api/docs/history 透传到 Java,
Vercel 不再跑 Node function 去调 GitHub API。后端带 Caffeine 10min 缓存 +
GITHUB_TOKEN。配套后端 commit 见 involutionhell-backend#main。
* perf(build): leaderboard 用 git log 反推 login + 加 dailyCounts 字段
- 从 noreply 邮箱(1234567+alice@users.noreply.github.com) 离线提取 id→login 映射,
14/21 命中直接拿到 login,剩下 7 才走 GitHub API。规避限流风险。
- DB 聚合时同时按日分桶贡献次数写进 dailyCounts: { "YYYY-MM-DD": count },
供前端活跃度热力图渲染,零运行时 DB 查询。
generated/site-leaderboard.json 一并重新生成带 dailyCounts。
* feat(profile): 活跃度热力图 52 周 GitHub 风格
- 新增 ActivityHeatmap server component,无 JS
- 52 列 × 7 行小格子,色阶分 5 档(0 / 1-2 / 3-5 / 6-10 / 10+),硬红(#CC0000)系列
- 数据走 leaderboard.json 的 dailyCounts(零运行时 DB 查询,和 docs git-based 一致)
- 月份刻度 + 周几标签(周一/四/六)
- page.tsx Bento grid 下方独立一行展示,仅当有贡献数据时渲染
* feat(follows): 个人主页关注按钮 + 粉丝/关注数
- FollowButton 客户端组件:拉 /api/user-center/follows/stats 填初始值,
点击走 POST/DELETE /api/user-center/follows/{id},乐观更新失败回滚
- 自己访问自己主页只显示统计,不显示按钮
- 匿名用户可见数字,按钮显示"登录后关注"置灰
- prisma/schema.prisma 加 user_follows model 同步(DB 已在 Neon 手动建表)
配套后端端点见 involutionhell-backend#main 30daf9d
* feat(profile): GitHub repos 小卡区 SEC. REPOS · 006
- GithubRepos server component,fetch /api/user-center/github/repos/{identifier}
- 2 列 grid,每卡显示 name / stars / description / language / 更新时间
- 数据为空时返回 null 不渲染 section
配套后端 commit involutionhell-backend b6b48b2(Caffeine 1h,不需要 OAuth scope 扩展)
* feat(profile): pinned_papers 支持 Zotero itemKey 运行时填充
- UserPaperItem 加 itemKey 字段(可选)
- page.tsx 提取所有 itemKey 批量调 /api/user-center/zotero/items,
Zotero 拉到的元信息作为主数据源,手填字段作为离线 fallback
- EditProfileForm papers 区增加 itemKey 输入框(提示可选),保存条件放宽为 itemKey 或 title 至少有一个
- 完全向后兼容:历史 pinned_papers(只有 title/authors 的)照常渲染
配套后端 commit involutionhell-backend 1fb697f(Zotero API 代理 + Caffeine 1h)
V2 全部完成:#1 编辑页 / #2 docs/history 迁 Java / #3 活跃度热力图 /
#4 关注系统 / #5 GitHub repos 同步 / #6 保留 analyticsEvent / #7 Zotero itemKey
* fix(ts): 修复 typecheck 两处报错
1. leaderboard as Row[] → leaderboard as unknown as Row[]
JSON 字面量的 dailyCounts 各自是不同 literal 类型,和 Row 的 Record<string, number>
索引签名不兼容,CI tsc --noEmit 报 TS2352。先经 unknown 绕开。
2. ProfileCard title prop 必填 string,但 UserPaperItem 引入 itemKey 后 title 变可选。
兜底为 p.title || p.itemKey || "(untitled paper)"。
* fix(review): 修复 Copilot PR CR 8 条
安全修复(P0):
- fetchProfile 只在后端真返 404 或 success=false 时 notFound();其他非 2xx 抛错进 error boundary
(避免后端故障伪装成"用户不存在")
- links/projects/papers 的 URL 在渲染前过 sanitizeExternalUrl 白名单
(仅 http/https/mailto + 相对路径,拦 javascript:/data: 等 XSS 向量)
- ProfileCard 内部再加 safeHref 二次防御
UX / a11y(P1):
- ProfileCard 的 click toggle 用 matchMedia('(hover: none)') 限定触屏设备
(桌面端仍走 group-hover,避免"离开 hover 后仍保持展开"幽灵状态)
- 补 role="button" / tabIndex=0 / onKeyDown(Enter|Space) / aria-expanded,
键盘 / 读屏用户可用;只在有 detail 时才挂这些属性
文档(P2):
- docs/architecture/frontend-backend-separation.md
环境变量章节改成"生产禁 fallback,开发态过渡允许",与现状(next.config.mjs/upload 仍用 fallback)自洽
- fetchProfile 注释说"直连后端(BACKEND_URL)",不再错写"走 rewrite"
* refactor(profile): 调整 profile 页版式,避免文档多的用户下滑过久
- 右侧 Bento 小卡区只保留 projects / papers,DOC 卡片抽出去
- 活跃度热力图从页底提到 Bento 之后立即显示(视觉重点前置)
- 新增 SEC. DOCS 独立 section 放页尾,紧凑列表(每行 ~48px 而非 180px 小卡)
- 默认只显示前 10 篇,超过的用 <details> 折叠"展开剩余 N 篇"
旧版顺序:Identity(bio/stats)+DOC 卡(8 条) → Heatmap → Repos
新版顺序:Identity(bio/stats)+projects/papers → Heatmap → Repos → Docs 列表(折叠)
文档特别多的用户(本作者 44 条)现在热力图和 repos 一屏可见,docs 列表放最后折叠。
* refactor(header): 更新顶部导航以对齐当前信息架构
- 去掉"首页":BrandMark 点击已回首页,避免冗余
- 去掉"特点":旧 Features 组件 2026-04 重构时删除,/#features 已失效
- 新增"文档"→ /docs 和"排行榜"→ /rank,把主要产品路由提到顶
- 保留"社区"→ /#community(DispatchNetwork bar)
- "联系我们"缩写为"联系"→ /#contact(Footer 还在)
* fix(profile-edit): 改善编辑页文案,让普通用户知道每个字段填什么
用户反馈"编辑个人主页这地方不知道 edit 什么,不懂 papers 是什么意思"。
根因是字段名是工程师视角(bio/tagline/pinned_papers),普通用户不理解。
改动:
- 编辑页顶部加一段"About this page"总说明:填的东西去哪里显示、全都可选
- 每个 Section 从只有 SEC 编号,扩成「SEC 编号 + 中文大标题 + 一句话描述」
- PAPERS 块重命名为"最近在读 / 推荐的论文"(papers 太学术,降低理解门槛)
- placeholder 从抽象("项目名")换成具体示例("involutionhell.com")
- IDENTITY/LINKS/PROJECTS/PAPERS 4 块都加 heading + description 说明作用
主页空态文案:
- 旧:"该用户还没有填写 projects / papers"(用户反馈看不懂)
- 新:"Ta 还没填个人项目和最近在读的论文" + 小字补充"仍会显示 GitHub repos 和文档贡献"
* feat(i18n): 个人主页相关 UI 接入真 i18n(cookie 驱动 zh/en 切换)
基建:
- lib/i18n/messages.ts:扁平 key 字典,zh/en 同结构,{param} 占位填充
- lib/i18n/server.ts:server-only,getServerLocale + getServerT
- lib/i18n/client.tsx:LocaleProvider + useT hook
- app/layout.tsx:LocaleProvider 包在 ThemeProvider 外,locale 服务端读 cookie
注入客户端 Context,避免 SSR/CSR 水合抖动
接入(全部 profile 相关 UI):
- page.tsx:dossier / stats / empty state / docs 列表全部走 t(key)
- ActivityHeatmap:改 async server component,月份 Jan..Dec 走 activity.month.{1..12}
- GithubRepos:heading / subtitle / count 走 t
- FollowButton / EditLinkIfOwner / ProfileCard:client hook useT
- edit/page.tsx + EditProfileForm:全套(intro / 4 个 section heading+description
+ placeholders + CTA + auth gate + RepeatableList 增删按钮文字)
- RepeatableList 新增 addLabel/removeLabel props,调用方翻译好后传入
验证:
- pnpm run typecheck 通过
- locale=zh → "活跃度/文档贡献/粉丝/积分/贡献过的文档/GitHub 仓库"
- locale=en → "Activity/Docs/Followers/Points/Docs Contributed/GitHub Repositories"
Copilot 提了 6 条 + CodeQL 2 条正则告警,全部修复: **lib/rate-limit.ts** - 文档头 usage 示例 API 改对(CR #1) - getClientIp 防伪造(CR #2,**安全修复**): 优先 x-real-ip(Vercel 等 CDN 写的是可信值);降级用 XFF 时取**最后一个** 而非首个,避免客户端伪造 `x-forwarded-for: fakeip` 绕过 rate-limit - Upstash 缺失 warn 改用 module-scoped flag,整个实例生命周期只打一次, 不再按 NODE_ENV 区分 —— dev 也得看到提示(CR #3) **app/api/chat/route.ts** - POST 入口预读 body 判定 hasImage,true 时触发 5 req/60s 严限流; 预读失败不阻塞,保持原有容错(CR #4) - 新增 messagesHaveImage helper:识别 type=image / image_url / file+image 媒体 - mapUpstreamError 不再把 err.stack 拼进匹配文本:stack 里的 `:429:` 行号 会误触发 rate-limited 分类(CR #5,**真实 bug**) - JSON.stringify 加 try/catch 兜底 String(err),避免循环引用再抛错(CR #6) - 所有业务码正则里的 `.*` 改成 `[^\s]{0,10}?`,限死回溯深度防 ReDoS (CodeQL polynomial regex 告警)
1. 抽 convertSlugToPinyin 到 lib/leetcode-slug.ts,lib/source.ts 和生成脚本 共用同一实现,消除双点维护(Copilot CR comment #1) 2. proxy.ts 里把 SLUG_MAP 从 plain object 换成 Map,实测 __proto__/constructor 等原型链 key 会被错误命中(拿到 [object Object] / Object 构造函数), Map 天然隔离原型链(Copilot CR comment #2) 3. 脚本从 .mjs 改为 .mts 以便 import TypeScript 共享模块,用 Node 24 的 原生 TS 支持执行,prebuild 走 node 不走 tsx(tsx v4.21 在 Node 24 下 会把 TS 转成 CJS 导致命名 ESM 导出丢失)
* fix(seo): leetcode 中文 slug 旧 URL 301 到拼音新路径 GSC 报 41 条 /docs/CommunityShare/Leetcode/<中文文件名> 的 404。根因: lib/source.ts 里的 transformer 会把 leetcode 目录下含中文的文件名转成拼音 slug,但 next.config.mjs 的 wildcard 只做前缀替换没做 slug 拼音化,跳过去 slug 还是中文导致 404。 改动: - scripts/generate-leetcode-slug-map.mjs 构建时扫目录,输出 generated/leetcode-slug-map.json「中文 stem → 拼音 stem」字面映射 - proxy.ts 新增 redirectLeetcodeIfNeeded:Edge 端 O(1) 查表单跳 301 - next.config.mjs 删 leetcode wildcard,改由 middleware 处理避免二跳 - prebuild 挂钩生成脚本,保证 JSON 永远同步最新文件列表 - 补 dev_docs/leetcode_slug_redirect.md 讲清方案 * fix(proxy): Copilot CR — 抽共享模块 + Map 防原型链污染 1. 抽 convertSlugToPinyin 到 lib/leetcode-slug.ts,lib/source.ts 和生成脚本 共用同一实现,消除双点维护(Copilot CR comment #1) 2. proxy.ts 里把 SLUG_MAP 从 plain object 换成 Map,实测 __proto__/constructor 等原型链 key 会被错误命中(拿到 [object Object] / Object 构造函数), Map 天然隔离原型链(Copilot CR comment #2) 3. 脚本从 .mjs 改为 .mts 以便 import TypeScript 共享模块,用 Node 24 的 原生 TS 支持执行,prebuild 走 node 不走 tsx(tsx v4.21 在 Node 24 下 会把 TS 转成 CJS 导致命名 ESM 导出丢失) --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Summary
check:contentscript to package.jsonTesting
pnpm run check:content(fails: The "code" argument must be of type number)https://chatgpt.com/codex/tasks/task_e_68c2f83cb5a48323927b38b9c90cce0d