Skip to content

feat(ui): improve blog navigation and social links#65

Merged
one-ea merged 5 commits into
mainfrom
dev
May 3, 2026
Merged

feat(ui): improve blog navigation and social links#65
one-ea merged 5 commits into
mainfrom
dev

Conversation

@one-ea

@one-ea one-ea commented May 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • Improve frontend and admin UI/UX across the blog experience.
  • Add extensible social/friend links via settings while preserving legacy GitHub/X/email fields.
  • Compact the frontend category sidebar and support archive filtering by category.
  • Keep D1 schema setup out of request path and preserve password manager icon alignment fixes already on dev.

Verification

  • npm -w monolith-client run check
  • npm -w monolith-client run lint
  • npm -w monolith-client run build
  • npm -w monolith-server run check
  • npx eslint "server/src" --max-warnings 0
  • Cloudflare production deploy completed; health checks passed for Workers, Pages, AE auth, sitemap, robots, RSS, and category archive route.

Notes

  • This PR is opened only; merge remains manual after required checks and review threads are clear.

@coderabbitai

coderabbitai Bot commented May 3, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@one-ea has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 16 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 82c6879c-5292-4d63-bfd5-fdd594a708fd

📥 Commits

Reviewing files that changed from the base of the PR and between 28e843c and dd871e3.

📒 Files selected for processing (16)
  • client/src/components/admin-gate.tsx
  • client/src/components/admin-layout.tsx
  • client/src/components/hero.tsx
  • client/src/globals.css
  • client/src/pages/admin/settings.tsx
  • client/src/pages/archive.tsx
  • client/src/pages/home.tsx
  • scripts/deploy-cloudflare.mjs
  • scripts/reconcile-d1-schema.mjs
  • server/src/db/schema-pg.ts
  • server/src/db/schema.ts
  • server/src/migrations/0009_runtime_schema_indexes.sql
  • server/src/storage/db/d1.ts
  • server/src/storage/db/postgres.ts
  • server/src/storage/db/turso.ts
  • server/src/storage/factory.ts
📝 Walkthrough

Walkthrough

本PR包含大规模前端UI重构(样式、响应式布局、icon替换)、社交链接配置系统升级、D1数据库schema自动迁移机制,以及存档页分类过滤功能。整体涉及25+个文件的改动,跨越客户端组件、管理后台、部署脚本和服务端接口。

Changes

UI组件与样式统一升级

Layer / File(s) Summary
Icon替换与导入
client/src/components/admin-gate.tsx, article-card.tsx, home.tsx
统一使用lucide-react替换内联SVG和emoji;ShieldCheckPinArrowRight等icon引入。
组件布局与响应式设计
client/src/components/admin-layout.tsx, admin-gate.tsx, article-card.tsx, search.tsx
响应式布局调整、固定侧边栏(w-[248px])、桌面端header新增、md断点处理优化;TOC焦点样式增强。
全局样式与动效
client/src/globals.css
body字体增大16px、overflow-x:hidden、新增.hero-gridprefers-reduced-motion无障碍支持、.card-hover动效优化、段落排版调整。
按钮与交互状态
client/src/components/theme-toggle.tsx, navbar.tsx, post.tsx
按钮尺寸统一(h-[44px]/w-[44px]响应式)、focus-visible轮廓样式增强、aria-label无障碍标签补全。
表单与输入框
client/src/pages/admin/dashboard.tsx, admin/login.tsx, cookie-consent.tsx
输入框高度/padding/焦点样式调整、提交按钮与输入框尺寸对齐、cookie横幅容器重构。
卡片与列表组件
reading-controls.tsx, hero.tsx, article-card.tsx
卡片背景透明度、控制按钮网格布局(固定height/width)、字体选项按钮样式统一。

社交链接配置系统

