Skip to content

feat(theme): add WindGlass theme#115

Open
MarkJun236 wants to merge 1 commit into
du2333:mainfrom
MarkJun236:feat/theme-WindGlass
Open

feat(theme): add WindGlass theme#115
MarkJun236 wants to merge 1 commit into
du2333:mainfrom
MarkJun236:feat/theme-WindGlass

Conversation

@MarkJun236
Copy link
Copy Markdown

@MarkJun236 MarkJun236 commented May 31, 2026

仅根据主题开发指南新增 WindGlass 主题,没有修改项目业务逻辑代码。该主题沿用默认主题的架构和布局,在视觉上增加玻璃风格,并在部分区域加入打字机效果。
Snipaste_2026-05-31_18-17-12
Snipaste_2026-05-31_18-17-50
Snipaste_2026-05-31_18-18-47
Snipaste_2026-05-31_18-18-30
Snipaste_2026-05-31_18-18-18
Snipaste_2026-05-31_18-18-03

Summary by CodeRabbit

发布说明

  • 新功能
    • 新增 Windglass 主题,提供精致的液态玻璃视觉风格
    • 支持光晕交互效果和沉浸式背景配置
    • Windglass 主题完整支持评论、文章、友情链接、用户认证等全部功能

仅根据主题开发指南新增 WindGlass 主题,没有修改项目业务逻辑代码。该主题沿用默认主题的架构和布局,在视觉上增加玻璃风格,并在部分区域加入打字机效果。
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

📝 Walkthrough

总览

本 PR 添加了全新的 windglass 主题 实现。windglass 是一套基于玻璃拟物化(Liquid Glass)设计风格的完整博客主题,包括配置定义、所有页面和组件、交互效果以及样式系统。共新增 81 个文件,约 5500+ 行代码,涵盖配置管理、前端组件、页面实现和样式设计。

变更内容

主题配置与注册

层级 / 文件 摘要
Schema 与配置模式定义
src/features/config/site-config.schema.ts
为 windglass 主题定义 Zod Schema,包括导航栏名称和背景配置的三层变体(完整、输入、表单),并导出推导类型。
配置解析与服务集成
src/features/config/service/config.service.ts
resolveSiteConfig 中实现 windglass 背景读取、默认回退和配置结构化。
配置管理 UI 表单
src/blog.config.ts, src/features/config/components/site-settings-section.tsx, src/features/config/components/themes/windglass-theme-settings.tsx
后台管理界面中为 windglass 主题新增配置表单,支持导航栏名称、背景图片、视觉参数(不透明度、模糊、过渡)。
主题注册与初始化
src/features/theme/registry.ts, src/features/theme/site-config.helpers.ts
在主题注册表添加 windglass,设置路由配置,更新预加载图片解析。

主题实现

