chore: upgrade tailwind & add shadcn/ui, implement multi-level docs config (closes #6)Feat/add multilevel directory#7
Conversation
…ing for improved theming
…uted fields for ordering and nesting level
…d bilingual support
There was a problem hiding this comment.
Pull Request Overview
This PR upgrades Tailwind CSS to v4 and implements a multi-level directory structure for documentation content, enabling a "Folder as a Book" organization system. It also prepares the project for future UI development by adding shadcn/ui components.
- Upgraded Tailwind CSS from v3 to v4 with new PostCSS configuration
- Implemented hierarchical documentation structure with automated URL generation and navigation
- Enhanced Contentlayer configuration to support multi-level folder organization with computed fields for routing
Reviewed Changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 49 comments.
Show a summary per file
| File | Description |
|---|---|
| tailwind.config.ts | Completely removed to use Tailwind v4's new configuration approach |
| postcss.config.mjs | Updated to use new Tailwind v4 PostCSS plugin |
| package.json | Added shadcn/ui dependencies and upgraded Tailwind to v4 |
| lib/utils.ts | Added utility function for class name merging (shadcn/ui pattern) |
| contentlayer.config.ts | Enhanced with multi-level directory support and computed fields |
| components.json | Added shadcn/ui configuration |
| app/globals.css | Migrated to Tailwind v4 syntax with extensive theme variables |
| app/docs/computer-science/ | Added example documentation structure demonstrating nested organization |
| README.md | Completely rewritten with comprehensive project documentation |
| CONTRIBUTING.md | Significantly expanded with detailed contribution guidelines |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| disableImportAliasWarning: true, | ||
| }) | ||
| import { defineDocumentType, makeSource } from 'contentlayer/source-files' | ||
| // import remarkGfm from 'remark-gfm' |
There was a problem hiding this comment.
The comment is in Chinese but should be in English to maintain consistency. Consider: '// Temporarily removed remarkGfm to resolve compatibility issues'
| contentDirPath: 'app/docs', | ||
| documentTypes: [Doc], | ||
| mdx: { | ||
| remarkPlugins: [], // 暂时移除 remarkGfm 以解决兼容性问题 |
There was a problem hiding this comment.
The comment is in Chinese. For consistency with the codebase, use English: '// Temporarily removed remarkGfm to resolve compatibility issues'
| remarkPlugins: [], // 暂时移除 remarkGfm 以解决兼容性问题 | |
| remarkPlugins: [], // Temporarily removed remarkGfm to resolve compatibility issues |
| return removedData; | ||
| } | ||
|
|
||
| // 删除指定值的第一个节点 |
There was a problem hiding this comment.
Mixed language comments detected. The comment should be in English: '// Remove the first node with specified value'
| // 删除指定值的第一个节点 | |
| // Remove the first node with specified value |
| return false; | ||
| } | ||
|
|
||
| // 如果头节点就是要删除的节点 |
There was a problem hiding this comment.
Comment should be in English: '// If the head node is the one to be deleted'
| // 如果头节点就是要删除的节点 | |
| // If the head node is the one to be deleted |
| current = current.next; | ||
| } | ||
|
|
||
| // 找到了要删除的节点 |
There was a problem hiding this comment.
Comment should be in English: '// Found the node to be deleted'
| // 找到了要删除的节点 | |
| // Found the node to be deleted |
| // 不同的增长策略 | ||
| const GROWTH_STRATEGIES = { | ||
| DOUBLE: (capacity) => capacity * 2, // 快速增长,可能浪费内存 | ||
| GOLDEN_RATIO: (capacity) => Math.floor(capacity * 1.5), // 平衡增长 | ||
| FIBONACCI: (capacity) => capacity + previousCapacity, // 渐进增长 |
There was a problem hiding this comment.
Comments should be in English: '// Different growth strategies', '// Fast growth, may waste memory', '// Balanced growth', '// Progressive growth'
| // 不同的增长策略 | |
| const GROWTH_STRATEGIES = { | |
| DOUBLE: (capacity) => capacity * 2, // 快速增长,可能浪费内存 | |
| GOLDEN_RATIO: (capacity) => Math.floor(capacity * 1.5), // 平衡增长 | |
| FIBONACCI: (capacity) => capacity + previousCapacity, // 渐进增长 | |
| // Different growth strategies | |
| const GROWTH_STRATEGIES = { | |
| DOUBLE: (capacity) => capacity * 2, // Fast growth, may waste memory | |
| GOLDEN_RATIO: (capacity) => Math.floor(capacity * 1.5), // Balanced growth | |
| FIBONACCI: (capacity) => capacity + previousCapacity, // Progressive growth |
| ### 2. 预分配容量 | ||
|
|
||
| ```javascript | ||
| // 如果知道大概的数据量,可以预分配容量 |
There was a problem hiding this comment.
Section heading and comment should be in English: '### 2. Pre-allocate Capacity' and '// If you know the approximate data size, you can pre-allocate capacity'
| ### 2. 预分配容量 | |
| ```javascript | |
| // 如果知道大概的数据量,可以预分配容量 | |
| ### 2. Pre-allocate Capacity | |
| ```javascript | |
| // If you know the approximate data size, you can pre-allocate capacity |
| this.data = new Array(this.capacity); | ||
| } | ||
|
|
||
| // 预留容量 |
There was a problem hiding this comment.
Comment should be in English: '// Reserve capacity'
| // 预留容量 | |
| // Reserve capacity |
| ### 3. 内存对齐优化 | ||
|
|
||
| ```cpp | ||
| // C++ 中考虑内存对齐 |
There was a problem hiding this comment.
Section heading and comment should be in English: '### 3. Memory Alignment Optimization' and '// Consider memory alignment in C++'
| ### 3. 内存对齐优化 | |
| ```cpp | |
| // C++ 中考虑内存对齐 | |
| ### 3. Memory Alignment Optimization | |
| ```cpp | |
| // Consider memory alignment in C++ |
| size_t size; | ||
| size_t capacity; | ||
|
|
||
| // 确保容量是 2 的幂次,有利于内存对齐 |
There was a problem hiding this comment.
Comment should be in English: '// Ensure capacity is a power of 2, beneficial for memory alignment'
| // 确保容量是 2 的幂次,有利于内存对齐 | |
| // Ensure capacity is a power of 2, beneficial for memory alignment |
- 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"
Changes
Dependencies
tailwindcssto the latest versionshadcn/uifor future component developmentDocs System
Implementing "Folder as a Book" Content Structure:
contentlayer.config.tsto support a hierarchical "Folder as a Book" structure. This automatically generates clean URLs, manages content order, and determines content nesting levels based on the file system.slug,slugAsParams,order, andlevelas computed fields in Contentlayer for dynamic routing and navigation.Verification
pnpm installafter pulling this branchpnpm dev/docs/...