Layer / File(s) Summary
数据结构与类型
client/src/pages/admin/settings.tsx, client/src/pages/home.tsx
新增SocialLinkConfig类型、social_links: string字段(JSON序列化)、icon映射表SOCIAL_ICON_MAP
配置解析与迁移逻辑
client/src/pages/admin/settings.tsx, client/src/pages/home.tsx
parseSocialLinks()支持JSON反序列化、getLegacySocialLinks()兼容旧字段(github_url/twitter_url/email)、getPublicSocialLinks()选择JSON或降级至旧字段。
编辑界面与状态管理
client/src/pages/admin/settings.tsx
新增"友链与社交入口"Tab、item编辑器(label、url、icon选择)、启用/禁用开关、删除操作;handleSave()生成nextSettings包含序列化JSON和降级遗留字段。
序列化与提交
client/src/pages/admin/settings.tsx
serializeSocialLinks()将列表转为JSON、toLegacySocialFields()提取enabled项写入旧字段,PUT请求body包含完整payload。
后端与公开API
server/src/index.ts
GET /api/settings/public响应体新增social_links字段;服务端Bindings类型补全。

D1数据库Schema自动迁移管理

Layer / File(s) Summary
新迁移脚本与工具
scripts/reconcile-d1-schema.mjs
新脚本支持--local/--remote模式、--database参数;通过pragma_table_info()检查现有列、对比目标schema (series_slugseries_ordercategory)、执行ALTER TABLE补齐缺失列。
部署流程增强
scripts/deploy-cloudflare.mjs
新增runResult()统一化spawnSync调用(cwd/stdio/shell/encoding)、failStep()集中错误处理;迁移后添加node scripts/reconcile-d1-schema.mjs --remote步骤;Pages项目创建流程(失败重试机制)。
运行时控制与迁移定义
server/src/storage/factory.ts, server/wrangler.toml
新增AUTO_SCHEMA_MIGRATION环境变量(默认false)、D1适配器条件执行ensureSchema();禁用运行时自动迁移,由部署脚本负责schema同步。
数据库基线定义
server/src/migrations/0008_runtime_schema_baseline.sql
新建migration补齐settingspagescommentsreactionsvisits表(含IF NOT EXISTS);定义主键、唯一约束、外键(ON DELETE CASCADE)、默认时间戳。

存档页分类过滤功能

Layer / File(s) Summary
URL查询解析
client/src/pages/archive.tsx
导入useLocation,从query提取category参数,驱动文章列表过滤。
数据派生与分组
client/src/pages/archive.tsx
计算visiblePosts(全部或匹配分类)、groupedyears基于filtered数据;SEO元数据和标题根据分类动态调整。
UI交互与导航
client/src/pages/archive.tsx
条件渲染"查看全部"链接、清除分类过滤;空状态提示适配分类场景;分组渲染和链接样式优化(min-h、hover状态)。

主页社交与分类展示升级

Layer / File(s) Summary
社交链接集成
client/src/pages/home.tsx
调用getPublicSocialLinks(),结合新JSON和旧字段支持;icon anchor补全aria-label和焦点样式。
分类列表重构
client/src/pages/home.tsx
新增CategoryList组件(可展开/折叠、aria-expanded)替代内联渲染;更新样式映射(weight-based color-mix替代opacity)。
文章列表与空态
client/src/pages/home.tsx
明确posts.length === 0空态卡片;tag cloud容器和"展开全部"按钮样式调整。

配置与脚本命令

Layer / File(s) Summary
.gitignore规则扩展
.gitignore
新增本地缓存忽略(.cache/.vite/coverage/playwright-report/test-results/);Wrangler env通配符.dev.vars.*;Yarn/Pnpm调试日志;AI工具私数据目录更新(.agents/.serena/.codex/)。
npm脚本命令
package.json
新增db:reconcile:d1:localdb:reconcile:d1:remote调用reconcile脚本。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

复杂度分析

  • 高重复性改动:20+组件的样式Tailwind类调整(降低审查难度)
  • 新业务逻辑:社交链接JSON序列化、向后兼容迁移逻辑(需重点审查)
  • 脚本与自动化:D1 schema reconciliation脚本、部署流程增强(中等复杂)
  • 跨域协调:前后端字段变更、DB模型扩展、type安全性

关键审查点

  • 社交链接的JSON<→legacy字段双向转换逻辑
  • D1 schema迁移顺序与缺失列检测
  • 响应式布局在移动端(smmd断点)的正确性
  • 暗/亮主题下TOC样式一致性(新增light-mode overrides)
  • 无障碍属性(aria-labelfocus-visible)的完整性