层级 / 文件 摘要
主题入口与配置
src/features/theme/themes/windglass/index.ts, config.ts
windglass 主题导出入口和配置常量(首页文章数、每页数量等)。
交互和工具组件
use-pointer-glow.ts, typewriter-text.tsx, ambient-background.tsx
指针跟踪高光、打字机逐字效果、环境背景动态渲染等核心工具。
主题布局组件
layouts/*
公开、认证、用户页面布局,导航栏、移动菜单、页脚、语言切换等通用组件。
首页与文章列表页
pages/home/*, pages/posts/*, components/post-card.tsx
首页(含打字机问候、社交链接)、文章列表(含标签筛选、分页)及骨架屏。
单篇文章详情页
pages/post/*
文章详情页,包含打字机逐字渲染、正文节点递归、代码块高亮、分享、评论区。
认证相关页面
pages/auth/*
登录、注册、忘记密码、重置密码、邮箱验证等认证流程页面。
辅助功能页面
pages/friend-links/*, pages/search/*, pages/submit-friend-link/*, pages/user/profile/*
友情链接、搜索、提交友情链接、个人资料页面。
评论系统完整实现
components/comments/*
编辑器工具栏、插入链接/图片弹窗、Markdown 渲染、评论列表、回复分页、删除确认等。
完整主题样式表
styles/index.css
玻璃拟物视觉、动画、暗色适配、代码块样式、响应式布局(811 行)。

序列图

sequenceDiagram
  participant User as 用户
  participant Blog as 博客首页
  participant Post as 文章详情
  participant Comment as 评论区
  participant Editor as 评论编辑器

  User->>Blog: 访问首页(加载 windglass 主题)
  Blog->>Blog: 渲染打字机问候语、社交链接、文章卡片
  User->>Post: 点击文章进入详情页
  Post->>Post: 逐字打字机渲染正文、代码块、目录
  Post->>Comment: 渲染评论区
  User->>Comment: 点击"回复"或"发表评论"
  Comment->>Editor: 打开评论编辑器
  Editor->>Editor: TipTap 编辑、工具栏(加粗、链接、图片等)
  User->>Editor: 提交评论(Turnstile 校验)
  Editor->>Comment: 清空编辑器、刷新评论列表
  Comment->>User: 显示已发表评论
Loading

预期审查工作量

🎯 4 (复杂) | ⏱️ ~45 分钟

原因: PR 引入了全新的主题实现,代码行数多(5500+),涉及异构变更(配置、页面、组件、样式、交互),需要逐层验证配置流、页面渲染、编辑器实现和样式一致性。评论系统和打字机效果的实现复杂度较高,需要仔细审查逻辑和性能。

可能相关的 PR

  • du2333/flare-stack-blog#35 — 类似地为 default 主题扩展 theme.default.background 配置并实现背景渲染层,与本 PR 的背景配置和渲染思路直接对应。

建议审查人

  • du2333
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (10)
src/features/config/components/themes/windglass-theme-settings.tsx (2)

94-104: 💤 Low value

考虑为 transitionDuration 字段添加 formatValue。

与 backdropBlur 类似,transitionDuration RangeField 也未设置 formatValue。虽然它是整数值(step: 50),但为了与其他字段保持一致性,建议显式设置格式化函数。

💡 可选的改进建议
 <RangeField
   name="site.theme.windglass.background.transitionDuration"
   label={m.settings_site_field_transition_duration()}
   hint={m.settings_site_field_transition_duration_hint()}
   min={DEFAULT_THEME_TRANSITION_MIN}
   max={DEFAULT_THEME_TRANSITION_MAX}
   step={50}
   unit="ms"
   defaultValue={600}
+  formatValue={(value) => value.toString()}
   error={windglassErrors?.background?.transitionDuration?.message}
 />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/config/components/themes/windglass-theme-settings.tsx` around
lines 94 - 104, The RangeField for transitionDuration
(name="site.theme.windglass.background.transitionDuration") is missing a
formatValue prop; add a formatValue function (e.g., value => `${value} ms` or
similar consistent formatter used elsewhere like for backdropBlur) to normalize
display and maintain consistency with other fields; update the RangeField
component in windglass-theme-settings.tsx (and ensure any related validation
error prop windglassErrors?.background?.transitionDuration?.message remains
unchanged).

83-93: 💤 Low value

考虑为 backdropBlur 字段添加 formatValue。

backdropBlur 的 RangeField(lines 83-93)未设置 formatValue 属性,而 light/dark opacity 字段(lines 61-82)使用了 formatValue={(value) => value.toFixed(2)}。虽然 backdropBlur 是整数(step: 1),不需要小数格式化,但为保持一致性和可读性,建议显式设置 formatValue 或确认 RangeField 组件对整数值有合理的默认格式化行为。

💡 可选的改进建议
 <RangeField
   name="site.theme.windglass.background.backdropBlur"
   label={m.settings_site_field_backdrop_blur()}
   hint={m.settings_site_field_backdrop_blur_hint()}
   min={DEFAULT_THEME_BLUR_MIN}
   max={DEFAULT_THEME_BLUR_MAX}
   step={1}
   unit="px"
   defaultValue={0}
+  formatValue={(value) => value.toString()}
   error={windglassErrors?.background?.backdropBlur?.message}
 />

或验证 RangeField 的默认行为是否已处理整数显示。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/config/components/themes/windglass-theme-settings.tsx` around
lines 83 - 93, RangeField for the backdropBlur setting
(name="site.theme.windglass.background.backdropBlur") lacks a formatValue prop
unlike the other fields; either add an explicit formatValue (e.g., format
integers with value => value.toFixed(0) or value => String(value)) to RangeField
or verify that RangeField's default formatting already displays integers
correctly so the UI is consistent with the light/dark opacity fields.
src/features/config/site-config.schema.ts (1)

273-293: ⚡ Quick win

考虑提取共享的主题配置工厂函数。

createWindglassThemeSiteConfigSchemacreateWindglassThemeSiteConfigInputSchemacreateWindglassThemeSiteConfigInputFormSchema 的实现与 createDefaultThemeSiteConfigSchema 系列完全相同(对比 lines 227-247)。这种重复会导致维护成本增加,若未来需要调整导航栏名称长度或背景配置验证规则,需要同步修改多处。

建议提取通用的工厂函数:

♻️ 提议的重构方案
+function createCommonThemeSiteConfigSchema() {
+  return z.object({
+    navBarName: createSiteTextSchema(60),
+    background: createDefaultThemeBackgroundSchema().optional(),
+  });
+}
+
+function createCommonThemeSiteConfigInputSchema() {
+  return z.object({
+    navBarName: createSiteTextSchema(60).optional(),
+    background: createDefaultThemeBackgroundInputSchema().optional(),
+  });
+}
+
+function createCommonThemeSiteConfigInputFormSchema(messages: Messages) {
+  return z.object({
+    navBarName: createSiteTextFormSchema(60, messages).optional(),
+    background: createDefaultThemeBackgroundInputFormSchema(messages).optional(),
+  });
+}

 function createDefaultThemeSiteConfigSchema() {
-  return z.object({
-    navBarName: createSiteTextSchema(60),
-    background: createDefaultThemeBackgroundSchema().optional(),
-  });
+  return createCommonThemeSiteConfigSchema();
 }

 function createDefaultThemeSiteConfigInputSchema() {
-  return z.object({
-    navBarName: createSiteTextSchema(60).optional(),
-    background: createDefaultThemeBackgroundInputSchema().optional(),
-  });
+  return createCommonThemeSiteConfigInputSchema();
 }

 function createDefaultThemeSiteConfigInputFormSchema(messages: Messages) {
-  return z.object({
-    navBarName: createSiteTextFormSchema(60, messages).optional(),
-    background: createDefaultThemeBackgroundInputFormSchema(messages).optional(),
-  });
+  return createCommonThemeSiteConfigInputFormSchema(messages);
 }

 function createWindglassThemeSiteConfigSchema() {
-  return z.object({
-    navBarName: createSiteTextSchema(60),
-    background: createDefaultThemeBackgroundSchema().optional(),
-  });
+  return createCommonThemeSiteConfigSchema();
 }

 function createWindglassThemeSiteConfigInputSchema() {
-  return z.object({
-    navBarName: createSiteTextSchema(60).optional(),
-    background: createDefaultThemeBackgroundInputSchema().optional(),
-  });
+  return createCommonThemeSiteConfigInputSchema();
 }

 function createWindglassThemeSiteConfigInputFormSchema(messages: Messages) {
-  return z.object({
-    navBarName: createSiteTextFormSchema(60, messages).optional(),
-    background: createDefaultThemeBackgroundInputFormSchema(messages).optional(),
-  });
+  return createCommonThemeSiteConfigInputFormSchema(messages);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/config/site-config.schema.ts` around lines 273 - 293, The three
Windglass functions duplicate the Default-theme trio; extract a shared factory
(e.g., createThemeSiteConfigSchemas or createThemeSiteConfigFactory) that
returns/creates the three variants (schema, inputSchema, inputFormSchema) and
reuse it to implement createWindglassThemeSiteConfigSchema,
createWindglassThemeSiteConfigInputSchema, and
createWindglassThemeSiteConfigInputFormSchema; the factory should accept
parameters for navBarName length (used with
createSiteTextSchema/createSiteTextFormSchema), background schema builders
(createDefaultThemeBackgroundSchema/createDefaultThemeBackgroundInputSchema/createDefaultThemeBackgroundInputFormSchema)
and Messages for the form variant, then replace the duplicated bodies to call
this factory so changes to navBarName length or background validation live in
one place.
src/features/theme/themes/windglass/pages/auth/register/page.tsx (2)

38-38: ⚡ Quick win

建议为表单添加 noValidate 属性。

当使用自定义表单验证逻辑时,浏览器的原生 HTML5 验证可能会先触发,导致与自定义验证消息产生冲突或不一致的用户体验。建议添加 noValidate 属性以禁用浏览器原生验证。

💡 建议的修复
-      <form onSubmit={handleSubmit} className="space-y-5">
+      <form onSubmit={handleSubmit} noValidate className="space-y-5">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/theme/themes/windglass/pages/auth/register/page.tsx` at line 38,
Add the HTML attribute noValidate to the JSX <form> element in the register page
so browser native validation is disabled when using the custom handler; update
the <form onSubmit={handleSubmit} className="space-y-5"> element to include
noValidate (e.g., <form noValidate onSubmit={handleSubmit} ...>) so your custom
validation messaging in the register page's component runs consistently.

94-94: ⚡ Quick win

考虑为 Turnstile 验证码添加视觉说明。

Turnstile 元素直接插入表单中,缺少对用户(特别是使用屏幕阅读器的用户)的说明文本。建议添加一个标签或辅助文本来说明这是安全验证步骤。

♻️ 建议的改进
+        <div className="space-y-2">
+          <span className="wg-kicker">{/* 添加验证码说明文本,如 "安全验证" */}</span>
           {turnstileElement}
