Conversation
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (16)
📝 WalkthroughWalkthrough本PR包含大规模前端UI重构(样式、响应式布局、icon替换)、社交链接配置系统升级、D1数据库schema自动迁移机制,以及存档页分类过滤功能。整体涉及25+个文件的改动,跨越客户端组件、管理后台、部署脚本和服务端接口。 ChangesUI组件与样式统一升级
社交链接配置系统
D1数据库Schema自动迁移管理
存档页分类过滤功能
主页社交与分类展示升级
配置与脚本命令
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~28 minutes 复杂度分析:
关键审查点:
Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
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.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Review rate limit: 0/1 reviews remaining, refill in 25 minutes and 16 seconds.Comment |
There was a problem hiding this comment.
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"当前实现:
- 模态容器没有
role="dialog"和aria-modal="true",屏幕阅读器不会将其识别为对话框。- 没有焦点陷阱——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 返回的元列名(cid、type、notnull、dflt_value、pk)等。目前的三个目标列名(series_slug、series_order、category)恰好不与这些字符串冲突,但若未来添加名为type或name的列,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
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json,!**/package-lock.json
📒 Files selected for processing (24)
.gitignoreclient/src/components/admin-gate.tsxclient/src/components/admin-layout.tsxclient/src/components/article-card.tsxclient/src/components/cookie-consent.tsxclient/src/components/hero.tsxclient/src/components/navbar.tsxclient/src/components/reading-controls.tsxclient/src/components/search.tsxclient/src/components/theme-toggle.tsxclient/src/globals.cssclient/src/pages/admin/dashboard.tsxclient/src/pages/admin/login.tsxclient/src/pages/admin/settings.tsxclient/src/pages/archive.tsxclient/src/pages/home.tsxclient/src/pages/post.tsxpackage.jsonscripts/deploy-cloudflare.mjsscripts/reconcile-d1-schema.mjsserver/src/index.tsserver/src/migrations/0008_runtime_schema_baseline.sqlserver/src/storage/factory.tsserver/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.tsxclient/src/pages/post.tsxclient/src/pages/home.tsxclient/src/pages/admin/dashboard.tsxclient/src/pages/admin/settings.tsxclient/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.tsxclient/src/components/cookie-consent.tsxclient/src/components/article-card.tsxclient/src/components/admin-layout.tsxclient/src/components/admin-gate.tsxclient/src/components/hero.tsxclient/src/components/navbar.tsxclient/src/components/search.tsxclient/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/30和bg-background/82均基于 CSS 变量,在 Tailwind v4 动态数值体系下有效,亮色/暗色主题均能正确跟随 token 切换。
98-103: 无障碍改进质量高,LGTM三点改进均到位:
- 触控目标:
h-[44px] w-[44px]满足 WCAG 2.5.5 建议的最小 44×44 px 点击区域。- 焦点环:
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)。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-row、sm: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_header、footer_text等字段处理方式一致,空值兜底合理,不含敏感信息。
Summary
Verification
Notes