Possibly related PRs

Suggested labels

frontend, backend, feature, database, styles, accessibility

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 标题「feat(ui): improve blog navigation and social links」遵循 Conventional Commits 格式,准确概括了主要改动(UI 改进、导航和社交链接功能)。
Description check ✅ Passed PR 描述清晰列举了所有主要改动(UI/UX 改进、社交链接功能、分类过滤、D1 Schema 优化),并提供了完整的验证步骤和部署确认,与代码变更高度相关。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch dev

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 25 minutes and 16 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added frontend 前端 (React/Vite) 相关变更 backend 后端 (Hono Workers) 相关变更 styles CSS/UI 样式调整 feature 新功能 labels May 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
client/src/components/admin-gate.tsx (1)

88-188: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

模态框缺少焦点陷阱和 role="dialog"

当前实现:

  1. 模态容器没有 role="dialog"aria-modal="true",屏幕阅读器不会将其识别为对话框。
  2. 没有焦点陷阱——Tab 键可以穿透遮罩层到达背景中的可交互元素,产生混乱的键盘导航体验。

ESC 关闭和初始聚焦均已实现,补全以下两点即可达到 WCAG 2.1 Level AA 要求。

♿ 建议修复方案
- <div className="fixed left-1/2 top-1/2 z-50 w-[min(92vw,420px)] -translate-x-1/2 -translate-y-1/2 animate-in fade-in slide-in-from-top-2 duration-200">
-   <div className="overflow-hidden rounded-md border border-border/35 bg-card/95 ...">
+ <div
+   role="dialog"
+   aria-modal="true"
+   aria-labelledby="admin-gate-title"
+   className="fixed left-1/2 top-1/2 z-50 w-[min(92vw,420px)] -translate-x-1/2 -translate-y-1/2 animate-in fade-in slide-in-from-top-2 duration-200"
+ >
+   <div className="overflow-hidden rounded-md border border-border/35 bg-card/95 ...">
- <p className="text-[13px] font-semibold text-foreground">后台安全验证</p>
+ <p id="admin-gate-title" className="text-[13px] font-semibold text-foreground">后台安全验证</p>

焦点陷阱可在已有的 ESC useEffect 中扩展,拦截 Tab/Shift+Tab 并在 wrapperRef 内的可聚焦元素之间循环,或引入 focus-trap-react 库。

As per coding guidelines: client/src/components/** 需确保"无障碍访问(aria 标签、键盘导航)"。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/admin-gate.tsx` around lines 88 - 188, The modal is
missing role="dialog"/aria-modal="true" and a focus trap; add these by giving
the modal container a ref (e.g., modalRef or wrapperRef), set role="dialog" and
aria-modal="true" on that container, ensure initial focus moves to inputRef on
open (you already have inputRef), and extend the existing ESC useEffect to also
listen for Tab/Shift+Tab: on keydown, collect focusable elements inside modalRef
(buttons, inputs, links, [tabindex] etc.) and prevent default Tab behavior while
cycling focus within that list (wrap from last->first and first->last). Also
ensure cleanup of the keydown listener on unmount/close and keep onClose
handling intact (use onClose when ESC pressed).
client/src/components/admin-layout.tsx (1)

160-173: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

aria-controls 所引用的元素 ID 不存在,无障碍关联失效

Line 197 按钮声明 aria-controls="admin-mobile-navigation",但 Lines 165-171 中的 aside 元素没有对应的 id 属性,辅助技术(屏幕阅读器)无法找到被控制的元素,导致 aria-controls 关系完全失效。

🔧 建议修复:给 aside 添加 id
  <aside
+   id="admin-mobile-navigation"
    role="dialog"
    aria-label="导航菜单"
    className="relative flex flex-col w-[260px] max-w-[80vw] h-full bg-background shadow-2xl animate-in slide-in-from-left"
  >

Also applies to: 192-200

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/admin-layout.tsx` around lines 160 - 173, The mobile
navigation <aside> rendered in the AdminLayout lacks the id referenced by the
toggle button's aria-controls ("admin-mobile-navigation"), breaking the
accessibility relationship; fix by adding id="admin-mobile-navigation" to the
<aside> used for the mobile menu (the element rendering SidebarContent inside
AdminLayout) and ensure any duplicate desktop/other <aside> uses a different
unique id or the same id only for the matching controlled element so the
button's aria-controls correctly points to that element.
🧹 Nitpick comments (2)
scripts/reconcile-d1-schema.mjs (1)

60-74: ⚡ Quick win

正则解析列名过于宽泛,建议改为 JSON 模式

output.match(/[A-Za-z_][A-Za-z0-9_]*/g) 会匹配 pragma_table_info 输出中的所有标识符,包括 wrangler 横幅文本、pragma 返回的元列名(cidtypenotnulldflt_valuepk)等。目前的三个目标列名(series_slugseries_ordercategory)恰好不与这些字符串冲突,但若未来添加名为 typename 的列,has() 检查将误报为"已存在",导致漏补。

