feat(theme): add WindGlass theme#115
Conversation
仅根据主题开发指南新增 WindGlass 主题,没有修改项目业务逻辑代码。该主题沿用默认主题的架构和布局,在视觉上增加玻璃风格,并在部分区域加入打字机效果。
📝 Walkthrough总览本 PR 添加了全新的 windglass 主题 实现。windglass 是一套基于玻璃拟物化(Liquid Glass)设计风格的完整博客主题,包括配置定义、所有页面和组件、交互效果以及样式系统。共新增 81 个文件,约 5500+ 行代码,涵盖配置管理、前端组件、页面实现和样式设计。 变更内容主题配置与注册
主题实现
序列图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: 显示已发表评论
预期审查工作量🎯 4 (复杂) | ⏱️ ~45 分钟 原因: PR 引入了全新的主题实现,代码行数多(5500+),涉及异构变更(配置、页面、组件、样式、交互),需要逐层验证配置流、页面渲染、编辑器实现和样式一致性。评论系统和打字机效果的实现复杂度较高,需要仔细审查逻辑和性能。 可能相关的 PR
建议审查人
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
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考虑提取共享的主题配置工厂函数。
createWindglassThemeSiteConfigSchema、createWindglassThemeSiteConfigInputSchema和createWindglassThemeSiteConfigInputFormSchema的实现与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 winuseEditor 在 SSR 下默认不立即渲染:无需强制修改,但可为文档一致性显式设置
immediatelyRender:false
tiptap/react的useEditor在 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
📒 Files selected for processing (58)
src/blog.config.tssrc/features/config/components/site-settings-section.tsxsrc/features/config/components/themes/windglass-theme-settings.tsxsrc/features/config/service/config.service.tssrc/features/config/site-config.schema.tssrc/features/theme/registry.tssrc/features/theme/site-config.helpers.tssrc/features/theme/themes/windglass/components/ambient-background.tsxsrc/features/theme/themes/windglass/components/comments/editor/comment-editor-toolbar.tsxsrc/features/theme/themes/windglass/components/comments/editor/comment-editor.tsxsrc/features/theme/themes/windglass/components/comments/editor/comment-insert-modal.tsxsrc/features/theme/themes/windglass/components/comments/view/comment-item.tsxsrc/features/theme/themes/windglass/components/comments/view/comment-list.tsxsrc/features/theme/themes/windglass/components/comments/view/comment-render.tsxsrc/features/theme/themes/windglass/components/comments/view/comment-section.tsxsrc/features/theme/themes/windglass/components/comments/view/confirmation-modal.tsxsrc/features/theme/themes/windglass/components/comments/view/expandable-content.tsxsrc/features/theme/themes/windglass/components/post-card.tsxsrc/features/theme/themes/windglass/components/typewriter-text.tsxsrc/features/theme/themes/windglass/components/use-pointer-glow.tssrc/features/theme/themes/windglass/config.tssrc/features/theme/themes/windglass/index.tssrc/features/theme/themes/windglass/layouts/auth-layout.tsxsrc/features/theme/themes/windglass/layouts/footer.tsxsrc/features/theme/themes/windglass/layouts/language-switcher.tsxsrc/features/theme/themes/windglass/layouts/mobile-menu.tsxsrc/features/theme/themes/windglass/layouts/navbar.tsxsrc/features/theme/themes/windglass/layouts/public-layout.tsxsrc/features/theme/themes/windglass/layouts/user-layout.tsxsrc/features/theme/themes/windglass/pages/auth/forgot-password/index.tssrc/features/theme/themes/windglass/pages/auth/forgot-password/page.tsxsrc/features/theme/themes/windglass/pages/auth/login/index.tssrc/features/theme/themes/windglass/pages/auth/login/page.tsxsrc/features/theme/themes/windglass/pages/auth/register/index.tssrc/features/theme/themes/windglass/pages/auth/register/page.tsxsrc/features/theme/themes/windglass/pages/auth/reset-password/index.tssrc/features/theme/themes/windglass/pages/auth/reset-password/page.tsxsrc/features/theme/themes/windglass/pages/auth/verify-email/index.tssrc/features/theme/themes/windglass/pages/auth/verify-email/page.tsxsrc/features/theme/themes/windglass/pages/friend-links/index.tssrc/features/theme/themes/windglass/pages/friend-links/page.tsxsrc/features/theme/themes/windglass/pages/friend-links/skeleton.tsxsrc/features/theme/themes/windglass/pages/home/index.tssrc/features/theme/themes/windglass/pages/home/page.tsxsrc/features/theme/themes/windglass/pages/home/skeleton.tsxsrc/features/theme/themes/windglass/pages/post/index.tssrc/features/theme/themes/windglass/pages/post/page.tsxsrc/features/theme/themes/windglass/pages/post/skeleton.tsxsrc/features/theme/themes/windglass/pages/posts/index.tssrc/features/theme/themes/windglass/pages/posts/page.tsxsrc/features/theme/themes/windglass/pages/posts/skeleton.tsxsrc/features/theme/themes/windglass/pages/search/index.tssrc/features/theme/themes/windglass/pages/search/page.tsxsrc/features/theme/themes/windglass/pages/submit-friend-link/index.tssrc/features/theme/themes/windglass/pages/submit-friend-link/page.tsxsrc/features/theme/themes/windglass/pages/user/profile/index.tssrc/features/theme/themes/windglass/pages/user/profile/page.tsxsrc/features/theme/themes/windglass/styles/index.css
| 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" | ||
| }`} | ||
| > |
There was a problem hiding this comment.
确认弹窗缺少对话框语义与键盘可达性。
弹窗容器未设置 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.
| <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" | ||
| }`} | ||
| > |
There was a problem hiding this comment.
🧩 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:
- 1: React DOM: Support boolean values for
inertprop facebook/react#24730 - 2: [DOM] Add support for the
inertattribute facebook/react#17157 - 3: https://make.wordpress.org/core/2026/05/27/react-19-upgrade-in-wordpress/
菜单关闭时应把子树设为 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.
| <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.
| <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> |
There was a problem hiding this comment.
移除 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.
| <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.
| 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(), | ||
| }); | ||
| }); | ||
| }} |
There was a problem hiding this comment.
复制链接前应检查 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.
| 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" /> |
There was a problem hiding this comment.
分隔线在浅色模式下可能不可见。
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.
| <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);这样可确保在浅/深色主题下都可见且易于识别。
| <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" | ||
| /> |
There was a problem hiding this comment.
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.
| <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).
| <Input | ||
| {...register("logoUrl")} | ||
| className="wg-field h-12 px-4 shadow-none font-mono text-sm" | ||
| placeholder={m.friend_link_placeholder_site_url()} | ||
| /> |
There was a problem hiding this comment.
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.
| .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); | ||
| } |
There was a problem hiding this comment.
为交互动画添加 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); |
There was a problem hiding this comment.
修正 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.
| 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 关键字小写约定并避免规范校验或兼容性问题。
仅根据主题开发指南新增 WindGlass 主题,没有修改项目业务逻辑代码。该主题沿用默认主题的架构和布局,在视觉上增加玻璃风格,并在部分区域加入打字机效果。






Summary by CodeRabbit
发布说明