+        </div>
-        {turnstileElement}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/theme/themes/windglass/pages/auth/register/page.tsx` at line 94,
在表单中直接插入的 turnstileElement 缺少可见或无障碍的说明,给屏幕阅读器用户和视觉用户添加用于说明这是安全验证步骤的文本;在
turnstileElement 周围添加一个描述性标签或辅助文本(例如一个可见的说明或者带有 sr-only 类的隐藏文本),并通过
aria-describedby 或 aria-labelledby 与 turnstileElement 关联,确保元素有明确的可访问名称/说明(查找
turnstileElement 在 page.tsx 中的位置并将标签/说明与该标识符绑定);同时保留视觉展示并确保文本可翻译/本地化。
src/features/theme/themes/windglass/pages/friend-links/page.tsx (1)

45-56: 💤 Low value

为 logo 图片添加加载失败兜底。

logoUrl 存在但实际加载失败时,会显示破损图片图标,而不会回退到第 53-55 行的首字母占位。可考虑通过 onError 隐藏失败图片或切换到首字母占位,以保证视觉一致性。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/theme/themes/windglass/pages/friend-links/page.tsx` around lines
45 - 56, The img element using link.logoUrl should handle image load failures
and fall back to the initial-letter span; update the component in page.tsx to
track image load errors (e.g., an imageError state) and render the <img> only
when logoUrl exists and imageError is false, and add an onError handler on the
<img> (referencing link.logoUrl and the <img> element) that sets imageError to
true or swaps to a safe fallback so the code falls back to the existing
{link.siteName.slice(0, 1)} span when the image fails to load.
src/features/theme/themes/windglass/styles/index.css (2)

55-291: 考虑 backdrop-filter 的性能影响。

WindGlass 主题在多个组件中密集使用 backdrop-filter 与高斯模糊(12-48px),这是实现玻璃拟物效果的核心技术。但需注意:

  • 性能成本backdrop-filter 在移动设备和低端设备上可能导致渲染性能下降
  • 建议措施
    • 在真实设备上进行性能测试,特别是中低端移动设备
    • 考虑为低端设备提供可选的降级样式(通过 @supports 或 JS 特性检测)
    • 在文档中说明浏览器和性能要求

示例降级方案(可选):

/* 不支持 backdrop-filter 的回退 */
`@supports` not (backdrop-filter: blur(1px)) {
  .wg-glass {
    background: rgba(255, 255, 255, 0.85);
    backdrop-filter: none;
  }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/theme/themes/windglass/styles/index.css` around lines 55 - 291,
The theme heavily uses backdrop-filter (e.g. .wg-glass, .wg-glass-card,
.wg-glass-pill, .wg-glass-subtle, .wg-nav, .wg-footer-glass, .wg-glass-overlay)
which can be costly on low-end/mobile devices—add a feature-detection fallback
and documentation: implement a CSS `@supports` not (backdrop-filter: blur(1px))
block that provides simpler non-blurred backgrounds/borders for those selectors,
optionally add a small JS feature-detect toggle to apply a low-performance
variant class for runtime control, and update the theme docs to note recommended
devices/browsers and to encourage testing on mid/low-end mobiles.

515-560: ⚡ Quick win

考虑减少 !important 的使用。

代码块样式中多次使用 !important(lines 515, 522, 538, 542, 546, 549, 551, 555, 559)可能表明选择器特异性问题。虽然这对覆盖 Shiki 默认样式可能是必要的,但过度使用会降低样式的可维护性。

建议考虑:

  • 提高选择器特异性而非使用 !important
  • 或在注释中明确说明为何必须使用 !important(例如覆盖第三方库内联样式)
♻️ 可选的重构方案

如果 Shiki 使用内联样式,则 !important 是合理的。但对于 pre 元素的 background(lines 515, 522),可以尝试提高特异性:

-.wg-prose .wg-code-block pre {
+.wg-prose .wg-code-block > pre[class*="shiki"] {
   width: max-content;
   min-width: 100%;
   margin: 0;
   border: 0;
   border-radius: 1rem;
-  background: rgba(255, 255, 255, 0.78) !important;
+  background: rgba(255, 255, 255, 0.78);
   /* ... */
 }

如果必须保留 !important,建议添加注释说明原因。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/theme/themes/windglass/styles/index.css` around lines 515 - 560,
The CSS uses many !important overrides in the code-block styles (.wg-prose
.wg-code-block pre, .wg-prose .wg-code-block .shiki, .wg-prose .wg-code-block
.shiki span and their html.dark/.dark variants); replace these by increasing
selector specificity (e.g. target html.dark .wg-prose .wg-code-block pre or add
a more specific parent class) to override Shiki defaults, and only keep
!important if Shiki injects inline styles—if you must keep any !important, add a
short comment next to the specific selector explaining it’s required to override
third‑party inline styles.
src/features/theme/themes/windglass/layouts/language-switcher.tsx (1)

23-34: ⚡ Quick win

补充下拉菜单的可访问性语义。

切换按钮缺少 aria-expanded,且下拉无法通过键盘 Escape 关闭。建议补充 aria-expanded/aria-haspopup 并支持 Escape 收起,以改善屏幕阅读器与键盘用户体验。

♻️ 建议的可访问性增强
       <button
         type="button"
         onClick={() => setIsOpen(!isOpen)}
         className="flex items-center justify-center w-full h-full transition-colors wg-pressable"
+        aria-haspopup="menu"
+        aria-expanded={isOpen}
         aria-label={m.common_switch_language()}
       >

并在 useEffect 中追加键盘监听:

     document.addEventListener("mousedown", onClickOutside);
-    return () => document.removeEventListener("mousedown", onClickOutside);
+    const onKeyDown = (e: KeyboardEvent) => {
+      if (e.key === "Escape") setIsOpen(false);
+    };
+    document.addEventListener("keydown", onKeyDown);
+    return () => {
+      document.removeEventListener("mousedown", onClickOutside);
+      document.removeEventListener("keydown", onKeyDown);
+    };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/theme/themes/windglass/layouts/language-switcher.tsx` around
lines 23 - 34, The language toggle button lacks accessibility semantics and
Escape-key handling: add aria-expanded={isOpen} and aria-haspopup="listbox" (or
"menu") to the button element that uses setIsOpen and isOpen (the
Languages/ChevronDown button), and implement a useEffect in the surrounding
component that registers a keydown listener which, when isOpen is true and
event.key === "Escape", calls setIsOpen(false); ensure the listener is cleaned
up on unmount and when isOpen changes.
src/features/theme/themes/windglass/components/comments/editor/comment-editor.tsx (1)

31-41: ⚡ Quick win

useEditor 在 SSR 下默认不立即渲染:无需强制修改,但可为文档一致性显式设置 immediatelyRender:false

tiptap/reactuseEditor 在 SSR/Next 环境中:若未显式传入 immediatelyRender,会在服务端返回 null(不创建编辑器),从而避免 hydration mismatch;因此你当前代码未必会触发该问题。若希望与 SSR 配置示例保持一致,可显式加上 immediatelyRender: false

🛠️ 可选建议:显式关闭(与文档一致)
   const editor = useEditor({
     extensions: getCommentExtensions(),
     content: "",
     autofocus: autoFocus ? "end" : false,
+    immediatelyRender: false,
     editorProps: {
       attributes: {
         class:
           "min-h-[86px] w-full bg-transparent py-2 text-sm leading-relaxed text-gray-800 focus:outline-none dark:text-white/78",
       },
     },
   });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/features/theme/themes/windglass/components/comments/editor/comment-editor.tsx`
around lines 31 - 41, The useEditor call (const editor = useEditor(...)) should
explicitly set immediatelyRender: false in its options to match SSR/Next
guidance and avoid hydration mismatches; update the useEditor invocation that
currently passes extensions: getCommentExtensions(), content, autofocus and
editorProps to also include immediatelyRender: false so the editor isn't created
during server-side render.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/features/theme/themes/windglass/components/comments/view/confirmation-modal.tsx`:
- Around line 28-49: The modal lacks dialog semantics and Escape-key handling:
add role="dialog" and aria-modal="true" plus either aria-label or
aria-labelledby on the root modal container (the div that currently toggles
classes based on isOpen) and ensure the backdrop/button retains accessible name;
import useEffect from react and add an effect that, when isOpen is true,
attaches a keydown listener that calls onClose() on Escape only if isLoading is
false, and cleans up the listener on unmount or when isOpen changes. Ensure the
listener respects isLoading/isOpen and that aria attributes use the existing
m.common_* labels or a passed title prop to provide an accessible label.

In `@src/features/theme/themes/windglass/layouts/mobile-menu.tsx`:
- Around line 18-22: The menu container div that toggles visibility using isOpen
still leaves its descendants in the focus order; update the fixed container (the
element using className with isOpen) to set the inert attribute when closed
(e.g., inert={!isOpen}) so the whole subtree is removed from the
focus/accessibility tree, and also set aria-hidden={!isOpen} for additional
screen-reader clarity; this change should be applied to the same container that
currently reads isOpen in its className.

In `@src/features/theme/themes/windglass/pages/auth/login/page.tsx`:
- Around line 55-61: The "Forgot password" Link component currently sets
tabIndex={-1}, which removes it from keyboard focus; remove the tabIndex prop
from that Link (the JSX element rendering m.login_forgot_password) so it is
reachable by Tab navigation and retains default focus behavior, ensuring
keyboard accessibility.

In `@src/features/theme/themes/windglass/pages/post/page.tsx`:
- Around line 143-156: The onClick handler currently calls
navigator.clipboard.writeText without checking availability, which can throw a
synchronous TypeError in insecure contexts; update the onClick callback to first
check that navigator.clipboard and navigator.clipboard.writeText exist (and
ideally that window.isSecureContext) before calling writeText, and if
unavailable fall back to an alternative (e.g., selecting a temporary input and
using document.execCommand('copy') or directly showing toast.error). Reference
the onClick handler, navigator.clipboard, writeText, and the
toast.success/toast.error calls when adding the guard and fallback so errors are
caught and the appropriate success/error toast is always shown.

In `@src/features/theme/themes/windglass/pages/posts/page.tsx`:
- Line 127: 分隔线的类 "h-px w-24 bg-white/45" 在浅色模式下对比度不足;在
src/features/theme/themes/windglass/pages/posts/page.tsx 中找到该 <span>(类名包含
bg-white/45)并替换为响应式颜色类,举例:为浅色模式提供较深的灰色(例如 bg-slate-200/60)并为深色模式保留白色半透明(例如
dark:bg-white/45),保持原有尺寸类(h-px w-24);这样可确保在浅/深色主题下都可见且易于识别。

In `@src/features/theme/themes/windglass/pages/search/page.tsx`:
- Around line 35-46: The label element is not associated with the input, so
update the JSX to add a matching id on the input and htmlFor on the label (or
generate a stable unique id via React's useId/useRef) so that
m.search_input_label() is announced and clicking the label focuses the input;
locate the label and input in the search page component (where inputRef, query,
and onQueryChange are used) and add e.g. an id like searchInputId and set <label
htmlFor={searchInputId}> and <input id={searchInputId} ... /> (or use useId() to
produce the id).

In `@src/features/theme/themes/windglass/pages/submit-friend-link/page.tsx`:
- Around line 71-75: The logoUrl input is using the site URL placeholder
m.friend_link_placeholder_site_url() by mistake; update the placeholder prop on
the Input with the correct logo-specific i18n message (e.g. replace
m.friend_link_placeholder_site_url() with the LOGO placeholder like
m.friend_link_placeholder_logo() or m.friend_link_placeholder_logo_url() to
match the screenshot) while keeping the same Input and register("logoUrl")
usage.

In `@src/features/theme/themes/windglass/styles/index.css`:
- Line 352: 在样式声明中修正 CSS 关键字大小写:在使用 color-mix 的 color 属性(即那一行包含 color:
color-mix(...))中将 currentColor 改为小写 currentcolor,以符合 CSS 关键字小写约定并避免规范校验或兼容性问题。
- Around line 294-330: Add prefers-reduced-motion support by disabling the
motion for the interactive classes: inside the existing `@media`
(prefers-reduced-motion: reduce) block, add rules targeting .wg-pressable and
.wg-hover-lift (and their state selectors .wg-pressable:active,
.wg-hover-lift:hover, .wg-hover-lift:hover::after) to set transition:
none!important; animation: none!important; and reset
transform/filter/box-shadow/opacity to their non-animated defaults (e.g.,
transform: none; filter: none; box-shadow: none; opacity: 1) so presses and
hover-lift effects are not animated when users prefer reduced motion.

---

Nitpick comments:
In `@src/features/config/components/themes/windglass-theme-settings.tsx`:
- Around line 94-104: The RangeField for transitionDuration
(name="site.theme.windglass.background.transitionDuration") is missing a
formatValue prop; add a formatValue function (e.g., value => `${value} ms` or
similar consistent formatter used elsewhere like for backdropBlur) to normalize
display and maintain consistency with other fields; update the RangeField
component in windglass-theme-settings.tsx (and ensure any related validation
error prop windglassErrors?.background?.transitionDuration?.message remains
unchanged).
- Around line 83-93: RangeField for the backdropBlur setting
(name="site.theme.windglass.background.backdropBlur") lacks a formatValue prop
unlike the other fields; either add an explicit formatValue (e.g., format
integers with value => value.toFixed(0) or value => String(value)) to RangeField
or verify that RangeField's default formatting already displays integers
correctly so the UI is consistent with the light/dark opacity fields.

In `@src/features/config/site-config.schema.ts`:
- Around line 273-293: The three Windglass functions duplicate the Default-theme
trio; extract a shared factory (e.g., createThemeSiteConfigSchemas or
createThemeSiteConfigFactory) that returns/creates the three variants (schema,
inputSchema, inputFormSchema) and reuse it to implement
createWindglassThemeSiteConfigSchema, createWindglassThemeSiteConfigInputSchema,
and createWindglassThemeSiteConfigInputFormSchema; the factory should accept
parameters for navBarName length (used with
createSiteTextSchema/createSiteTextFormSchema), background schema builders
(createDefaultThemeBackgroundSchema/createDefaultThemeBackgroundInputSchema/createDefaultThemeBackgroundInputFormSchema)
and Messages for the form variant, then replace the duplicated bodies to call
this factory so changes to navBarName length or background validation live in
one place.

In
`@src/features/theme/themes/windglass/components/comments/editor/comment-editor.tsx`:
- Around line 31-41: The useEditor call (const editor = useEditor(...)) should
explicitly set immediatelyRender: false in its options to match SSR/Next
guidance and avoid hydration mismatches; update the useEditor invocation that
currently passes extensions: getCommentExtensions(), content, autofocus and
editorProps to also include immediatelyRender: false so the editor isn't created
during server-side render.

In `@src/features/theme/themes/windglass/layouts/language-switcher.tsx`:
- Around line 23-34: The language toggle button lacks accessibility semantics
and Escape-key handling: add aria-expanded={isOpen} and aria-haspopup="listbox"
(or "menu") to the button element that uses setIsOpen and isOpen (the
Languages/ChevronDown button), and implement a useEffect in the surrounding
component that registers a keydown listener which, when isOpen is true and
event.key === "Escape", calls setIsOpen(false); ensure the listener is cleaned
up on unmount and when isOpen changes.

In `@src/features/theme/themes/windglass/pages/auth/register/page.tsx`:
- Line 38: Add the HTML attribute noValidate to the JSX <form> element in the
register page so browser native validation is disabled when using the custom
handler; update the <form onSubmit={handleSubmit} className="space-y-5"> element
to include noValidate (e.g., <form noValidate onSubmit={handleSubmit} ...>) so
your custom validation messaging in the register page's component runs
consistently.
- Line 94: 在表单中直接插入的 turnstileElement
缺少可见或无障碍的说明,给屏幕阅读器用户和视觉用户添加用于说明这是安全验证步骤的文本;在 turnstileElement
周围添加一个描述性标签或辅助文本(例如一个可见的说明或者带有 sr-only 类的隐藏文本),并通过 aria-describedby 或
aria-labelledby 与 turnstileElement 关联,确保元素有明确的可访问名称/说明(查找 turnstileElement 在
page.tsx 中的位置并将标签/说明与该标识符绑定);同时保留视觉展示并确保文本可翻译/本地化。

In `@src/features/theme/themes/windglass/pages/friend-links/page.tsx`:
- Around line 45-56: The img element using link.logoUrl should handle image load
failures and fall back to the initial-letter span; update the component in
page.tsx to track image load errors (e.g., an imageError state) and render the
<img> only when logoUrl exists and imageError is false, and add an onError
handler on the <img> (referencing link.logoUrl and the <img> element) that sets
imageError to true or swaps to a safe fallback so the code falls back to the
existing {link.siteName.slice(0, 1)} span when the image fails to load.

In `@src/features/theme/themes/windglass/styles/index.css`:
- Around line 55-291: The theme heavily uses backdrop-filter (e.g. .wg-glass,
.wg-glass-card, .wg-glass-pill, .wg-glass-subtle, .wg-nav, .wg-footer-glass,
.wg-glass-overlay) which can be costly on low-end/mobile devices—add a
feature-detection fallback and documentation: implement a CSS `@supports` not
(backdrop-filter: blur(1px)) block that provides simpler non-blurred
backgrounds/borders for those selectors, optionally add a small JS
feature-detect toggle to apply a low-performance variant class for runtime
control, and update the theme docs to note recommended devices/browsers and to
encourage testing on mid/low-end mobiles.
- Around line 515-560: The CSS uses many !important overrides in the code-block
styles (.wg-prose .wg-code-block pre, .wg-prose .wg-code-block .shiki, .wg-prose
.wg-code-block .shiki span and their html.dark/.dark variants); replace these by
increasing selector specificity (e.g. target html.dark .wg-prose .wg-code-block
pre or add a more specific parent class) to override Shiki defaults, and only
keep !important if Shiki injects inline styles—if you must keep any !important,
add a short comment next to the specific selector explaining it’s required to
override third‑party inline styles.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 63493df1-d3f8-4a75-a125-b4cdf1f97594

📥 Commits

Reviewing files that changed from the base of the PR and between e203574 and 09d0565.

📒 Files selected for processing (58)
  • src/blog.config.ts
  • src/features/config/components/site-settings-section.tsx
  • src/features/config/components/themes/windglass-theme-settings.tsx
  • src/features/config/service/config.service.ts
  • src/features/config/site-config.schema.ts
  • src/features/theme/registry.ts
  • src/features/theme/site-config.helpers.ts
  • src/features/theme/themes/windglass/components/ambient-background.tsx
  • src/features/theme/themes/windglass/components/comments/editor/comment-editor-toolbar.tsx
  • src/features/theme/themes/windglass/components/comments/editor/comment-editor.tsx
  • src/features/theme/themes/windglass/components/comments/editor/comment-insert-modal.tsx
  • src/features/theme/themes/windglass/components/comments/view/comment-item.tsx
  • src/features/theme/themes/windglass/components/comments/view/comment-list.tsx
  • src/features/theme/themes/windglass/components/comments/view/comment-render.tsx
  • src/features/theme/themes/windglass/components/comments/view/comment-section.tsx
  • src/features/theme/themes/windglass/components/comments/view/confirmation-modal.tsx
  • src/features/theme/themes/windglass/components/comments/view/expandable-content.tsx
  • src/features/theme/themes/windglass/components/post-card.tsx
  • src/features/theme/themes/windglass/components/typewriter-text.tsx
  • src/features/theme/themes/windglass/components/use-pointer-glow.ts
  • src/features/theme/themes/windglass/config.ts
  • src/features/theme/themes/windglass/index.ts
  • src/features/theme/themes/windglass/layouts/auth-layout.tsx
  • src/features/theme/themes/windglass/layouts/footer.tsx
  • src/features/theme/themes/windglass/layouts/language-switcher.tsx
  • src/features/theme/themes/windglass/layouts/mobile-menu.tsx
  • src/features/theme/themes/windglass/layouts/navbar.tsx
  • src/features/theme/themes/windglass/layouts/public-layout.tsx
  • src/features/theme/themes/windglass/layouts/user-layout.tsx
  • src/features/theme/themes/windglass/pages/auth/forgot-password/index.ts
  • src/features/theme/themes/windglass/pages/auth/forgot-password/page.tsx
  • src/features/theme/themes/windglass/pages/auth/login/index.ts
  • src/features/theme/themes/windglass/pages/auth/login/page.tsx
  • src/features/theme/themes/windglass/pages/auth/register/index.ts
  • src/features/theme/themes/windglass/pages/auth/register/page.tsx
  • src/features/theme/themes/windglass/pages/auth/reset-password/index.ts
  • src/features/theme/themes/windglass/pages/auth/reset-password/page.tsx
  • src/features/theme/themes/windglass/pages/auth/verify-email/index.ts
  • src/features/theme/themes/windglass/pages/auth/verify-email/page.tsx
  • src/features/theme/themes/windglass/pages/friend-links/index.ts
  • src/features/theme/themes/windglass/pages/friend-links/page.tsx
  • src/features/theme/themes/windglass/pages/friend-links/skeleton.tsx
  • src/features/theme/themes/windglass/pages/home/index.ts
  • src/features/theme/themes/windglass/pages/home/page.tsx
  • src/features/theme/themes/windglass/pages/home/skeleton.tsx
  • src/features/theme/themes/windglass/pages/post/index.ts
  • src/features/theme/themes/windglass/pages/post/page.tsx
  • src/features/theme/themes/windglass/pages/post/skeleton.tsx
  • src/features/theme/themes/windglass/pages/posts/index.ts
  • src/features/theme/themes/windglass/pages/posts/page.tsx
  • src/features/theme/themes/windglass/pages/posts/skeleton.tsx
  • src/features/theme/themes/windglass/pages/search/index.ts
  • src/features/theme/themes/windglass/pages/search/page.tsx
  • src/features/theme/themes/windglass/pages/submit-friend-link/index.ts
  • src/features/theme/themes/windglass/pages/submit-friend-link/page.tsx
  • src/features/theme/themes/windglass/pages/user/profile/index.ts
  • src/features/theme/themes/windglass/pages/user/profile/page.tsx
  • src/features/theme/themes/windglass/styles/index.css

Comment on lines +28 to +49
return createPortal(
<div
className={`fixed inset-0 z-100 flex items-center justify-center p-4 transition-all duration-300 md:p-6 ${
isOpen
? "pointer-events-auto opacity-100"
: "pointer-events-none opacity-0"
}`}
>
<button
type="button"
className="absolute inset-0 bg-slate-950/28 backdrop-blur-2xl dark:bg-black/55"
onClick={isLoading ? undefined : () => onClose()}
aria-label={m.common_cancel()}
/>

<div
className={`wg-glass-card relative flex w-full max-w-md flex-col rounded-[2rem] transition-all duration-300 ${
isOpen
? "translate-y-0 scale-100 opacity-100"
: "translate-y-4 scale-95 opacity-0"
}`}
>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

确认弹窗缺少对话框语义与键盘可达性。

弹窗容器未设置 role="dialog"aria-modal="true"aria-label/aria-labelledby,也未处理 Escape 键关闭,键盘与屏幕阅读器用户难以感知和退出该模态框。建议补充对话框语义并支持 Esc 关闭(加载中时禁用)。

♿ 建议补充对话框语义与 Esc 关闭
+  useEffect(() => {
+    if (!isOpen) return;
+    const onKeyDown = (e: KeyboardEvent) => {
+      if (e.key === "Escape" && !isLoading) onClose();
+    };
+    window.addEventListener("keydown", onKeyDown);
+    return () => window.removeEventListener("keydown", onKeyDown);
+  }, [isOpen, isLoading, onClose]);
+
   return createPortal(
     <div
+      role="dialog"
+      aria-modal="true"
+      aria-label={title}
       className={`fixed inset-0 z-100 flex items-center justify-center p-4 transition-all duration-300 md:p-6 ${

需相应从 react 导入 useEffect

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/features/theme/themes/windglass/components/comments/view/confirmation-modal.tsx`
around lines 28 - 49, The modal lacks dialog semantics and Escape-key handling:
add role="dialog" and aria-modal="true" plus either aria-label or
aria-labelledby on the root modal container (the div that currently toggles
classes based on isOpen) and ensure the backdrop/button retains accessible name;
import useEffect from react and add an effect that, when isOpen is true,
attaches a keydown listener that calls onClose() on Escape only if isLoading is
false, and cleans up the listener on unmount or when isOpen changes. Ensure the
listener respects isLoading/isOpen and that aria attributes use the existing
m.common_* labels or a passed title prop to provide an accessible label.

Comment on lines +18 to +22
<div
className={`fixed inset-0 z-100 transition-all duration-400 ease-in-out ${
isOpen ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"
}`}
>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does React 19 support the inert attribute on JSX elements as a boolean prop?

💡 Result:

Yes. React 19 supports the inert attribute as a boolean prop on JSX elements (so you can write <dialog inert /> or <dialog inert={true} />). React DOM also intentionally changed behavior so that the old workaround inert="" is treated as false/removed for boolean props in React 19-era handling. Evidence: the React DOM pull request for inert adds support for boolean values and notes that “the previous workaround (inert="") will no longer work since the empty string is considered false for boolean props” [1]. The React issue discussion likewise indicates “boolean values for inert … land it in stable in React 19” and describes migration away from inert="" [2]. A React 19 community upgrade report explicitly states “The inert attribute is now a boolean in React 19” and shows examples using <div inert /> and inert={true} [3].

Citations:


菜单关闭时应把子树设为 inert,避免不可见项仍可被键盘聚焦

关闭状态仅通过 opacity-0 + pointer-events-none 隐藏,但内部的 Link/按钮仍保留在 Tab 焦点顺序中,键盘用户仍会聚焦到不可见菜单项。React 19 支持在 JSX 上使用 inert 作为布尔属性,可在关闭时将整棵子树移出焦点/可访问性树。

🛡️ 建议修复
     <div
+      inert={!isOpen}
       className={`fixed inset-0 z-100 transition-all duration-400 ease-in-out ${
         isOpen ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"
       }`}
     >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div
className={`fixed inset-0 z-100 transition-all duration-400 ease-in-out ${
isOpen ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"
}`}
>
<div
inert={!isOpen}
className={`fixed inset-0 z-100 transition-all duration-400 ease-in-out ${
isOpen ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"
}`}
>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/theme/themes/windglass/layouts/mobile-menu.tsx` around lines 18
- 22, The menu container div that toggles visibility using isOpen still leaves
its descendants in the focus order; update the fixed container (the element
using className with isOpen) to set the inert attribute when closed (e.g.,
inert={!isOpen}) so the whole subtree is removed from the focus/accessibility
tree, and also set aria-hidden={!isOpen} for additional screen-reader clarity;
this change should be applied to the same container that currently reads isOpen
in its className.

Comment on lines +55 to +61
<Link
to="/forgot-password"
tabIndex={-1}
className="text-xs text-gray-500 dark:text-white/42 hover:text-gray-900 dark:hover:text-white"
>
{m.login_forgot_password()}
</Link>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

移除 tabIndex={-1} 以改善键盘导航可访问性。

在"忘记密码"链接上设置 tabIndex={-1} 会将其从键盘 Tab 顺序中移除,导致键盘用户无法通过 Tab 键访问此链接。这违反了 WCAG 2.1.1 键盘可访问性指南。

♿ 建议的修复
             <span className="flex items-center justify-between gap-3">
               <span className="wg-kicker">{m.login_password()}</span>
               <Link
                 to="/forgot-password"
-                tabIndex={-1}
                 className="text-xs text-gray-500 dark:text-white/42 hover:text-gray-900 dark:hover:text-white"
               >
                 {m.login_forgot_password()}
               </Link>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Link
to="/forgot-password"
tabIndex={-1}
className="text-xs text-gray-500 dark:text-white/42 hover:text-gray-900 dark:hover:text-white"
>
{m.login_forgot_password()}
</Link>
<Link
to="/forgot-password"
className="text-xs text-gray-500 dark:text-white/42 hover:text-gray-900 dark:hover:text-white"
>
{m.login_forgot_password()}
</Link>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/theme/themes/windglass/pages/auth/login/page.tsx` around lines
55 - 61, The "Forgot password" Link component currently sets tabIndex={-1},
which removes it from keyboard focus; remove the tabIndex prop from that Link
(the JSX element rendering m.login_forgot_password) so it is reachable by Tab
navigation and retains default focus behavior, ensuring keyboard accessibility.

Comment on lines +143 to +156
onClick={() => {
navigator.clipboard
.writeText(window.location.href)
.then(() => {
toast.success(m.post_share_success(), {
description: m.post_share_success_desc(),
});
})
.catch(() => {
toast.error(m.post_share_error(), {
description: m.post_share_error_desc(),
});
});
}}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

复制链接前应检查 navigator.clipboard 是否可用。

在非安全上下文(HTTP)或部分旧浏览器中 navigator.clipboard 可能为 undefined,此时访问 .writeText 会抛出同步 TypeError,无法被 .catch() 捕获,导致点击分享按钮报错且不触发错误提示。

🛡️ 建议增加可用性判断
                 onClick={() => {
-                  navigator.clipboard
-                    .writeText(window.location.href)
-                    .then(() => {
+                  if (!navigator.clipboard?.writeText) {
+                    toast.error(m.post_share_error(), {
+                      description: m.post_share_error_desc(),
+                    });
+                    return;
+                  }
+                  navigator.clipboard
+                    .writeText(window.location.href)
+                    .then(() => {
                       toast.success(m.post_share_success(), {
                         description: m.post_share_success_desc(),
                       });
                     })
                     .catch(() => {
                       toast.error(m.post_share_error(), {
                         description: m.post_share_error_desc(),
                       });
                     });
                 }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onClick={() => {
navigator.clipboard
.writeText(window.location.href)
.then(() => {
toast.success(m.post_share_success(), {
description: m.post_share_success_desc(),
});
})
.catch(() => {
toast.error(m.post_share_error(), {
description: m.post_share_error_desc(),
});
});
}}
onClick={() => {
if (!navigator.clipboard?.writeText) {
toast.error(m.post_share_error(), {
description: m.post_share_error_desc(),
});
return;
}
navigator.clipboard
.writeText(window.location.href)
.then(() => {
toast.success(m.post_share_success(), {
description: m.post_share_success_desc(),
});
})
.catch(() => {
toast.error(m.post_share_error(), {
description: m.post_share_error_desc(),
});
});
}}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/theme/themes/windglass/pages/post/page.tsx` around lines 143 -
156, The onClick handler currently calls navigator.clipboard.writeText without
checking availability, which can throw a synchronous TypeError in insecure
contexts; update the onClick callback to first check that navigator.clipboard
and navigator.clipboard.writeText exist (and ideally that
window.isSecureContext) before calling writeText, and if unavailable fall back
to an alternative (e.g., selecting a temporary input and using
document.execCommand('copy') or directly showing toast.error). Reference the
onClick handler, navigator.clipboard, writeText, and the
toast.success/toast.error calls when adding the guard and fallback so errors are
caught and the appropriate success/error toast is always shown.

{m.posts_loading()}
</span>
) : hasNextPage ? (
<span className="h-px w-24 bg-white/45" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

分隔线在浅色模式下可能不可见。

bg-white/45 在深色背景上可见,但在浅色模式的浅色背景上会几乎不可见。建议使用响应式颜色类以确保在两种模式下都有良好的对比度。

🎨 建议的颜色修复
-) : hasNextPage ? (
-  <span className="h-px w-24 bg-white/45" />
+) : hasNextPage ? (
+  <span className="h-px w-24 bg-gray-300 dark:bg-white/45" />
 ) : posts.length > 0 ? (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<span className="h-px w-24 bg-white/45" />
<span className="h-px w-24 bg-gray-300 dark:bg-white/45" />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/theme/themes/windglass/pages/posts/page.tsx` at line 127, 分隔线的类
"h-px w-24 bg-white/45" 在浅色模式下对比度不足;在
src/features/theme/themes/windglass/pages/posts/page.tsx 中找到该 <span>(类名包含
bg-white/45)并替换为响应式颜色类,举例:为浅色模式提供较深的灰色(例如 bg-slate-200/60)并为深色模式保留白色半透明(例如
dark:bg-white/45),保持原有尺寸类(h-px w-24);这样可确保在浅/深色主题下都可见且易于识别。

Comment on lines +35 to +46
<label className="wg-kicker mb-3 flex items-center gap-2">
<Search size={14} />
<span>{m.search_input_label()}</span>
</label>
<input
ref={inputRef}
type="text"
value={query}
onChange={(event) => onQueryChange(event.target.value)}
placeholder="..."
className="w-full bg-transparent text-4xl md:text-6xl font-semibold tracking-tight text-gray-900 dark:text-white/90 placeholder:text-gray-400/30 dark:placeholder:text-white/15 focus:outline-none"
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

label 未与输入框关联。

<label> 既未包裹 <input>,也没有 htmlFor/id 关联,屏幕阅读器无法将标签播报给输入框,点击标签也不会聚焦输入。建议补充 htmlFor/id

♿ 建议修复
-        <label className="wg-kicker mb-3 flex items-center gap-2">
+        <label htmlFor="wg-search-input" className="wg-kicker mb-3 flex items-center gap-2">
           <Search size={14} />
           <span>{m.search_input_label()}</span>
         </label>
         <input
+          id="wg-search-input"
           ref={inputRef}
           type="text"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<label className="wg-kicker mb-3 flex items-center gap-2">
<Search size={14} />
<span>{m.search_input_label()}</span>
</label>
<input
ref={inputRef}
type="text"
value={query}
onChange={(event) => onQueryChange(event.target.value)}
placeholder="..."
className="w-full bg-transparent text-4xl md:text-6xl font-semibold tracking-tight text-gray-900 dark:text-white/90 placeholder:text-gray-400/30 dark:placeholder:text-white/15 focus:outline-none"
/>
<label htmlFor="wg-search-input" className="wg-kicker mb-3 flex items-center gap-2">
<Search size={14} />
<span>{m.search_input_label()}</span>
</label>
<input
id="wg-search-input"
ref={inputRef}
type="text"
value={query}
onChange={(event) => onQueryChange(event.target.value)}
placeholder="..."
className="w-full bg-transparent text-4xl md:text-6xl font-semibold tracking-tight text-gray-900 dark:text-white/90 placeholder:text-gray-400/30 dark:placeholder:text-white/15 focus:outline-none"
/>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/theme/themes/windglass/pages/search/page.tsx` around lines 35 -
46, The label element is not associated with the input, so update the JSX to add
a matching id on the input and htmlFor on the label (or generate a stable unique
id via React's useId/useRef) so that m.search_input_label() is announced and
clicking the label focuses the input; locate the label and input in the search
page component (where inputRef, query, and onQueryChange are used) and add e.g.
an id like searchInputId and set <label htmlFor={searchInputId}> and <input
id={searchInputId} ... /> (or use useId() to produce the id).

Comment on lines +71 to +75
<Input
{...register("logoUrl")}
className="wg-field h-12 px-4 shadow-none font-mono text-sm"
placeholder={m.friend_link_placeholder_site_url()}
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

LOGO 字段占位符疑似复制粘贴错误。

logoUrl 字段使用了 m.friend_link_placeholder_site_url()(与上方站点网址字段相同),与 PR 截图中 LOGO 字段展示的 https://... 占位不符。请确认是否应使用 LOGO 专属占位符消息。

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/theme/themes/windglass/pages/submit-friend-link/page.tsx` around
lines 71 - 75, The logoUrl input is using the site URL placeholder
m.friend_link_placeholder_site_url() by mistake; update the placeholder prop on
the Input with the correct logo-specific i18n message (e.g. replace
m.friend_link_placeholder_site_url() with the LOGO placeholder like
m.friend_link_placeholder_logo() or m.friend_link_placeholder_logo_url() to
match the screenshot) while keeping the same Input and register("logoUrl")
usage.

Comment on lines +294 to +330
.wg-pressable {
transition:
transform 160ms cubic-bezier(0.2, 0.8, 0.2, 1),
filter 160ms ease,
box-shadow 160ms ease;
touch-action: manipulation;
}

.wg-pressable:active {
transform: scale(0.97);
filter: brightness(1.08);
}

/* Hover float lift */
.wg-hover-lift {
transition:
transform 280ms cubic-bezier(0.34, 1.56, 0.64, 1),
box-shadow 280ms ease;
}

.wg-hover-lift:hover {
transform: translateY(-3px);
box-shadow:
0 14px 40px rgba(0, 0, 0, 0.10),
inset 0 1px 1px rgba(255, 255, 255, 0.7),
inset 0 -0.5px 0.5px rgba(255, 255, 255, 0.2);
}

.wg-hover-lift:hover::after {
opacity: 0.75;
}

.dark .wg-hover-lift:hover {
box-shadow:
0 12px 36px rgba(0, 0, 0, 0.4),
inset 0 0.5px 0.5px rgba(255, 255, 255, 0.15);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

为交互动画添加 prefers-reduced-motion 支持。

.wg-pressable.wg-hover-lift 的动画过渡效果未在用户启用减少动画首选项时禁用。这可能对患有前庭运动障碍的用户造成不适,影响无障碍访问合规性(WCAG 2.1 标准 2.3.3)。

建议在现有的 @media (prefers-reduced-motion: reduce) 规则(line 805-811)中添加这些类的动画禁用:

♿ 建议的修复
 `@media` (prefers-reduced-motion: reduce) {
   .wg-typewriter-char,
   .wg-typewriter-cursor,
-  .wg-shimmer::after {
+  .wg-shimmer::after,
+  .wg-pressable,
+  .wg-hover-lift {
     animation: none;
+    transition: none;
   }
+  
+  .wg-pressable:active {
+    transform: none;
+    filter: none;
+  }
+  
+  .wg-hover-lift:hover {
+    transform: none;
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/theme/themes/windglass/styles/index.css` around lines 294 - 330,
Add prefers-reduced-motion support by disabling the motion for the interactive
classes: inside the existing `@media` (prefers-reduced-motion: reduce) block, add
rules targeting .wg-pressable and .wg-hover-lift (and their state selectors
.wg-pressable:active, .wg-hover-lift:hover, .wg-hover-lift:hover::after) to set
transition: none!important; animation: none!important; and reset
transform/filter/box-shadow/opacity to their non-animated defaults (e.g.,
transform: none; filter: none; box-shadow: none; opacity: 1) so presses and
hover-lift effects are not animated when users prefer reduced motion.

font-weight: 650;
text-transform: uppercase;
letter-spacing: 0.16em;
color: color-mix(in srgb, currentColor 42%, transparent);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

修正 CSS 关键字大小写。

按照 CSS 约定,关键字应使用小写形式。将 currentColor 改为 currentcolor

🔧 建议的修复
-    color: color-mix(in srgb, currentColor 42%, transparent);
+    color: color-mix(in srgb, currentcolor 42%, transparent);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
color: color-mix(in srgb, currentColor 42%, transparent);
color: color-mix(in srgb, currentcolor 42%, transparent);
🧰 Tools
🪛 Stylelint (17.12.0)

[error] 352-352: Expected "currentColor" to be "currentcolor" (value-keyword-case)

(value-keyword-case)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/theme/themes/windglass/styles/index.css` at line 352, 在样式声明中修正
CSS 关键字大小写:在使用 color-mix 的 color 属性(即那一行包含 color: color-mix(...))中将 currentColor
改为小写 currentcolor,以符合 CSS 关键字小写约定并避免规范校验或兼容性问题。

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.

1 participant