建议对 wrangler 以 --json 格式执行查询,再按结构解析 name 字段:

♻️ 建议改用 --json 解析
 function queryPostsColumns(options) {
   const output = runWrangler(
     [
       "d1",
       "execute",
       options.database,
       d1Scope(options.mode),
+      "--json",
       "--command",
       "SELECT name FROM pragma_table_info('posts');",
     ],
     "读取 posts 表结构",
   );

-  return new Set(output.match(/[A-Za-z_][A-Za-z0-9_]*/g) || []);
+  try {
+    // wrangler --json 输出形如: [{"results":[{"name":"id"},...],...}]
+    const parsed = JSON.parse(output.trim().split("\n").find((l) => l.startsWith("[")));
+    const rows = parsed?.[0]?.results ?? [];
+    return new Set(rows.map((r) => r.name));
+  } catch {
+    // fallback: 正则兜底(兼容旧版 wrangler 输出格式)
+    return new Set(output.match(/[A-Za-z_][A-Za-z0-9_]*/g) || []);
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/reconcile-d1-schema.mjs` around lines 60 - 74, The regex in
queryPostsColumns is too broad and can capture non-column tokens; change the
runWrangler invocation inside queryPostsColumns to use Wrangler's --json output
(add "--json" to the args) and parse the returned JSON string (from runWrangler)
to extract the "name" property for each row, then return a Set of those names;
update any error handling around JSON.parse accordingly and keep references to
queryPostsColumns and runWrangler so the logic remains clearly located.
server/src/migrations/0008_runtime_schema_baseline.sql (1)

21-29: ⚡ Quick win

建议为 comments(post_id) 添加索引

WHERE post_id = ? 是评论最常见的查询模式,数据增长后无索引会退化为全表扫描。reactions 表的 UNIQUE 约束已隐式建索引,comments 缺少同等覆盖。

⚡ 建议补充索引
  created_at TEXT NOT NULL DEFAULT (datetime('now'))
);
+
+CREATE INDEX IF NOT EXISTS idx_comments_post_id ON comments(post_id);
+CREATE INDEX IF NOT EXISTS idx_visits_path ON visits(path);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/src/migrations/0008_runtime_schema_baseline.sql` around lines 21 - 29,
Add an index on comments(post_id) to avoid full-table scans for WHERE post_id =
? queries; update the migration (0008_runtime_schema_baseline.sql) to CREATE
INDEX IF NOT EXISTS comments_post_id_idx ON comments(post_id) so the comments
table has the same query-performance coverage as the reactions UNIQUE constraint
which implicitly created an index.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/src/components/hero.tsx`:
- Line 5: The gradient in the Hero component currently hardcodes color tokens in
the div with className containing "from-blue-500 ... via-cyan-500"; change it to
use semantic/theme tokens (e.g., replace "from-blue-500" and "via-cyan-500" with
theme-aware classes like "from-primary-..." or CSS variables such as
"from-[var(--color-primary)]/..." or Tailwind semantic tokens) so the gradient
follows light/dark theme switches; update the JSX in
client/src/components/hero.tsx (the <div ... className="... -translate-y-1/2
rounded-full bg-gradient-radial ...">) to reference those theme tokens and
ensure corresponding CSS/variables or Tailwind config provides the semantic
colors.

In `@client/src/globals.css`:
- Around line 138-141: 在 body 选择器的声明块中, Stylelint 的
declaration-empty-line-before 规则要求在 `@apply` 指令和后续普通声明之间保留一个空行;在包含 `@apply`
bg-background text-foreground; 的块(body)中,在 `@apply` 与 font-size: 16px
之间插入一个空行以修复报错(检查其他类似块以确保一致性)。

In `@client/src/pages/admin/settings.tsx`:
- Around line 111-113: getSocialLinks currently treats any non-empty
settings.social_links as the new format, but parseSocialLinks can fail and
return an empty array which causes accidental fallback clearing of legacy
fields; change getSocialLinks to first check for an empty string (treat as
not-provided -> use getLegacySocialLinks), otherwise attempt to parse inside a
try/catch (call parseSocialLinks(settings.social_links)); if parsing throws or
returns a special failure value (null/undefined) then fall back to
getLegacySocialLinks, but if parsing succeeds (including returning an explicit
empty array []) return that result so an intentional empty list is preserved;
reference getSocialLinks, parseSocialLinks, getLegacySocialLinks, Settings, and
SocialLinkConfig when making this change.

In `@client/src/pages/archive.tsx`:
- Around line 57-62: The empty-state message in the archive component is
misleading when no category filter is active; update the rendering logic in the
archive page (look for selectedCategory and the rounded-md border div) to
conditionally show two messages: when selectedCategory is truthy display the
current message about "当前分类下暂无可见文章,可以返回全部归档继续浏览。", and when selectedCategory is
falsy display a global-empty message such as "暂无文章,稍后再来或创建首篇文章" (or similar), so
users know there are no posts at all rather than a filter issue.
- Line 40: SeoHead currently hardcodes url="/archive" and breadcrumbs only show
top-level items even when selectedCategory is set; update the SeoHead usage so
its url reflects the actual page canonical (e.g., include selectedCategory
identifier or slug in the path or query based on your routing: use
selectedCategory?.slug or selectedCategory?.id) and amend the breadcrumbs prop
to append the current category ({ name: selectedCategory.name, url: ... }) when
selectedCategory is non-null; adjust references to
archiveTitle/archiveDescription if you want the title/description to include the
category as well.
- Line 19: Add an error state and set it when fetchPosts fails: update the
fetchPosts().then(setPosts).catch(console.error).finally(() =>
setLoading(false)) call to capture the thrown error and call setError(error)
(and clear error on success), and update the component JSX to, after the loading
check and before the empty posts check, render an error message when error is
set (use the existing posts, setPosts, setLoading, fetchPosts symbols to locate
the logic and the empty-state render). Ensure error state is initialized (e.g.,
const [error, setError] = useState(null)) and reset on successful fetch so users
see a clear network/error message instead of “no posts” when the request fails.
- Line 2: Replace the non-reactive useLocation usage with wouter's useSearch to
read the query string reactively (swap useLocation for useSearch and derive
selectedCategory from the returned search string), update any checks that relied
on location.includes("?") to parse the search string instead, add an error state
(e.g., postsError via useState) and set it when fetchPosts() fails so the UI can
show an error + retry action instead of the empty-state, include the active
category in the canonical URL and breadcrumb generation (build
url="/archive?category=X" and breadcrumb label "分类:X" when selectedCategory is
present), and change the empty-state copy to conditionally show "没有找到匹配文章" when
no filter is active and "该分类暂无文章" when selectedCategory is set; reference
functions/vars: useSearch, fetchPosts, selectedCategory, posts (or whatever
state holds posts), and canonical/breadcrumb rendering logic.

In `@client/src/pages/home.tsx`:
- Around line 83-98: In getPublicSocialLinks replace using link.label as a React
key by passing through and using a stable unique identifier (e.g., link.id) or a
stable composite (e.g., `${link.id}-${link.url}`) when rendering the link list;
ensure the map still uses SOCIAL_ICON_MAP, ExternalLink, and normalizeSocialHref
as before but carry link.id into the returned objects so the rendering component
can use it as the key to avoid DOM reuse bugs when labels collide (also apply
the same fix to the other occurrence that maps social links).
- Around line 76-80: normalizeSocialHref currently returns backend-provided URLs
directly, allowing unsafe protocols; update normalizeSocialHref to parse and
enforce a protocol whitelist (allow only http:, https:, mailto:) and permit
relative paths (no protocol) and explicit /rss.xml case, and return null or
empty for any other protocols (e.g., javascript:, data:). Then update the code
that builds the social links list (the code around the assembly mentioned at
lines 94-104) to filter out falsy/invalid results from normalizeSocialHref
before rendering <a> elements. Reference: function normalizeSocialHref and the
social-link list assembly logic.

In `@scripts/deploy-cloudflare.mjs`:
- Around line 254-280: The pages project auto-create path never runs because
runResult call that sets pagesSecret uses stdio which inherits stdout/stderr so
shouldCreatePagesProject can't see output; modify the runResult invocation that
creates pagesSecret (the call using pagesSecretArgs and options.apiBase) to
explicitly capture child output (e.g. pass stdio: "pipe" or stdio:
["pipe","pipe","pipe"] in the options) so result.stdout/result.stderr are
populated and shouldCreatePagesProject can detect the missing-project signals;
keep the retry logic with pagesSecretArgs, pagesSecret variable,
ensurePagesProject, and failStep unchanged except for this stdio fix.

In `@server/src/storage/factory.ts`:
- Around line 22-30: The D1 branch returns the adapter without validation when
AUTO_SCHEMA_MIGRATION is disabled, so add a read-only schema readiness check and
fail fast: when provider === "d1" and autoSchemaMigration is false, call a new
D1Adapter method (e.g. ensureSchemaBaseline or checkSchemaReadiness) that only
verifies presence/columns of core tables
(settings/pages/comments/reactions/visits and posts) and throws a descriptive
Error if missing; keep calling ensureSchema() when autoSchemaMigration is true
and return the D1Adapter only after the baseline check passes so startup fails
clearly instead of surfacing SQL errors on first request.

---

Outside diff comments:
In `@client/src/components/admin-gate.tsx`:
- Around line 88-188: The modal is missing role="dialog"/aria-modal="true" and a
focus trap; add these by giving the modal container a ref (e.g., modalRef or
wrapperRef), set role="dialog" and aria-modal="true" on that container, ensure
initial focus moves to inputRef on open (you already have inputRef), and extend
the existing ESC useEffect to also listen for Tab/Shift+Tab: on keydown, collect
focusable elements inside modalRef (buttons, inputs, links, [tabindex] etc.) and
prevent default Tab behavior while cycling focus within that list (wrap from
last->first and first->last). Also ensure cleanup of the keydown listener on
unmount/close and keep onClose handling intact (use onClose when ESC pressed).

In `@client/src/components/admin-layout.tsx`:
- Around line 160-173: The mobile navigation <aside> rendered in the AdminLayout
lacks the id referenced by the toggle button's aria-controls
("admin-mobile-navigation"), breaking the accessibility relationship; fix by
adding id="admin-mobile-navigation" to the <aside> used for the mobile menu (the
element rendering SidebarContent inside AdminLayout) and ensure any duplicate
desktop/other <aside> uses a different unique id or the same id only for the
matching controlled element so the button's aria-controls correctly points to
that element.

---

Nitpick comments:
In `@scripts/reconcile-d1-schema.mjs`:
- Around line 60-74: The regex in queryPostsColumns is too broad and can capture
non-column tokens; change the runWrangler invocation inside queryPostsColumns to
use Wrangler's --json output (add "--json" to the args) and parse the returned
JSON string (from runWrangler) to extract the "name" property for each row, then
return a Set of those names; update any error handling around JSON.parse
accordingly and keep references to queryPostsColumns and runWrangler so the
logic remains clearly located.

In `@server/src/migrations/0008_runtime_schema_baseline.sql`:
- Around line 21-29: Add an index on comments(post_id) to avoid full-table scans
for WHERE post_id = ? queries; update the migration
(0008_runtime_schema_baseline.sql) to CREATE INDEX IF NOT EXISTS
comments_post_id_idx ON comments(post_id) so the comments table has the same
query-performance coverage as the reactions UNIQUE constraint which implicitly
created an index.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d0fe0292-f0f3-409f-9b0c-2c44038c5427

📥 Commits

Reviewing files that changed from the base of the PR and between 31bfc8e and 28e843c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json, !**/package-lock.json
📒 Files selected for processing (24)
  • .gitignore
  • client/src/components/admin-gate.tsx
  • client/src/components/admin-layout.tsx
  • client/src/components/article-card.tsx
  • client/src/components/cookie-consent.tsx
  • client/src/components/hero.tsx
  • client/src/components/navbar.tsx
  • client/src/components/reading-controls.tsx
  • client/src/components/search.tsx
  • client/src/components/theme-toggle.tsx
  • client/src/globals.css
  • client/src/pages/admin/dashboard.tsx
  • client/src/pages/admin/login.tsx
  • client/src/pages/admin/settings.tsx
  • client/src/pages/archive.tsx
  • client/src/pages/home.tsx
  • client/src/pages/post.tsx
  • package.json
  • scripts/deploy-cloudflare.mjs
  • scripts/reconcile-d1-schema.mjs
  • server/src/index.ts
  • server/src/migrations/0008_runtime_schema_baseline.sql
  • server/src/storage/factory.ts
  • server/wrangler.toml
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
client/src/pages/**

⚙️ CodeRabbit configuration file

client/src/pages/**: 页面级组件。审查时请关注: 1. 数据加载和错误处理是否完善 2. SEO 相关(页面标题、meta 标签) 3. 导航和路由是否正确

Files:

  • client/src/pages/admin/login.tsx
  • client/src/pages/post.tsx
  • client/src/pages/home.tsx
  • client/src/pages/admin/dashboard.tsx
  • client/src/pages/admin/settings.tsx
  • client/src/pages/archive.tsx
client/src/components/**

⚙️ CodeRabbit configuration file

client/src/components/**: 这是 React 前端组件目录。审查时请关注: 1. 是否同时兼容暗色和亮色主题(检查 CSS 变量和 data-theme) 2. 响应式布局是否完整(移动端/平板/桌面端) 3. 无障碍访问(aria 标签、键盘导航) 4. 组件是否保持单一职责

Files:

  • client/src/components/theme-toggle.tsx
  • client/src/components/cookie-consent.tsx
  • client/src/components/article-card.tsx
  • client/src/components/admin-layout.tsx
  • client/src/components/admin-gate.tsx
  • client/src/components/hero.tsx
  • client/src/components/navbar.tsx
  • client/src/components/search.tsx
  • client/src/components/reading-controls.tsx
server/src/storage/**

⚙️ CodeRabbit configuration file

server/src/storage/**: 存储适配器层(数据库 + 对象存储)。审查时请关注: 1. 接口实现是否完整(IDatabase / IObjectStorage) 2. SQL 注入防护(参数化查询) 3. 多后端(D1/Turso/PostgreSQL)行为一致性 4. 错误处理和边界情况

Files:

  • server/src/storage/factory.ts
server/src/index.ts

⚙️ CodeRabbit configuration file

server/src/index.ts: Hono Workers API 路由总入口。审查时请关注: 1. JWT 认证中间件是否正确保护管理接口 2. CORS 配置是否安全 3. 请求参数验证

Files:

  • server/src/index.ts
client/src/globals.css

⚙️ CodeRabbit configuration file

client/src/globals.css: 全局样式和 CSS 变量系统。审查时请关注: 1. [data-theme="light"] 和默认暗色主题的变量是否配对 2. OKLCH 色值的明度/色度是否合理 3. 是否有遗漏的选择器未覆盖亮色模式

Files:

  • client/src/globals.css
🪛 Stylelint (17.9.0)
client/src/globals.css

[error] 140-140: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)

🔇 Additional comments (12)
client/src/components/cookie-consent.tsx (1)

43-57: 样式改动方向正确,响应式与可访问性表现良好。

本次 class 调整在不改变同意逻辑的前提下,增强了小屏到 sm 断点的布局适配,并保留了清晰的 focus-visible 焦点样式与 44px 触控高度,整体实现干净且职责单一。

As per coding guidelines, “请关注:1. 是否同时兼容暗色和亮色主题… 2. 响应式布局是否完整… 3. 无障碍访问… 4. 组件是否保持单一职责”.

client/src/components/hero.tsx (1)

3-3: 这几处样式与结构调整整体可接受。

响应式间距仍然完整,组件职责保持单一,文本结构与可读性也更清晰。

Also applies to: 11-13, 17-19

client/src/components/navbar.tsx (2)

58-58: Header 毛玻璃样式 LGTM

border-border/30bg-background/82 均基于 CSS 变量,在 Tailwind v4 动态数值体系下有效,亮色/暗色主题均能正确跟随 token 切换。


98-103: 无障碍改进质量高,LGTM

三点改进均到位:

  1. 触控目标h-[44px] w-[44px] 满足 WCAG 2.5.5 建议的最小 44×44 px 点击区域。
  2. 焦点环focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring 是标准的 shadcn/ui + Tailwind v4 组合——--color-ring: var(--ring) 已在 @theme inline 中声明,outline-ring 可正确解析为 outline-color: var(--color-ring)
  3. aria-label"打开导航菜单" 与应用语言一致,屏幕阅读器可正确播报按钮用途;Radix UI 的 SheetTrigger 会自动管理 aria-expanded 状态,无需额外处理。
client/src/components/theme-toggle.tsx (1)

57-60: LGTM!

移动端 44px 触控目标符合 WCAG 2.5.5,sm: 断点缩放至 32px 符合桌面场景;focus-visible 环与整个项目保持一致;CSS 变量驱动保证暗/亮双主题兼容。

client/src/pages/admin/login.tsx (1)

31-70: LGTM!

响应式容器/卡片尺寸调整合理,sm: 断点完整;密码管理器 username 占位字段保留;禁用态样式正确。

.gitignore (1)

11-15: LGTM!

新增构建/测试产物的 ignore 规则完整;.dev.vars.* 通配覆盖多环境变体;AI 工具目录和包管理器日志的补充也是良好的仓库卫生实践。

package.json (1)

17-18: LGTM!

新增脚本命名与现有 db:migrate:d1:local/remote 模式一致,--local/--remote 参数传递正确,与 deploy-cloudflare.mjs 中的自动调用形成完整闭环。

client/src/components/article-card.tsx (1)

22-79: LGTM — 图标替换与样式更新均无问题

Pin/ArrowRight 图标替换整洁,响应式布局(sm:flex-rowsm:w-[156px]lg:w-[176px])和 focus-visible 无障碍样式完善,暗/亮主题均通过 CSS 变量适配。

client/src/pages/admin/dashboard.tsx (1)

236-272: LGTM — 操作按钮无障碍属性与样式更新均规范

title/aria-label 双属性、focus-visible 轮廓、min-h-[36px] 触控目标均已到位,改动为纯样式优化,无逻辑变更。

client/src/pages/post.tsx (1)

200-212: LGTM — 无障碍与焦点样式改进规范

aria-label 补齐、focus-visible 轮廓、rounded-md 一致应用,两处"返回首页"链接样式保持对称,改动整洁。

server/src/index.ts (1)

344-365: LGTM — social_links 字段正确暴露于公开设置端点

social_links: all.social_links || "" 与现有的 custom_headerfooter_text 等字段处理方式一致,空值兜底合理,不含敏感信息。

Comment thread client/src/components/hero.tsx Outdated
Comment thread client/src/globals.css
Comment thread client/src/pages/admin/settings.tsx
Comment thread client/src/pages/archive.tsx Outdated
Comment thread client/src/pages/archive.tsx Outdated
Comment thread client/src/pages/archive.tsx
Comment thread client/src/pages/home.tsx Outdated
Comment thread client/src/pages/home.tsx Outdated
Comment thread scripts/deploy-cloudflare.mjs
Comment thread server/src/storage/factory.ts
@one-ea one-ea merged commit 0851c95 into main May 3, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend 后端 (Hono Workers) 相关变更 feature 新功能 frontend 前端 (React/Vite) 相关变更 styles CSS/UI 样式调整

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant