fix(ui): 服务商表单小屏体验与客户端限制配置#816
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthrough将大量固定 vh/dvh 高度改为基于新 CSS 变量 --cch-viewport-height-* 的计算并添加 safe-area 支持;providers 表单移动端导航与步骤进度更新;新增 ClientRestrictionsEditor;E2E 改为登录令牌流程;Redis 客户端类型与生命周期重构。 Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @tesgth032, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 此拉取请求主要解决了服务商表单在小屏和移动设备上的自适应问题,特别是修复了提交按钮被底部导航遮挡的缺陷。通过调整表单布局和统一对话框内容样式,提升了用户体验。此外,还包含了对单元测试配置的改进,以增强测试的稳定性和兼容性。 Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
| <div className="order-2 md:order-1 shrink-0"> | ||
| {/* Tab Navigation */} | ||
| <FormTabNav | ||
| activeTab={state.ui.activeTab} | ||
| onTabChange={handleTabChange} | ||
| disabled={isPending} | ||
| tabStatus={getTabStatus()} | ||
| /> | ||
| </div> |
There was a problem hiding this comment.
On small screens (< md breakpoint), the nav appears below content due to order-2 md:order-1, but on mobile the nav should typically be at the bottom for thumb accessibility. Check that this ordering matches the intended UX.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx
Line: 556-564
Comment:
On small screens (< `md` breakpoint), the nav appears below content due to `order-2 md:order-1`, but on mobile the nav should typically be at the bottom for thumb accessibility. Check that this ordering matches the intended UX.
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Code Review
本次 PR 主要包含两方面的改进:修复服务商表单在小屏幕/移动端的自适应布局问题,以及提升测试的稳定性。在 UI 方面,通过将之前在小屏幕上遮挡内容区域的固定底部导航栏,调整为文档流的一部分,成功解决了提交按钮被遮挡的问题。同时,统一了多个组件中 DialogContent 的样式,采用 p-0 和 overflow-hidden 等一致的布局属性,避免了双层内边距等问题,提升了布局的统一性和可维护性。使用 Flexbox 的 order 属性进行响应式布局调整,是一个清晰且有效的实现方式。在测试稳定性方面,简化了 docker-executor.test.ts 中对 Node.js 内置模块的 mock 方式,这有助于避免在某些 Vitest 环境下因模块提升(hoisting)而导致的加载错误。此外,更新了 Vitest 配置,允许通过环境变量设置 maxWorkers,从而更好地控制测试期间的资源占用。总体而言,这些变更是目标明确、实现清晰的改进,同时提升了用户体验和开发环境的稳定性。代码质量很高,我没有发现需要修改的问题。
There was a problem hiding this comment.
Code Review Summary
This PR addresses a mobile/small-screen UI issue where the provider form submit button was being obscured by the fixed bottom navigation. The changes are well-targeted and minimal.
PR Size: S
- Lines changed: 63 (35 additions, 28 deletions)
- Files changed: 8
Issues Found
| Category | Critical | High | Medium | Low |
|---|---|---|---|---|
| Logic/Bugs | 0 | 0 | 0 | 0 |
| Security | 0 | 0 | 0 | 0 |
| Error Handling | 0 | 0 | 0 | 0 |
| Types | 0 | 0 | 0 | 0 |
| Comments/Docs | 0 | 0 | 0 | 0 |
| Tests | 0 | 0 | 0 | 0 |
| Simplification | 0 | 0 | 0 | 0 |
Change Analysis
UI Layout Fix (Primary Change)
form-tab-nav.tsx:214: Changed mobile navigation fromfixed bottom-0 left-0 right-0 z-50toshrink-0 relative- correctly removes the fixed positioning that was covering the submit buttonprovider-form/index.tsx:553-570: WrappedFormTabNavin a div withorder-2 md:order-1and changed content area toorder-1 md:order-2- this properly reorders the layout for mobile while maintaining desktop order- Removed
pb-24 md:pb-6padding from content area since fixed positioning is no longer used
Dialog Consistency
- 6 files: Added
overflow-hidden p-0 gap-0to DialogContent for consistent padding/overflow handling across all provider dialogs
Test Infrastructure
docker-executor.test.ts: Simplified mock pattern by removingimportOriginal, which resolves Vitest compatibility issuesvitest.config.ts: AddedparsePositiveInthelper andmaxWorkersconfig to limit worker count (defaults to 8, configurable viaVITEST_MAX_WORKERS)
Validation Performed
- Verified
parsePositiveInthandles all edge cases (undefined, NaN, non-positive values) - Confirmed the layout reordering logic is correct for both mobile and desktop viewports
- Checked that the mock changes don't affect test coverage
Review Coverage
- Logic and correctness - Clean
- Security (OWASP Top 10) - Clean
- Error handling - Clean
- Type safety - Clean
- Documentation accuracy - Clean
- Test coverage - Adequate (CSS changes, test infrastructure improved)
- Code clarity - Good
Automated review by Claude AI
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/app/[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx (2)
268-268:findIndex回调参数t遮蔽了外层翻译函数t第 32 行定义了
const t = useTranslations("settings.providers.form"),而此处TAB_CONFIG.findIndex((t) => t.id === activeTab)中的参数t遮蔽了外层的翻译函数。虽然当前逻辑正确(t在此 lambda 内未被调用为翻译函数),但容易造成误解,建议改用更具描述性的参数名。建议修复
- width: `${((TAB_CONFIG.findIndex((t) => t.id === activeTab) + 1) / TAB_CONFIG.length) * 100}%`, + width: `${((TAB_CONFIG.findIndex((tab) => tab.id === activeTab) + 1) / TAB_CONFIG.length) * 100}%`,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx at line 268, The findIndex callback parameter shadows the outer translation function const t = useTranslations("settings.providers.form"); update TAB_CONFIG.findIndex((t) => t.id === activeTab) to use a descriptive name (e.g., tab or cfg) to avoid shadowing and confusion—locate the expression in form-tab-nav.tsx where width is computed and replace the callback parameter name while keeping the same comparison against activeTab.
263-272: 进度条初始宽度硬编码为"20%",与 TAB 数量耦合
initial={{ width: "20%" }}硬编码对应 5 个 Tab(1/5 = 20%)。若未来TAB_CONFIG数量变化,初始动画值将不匹配,导致组件首次挂载时出现不必要的动画跳跃(从错误的初始值动画到正确目标值)。建议修复:动态计算初始宽度
- initial={{ width: "20%" }} + initial={{ width: `${(1 / TAB_CONFIG.length) * 100}%` }}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx around lines 263 - 272, The progress bar's initial width is hardcoded to "20%" causing jump when TAB_CONFIG length changes; update the motion.div initial width to compute dynamically using TAB_CONFIG and activeTab (e.g., find the index via TAB_CONFIG.findIndex(t => t.id === activeTab) and set initial width to ((indexFound >= 0 ? indexFound + 1 : 0) / TAB_CONFIG.length) * 100 + '%') so initial and animate widths match; reference the motion.div, TAB_CONFIG and activeTab when making this change and ensure you handle index not found fallback to 0%.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/app/`[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx:
- Line 214: The custom class safe-area-bottom used in components like
form-tab-nav (component FormTabNav) and settings-nav is not defined in Tailwind
or globals.css, so add a utility definition in src/app/globals.css inside `@layer`
utilities that defines .safe-area-bottom to apply bottom padding using the iOS
safe-area inset (e.g. padding-bottom: env(safe-area-inset-bottom); with a
sensible fallback), then rebuild so Tailwind includes the new utility.
---
Nitpick comments:
In
`@src/app/`[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx:
- Line 268: The findIndex callback parameter shadows the outer translation
function const t = useTranslations("settings.providers.form"); update
TAB_CONFIG.findIndex((t) => t.id === activeTab) to use a descriptive name (e.g.,
tab or cfg) to avoid shadowing and confusion—locate the expression in
form-tab-nav.tsx where width is computed and replace the callback parameter name
while keeping the same comparison against activeTab.
- Around line 263-272: The progress bar's initial width is hardcoded to "20%"
causing jump when TAB_CONFIG length changes; update the motion.div initial width
to compute dynamically using TAB_CONFIG and activeTab (e.g., find the index via
TAB_CONFIG.findIndex(t => t.id === activeTab) and set initial width to
((indexFound >= 0 ? indexFound + 1 : 0) / TAB_CONFIG.length) * 100 + '%') so
initial and animate widths match; reference the motion.div, TAB_CONFIG and
activeTab when making this change and ensure you handle index not found fallback
to 0%.
src/app/[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx
Show resolved
Hide resolved
| initial={{ width: stepProgressWidth }} | ||
| animate={{ width: stepProgressWidth }} |
There was a problem hiding this comment.
initial should animate from current width, not jump to target width. Setting both to same value disables entry animation.
| initial={{ width: stepProgressWidth }} | |
| animate={{ width: stepProgressWidth }} | |
| initial={{ width: "0%" }} | |
| animate={{ width: stepProgressWidth }} |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx
Line: 270-271
Comment:
`initial` should animate from current width, not jump to target width. Setting both to same value disables entry animation.
```suggestion
initial={{ width: "0%" }}
animate={{ width: stepProgressWidth }}
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/app/[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx (2)
45-47:stepProgressWidth在horizontal布局下被无效计算
layout === "horizontal"时组件在第 49 行提前返回,但这三个变量在每次渲染时都会被计算。建议将此段逻辑移入vertical分支内部,或包裹在layout !== "horizontal"条件中,避免冗余运算。♻️ 建议重构
- const activeTabIndex = TAB_CONFIG.findIndex((tab) => tab.id === activeTab); - const stepNumber = activeTabIndex >= 0 ? activeTabIndex + 1 : 0; - const stepProgressWidth = `${(stepNumber / TAB_CONFIG.length) * 100}%`; if (layout === "horizontal") { return ( ... ); } + const activeTabIndex = TAB_CONFIG.findIndex((tab) => tab.id === activeTab); + const stepNumber = activeTabIndex >= 0 ? activeTabIndex + 1 : 0; + const stepProgressWidth = `${(stepNumber / TAB_CONFIG.length) * 100}%`; return ( ... );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx around lines 45 - 47, The computed values activeTabIndex, stepNumber and stepProgressWidth are always calculated even when layout === "horizontal" (where the component returns early); move the logic that derives activeTabIndex, stepNumber and stepProgressWidth into the vertical branch (or wrap them with an if (layout !== "horizontal") guard) so those values are only computed when needed—use the existing TAB_CONFIG and activeTab symbols to locate and relocate the calculations (or wrap them) near the rendering logic that handles the vertical layout.
270-271:initial与animate值相同,mount 动画被抑制
initial在首次挂载时仅生效一次;因其值与animate完全相同,首次渲染不会产生入场动画。若希望进度条从0%滑入当前位置,应将initial改为"0%";若刻意跳过入场动画,则可直接删除initial属性(framer-motion 默认行为与此等效)。♻️ 可选调整(带入场动画)
<motion.div className="h-full bg-primary" - initial={{ width: stepProgressWidth }} + initial={{ width: "0%" }} animate={{ width: stepProgressWidth }} transition={{ type: "spring", stiffness: 300, damping: 30 }} />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/`[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx around lines 270 - 271, The component sets the Framer Motion props initial and animate both to the same value (stepProgressWidth), so the mount animation is suppressed; change initial to a fixed starting value (e.g., "0%") to animate the progress bar in from zero, or remove the initial prop entirely if you want to skip the entrance animation—update the JSX where initial={{ width: stepProgressWidth }} / animate={{ width: stepProgressWidth }} (reference: stepProgressWidth in form-tab-nav.tsx) accordingly.src/app/globals.css (1)
138-141: Tailwind v4 中自定义工具类应使用@utility而非@layer utilities在 Tailwind v4 中,
@layer utilities不再由 Tailwind 特殊处理,因此定义在其中的类不会支持hover:、focus:或响应式前缀(如md:)等变体。应改用@utility指令以确保变体正常工作。该类已在以下位置使用:
src/app/[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx:218(无固定定位)src/app/[locale]/settings/_components/settings-nav.tsx:222(带fixed bottom-0)建议按照 Tailwind v4 最佳实践改为
@utility语法:♻️ 建议使用 `@utility` 指令
- /* iOS 安全区:避免底部固定导航被 Home Indicator 挡住 */ - .safe-area-bottom { - padding-bottom: env(safe-area-inset-bottom, 0px); - } +/* iOS 安全区:为底部内容提供 Home Indicator 安全间距 */ +@utility safe-area-bottom { + padding-bottom: env(safe-area-inset-bottom, 0px); +}注:
@utility应置于@layer utilities块之外,与@layer base同级。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/app/globals.css` around lines 138 - 141, The custom .safe-area-bottom utility is defined in a plain CSS layer so Tailwind v4 won't apply hover/focus/responsive variants; move its definition out of any `@layer` utilities block and convert it to a Tailwind `@utility` declaration (placed at the same level as `@layer` base) that sets padding-bottom: env(safe-area-inset-bottom, 0px); this ensures variant support for usages in components such as form-tab-nav.tsx (line with no fixed positioning) and settings-nav.tsx (where it’s used with fixed bottom-0).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@src/app/`[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx:
- Around line 267-274: The visual progress indicator in form-tab-nav.tsx (the
outer div + motion.div used for step progress) lacks accessibility semantics;
update the element representing progress (the motion.div with className "h-full
bg-primary") to include role="progressbar" and ARIA attributes aria-valuenow
(current step progress value), aria-valuemin="0" and aria-valuemax="100", and a
descriptive aria-label or aria-labelledby so screen readers can announce
progress; ensure the code reads the same stepProgressWidth/value used for the
animated width to compute aria-valuenow.
---
Nitpick comments:
In
`@src/app/`[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx:
- Around line 45-47: The computed values activeTabIndex, stepNumber and
stepProgressWidth are always calculated even when layout === "horizontal" (where
the component returns early); move the logic that derives activeTabIndex,
stepNumber and stepProgressWidth into the vertical branch (or wrap them with an
if (layout !== "horizontal") guard) so those values are only computed when
needed—use the existing TAB_CONFIG and activeTab symbols to locate and relocate
the calculations (or wrap them) near the rendering logic that handles the
vertical layout.
- Around line 270-271: The component sets the Framer Motion props initial and
animate both to the same value (stepProgressWidth), so the mount animation is
suppressed; change initial to a fixed starting value (e.g., "0%") to animate the
progress bar in from zero, or remove the initial prop entirely if you want to
skip the entrance animation—update the JSX where initial={{ width:
stepProgressWidth }} / animate={{ width: stepProgressWidth }} (reference:
stepProgressWidth in form-tab-nav.tsx) accordingly.
In `@src/app/globals.css`:
- Around line 138-141: The custom .safe-area-bottom utility is defined in a
plain CSS layer so Tailwind v4 won't apply hover/focus/responsive variants; move
its definition out of any `@layer` utilities block and convert it to a Tailwind
`@utility` declaration (placed at the same level as `@layer` base) that sets
padding-bottom: env(safe-area-inset-bottom, 0px); this ensures variant support
for usages in components such as form-tab-nav.tsx (line with no fixed
positioning) and settings-nav.tsx (where it’s used with fixed bottom-0).
src/app/[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx
Outdated
Show resolved
Hide resolved
tests/e2e/_helpers/auth.ts
Outdated
| return combined | ||
| .split(/,(?=[^;]+?=)/g) | ||
| .map((s) => s.trim()) | ||
| .filter(Boolean); |
There was a problem hiding this comment.
Regex /,(?=[^;]+?=)/g may incorrectly split Set-Cookie headers containing dates (e.g., Expires=Wed, 21 Oct 2026). The lookahead (?=[^;]+?=) prevents splitting at commas within individual cookie attributes, but date values like Expires=Wed, 21 Oct 2026 will still match because "21" is followed by =.
| return combined | |
| .split(/,(?=[^;]+?=)/g) | |
| .map((s) => s.trim()) | |
| .filter(Boolean); | |
| // Use getSetCookie() if available, otherwise parse manually | |
| // Note: Splitting Set-Cookie headers is complex due to date formats | |
| return combined | |
| .split(/,\s*(?=[a-zA-Z][\w-]*=)/g) | |
| .map((s) => s.trim()) | |
| .filter(Boolean); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/e2e/_helpers/auth.ts
Line: 18-21
Comment:
Regex `/,(?=[^;]+?=)/g` may incorrectly split Set-Cookie headers containing dates (e.g., `Expires=Wed, 21 Oct 2026`). The lookahead `(?=[^;]+?=)` prevents splitting at commas within individual cookie attributes, but date values like `Expires=Wed, 21 Oct 2026` will still match because "21" is followed by `=`.
```suggestion
// Use getSetCookie() if available, otherwise parse manually
// Note: Splitting Set-Cookie headers is complex due to date formats
return combined
.split(/,\s*(?=[a-zA-Z][\w-]*=)/g)
.map((s) => s.trim())
.filter(Boolean);
```
How can I resolve this? If you propose a fix, please make it concise.| <div className="order-2 md:order-1 shrink-0"> | ||
| {/* Tab Navigation */} | ||
| <FormTabNav | ||
| activeTab={state.ui.activeTab} | ||
| onTabChange={handleTabChange} | ||
| disabled={isPending} | ||
| tabStatus={getTabStatus()} | ||
| /> | ||
| </div> |
There was a problem hiding this comment.
Wrapping FormTabNav in a separate div with shrink-0 prevents the nav from being a direct flex child of the parent. This adds an extra DOM node that may not be necessary.
| <div className="order-2 md:order-1 shrink-0"> | |
| {/* Tab Navigation */} | |
| <FormTabNav | |
| activeTab={state.ui.activeTab} | |
| onTabChange={handleTabChange} | |
| disabled={isPending} | |
| tabStatus={getTabStatus()} | |
| /> | |
| </div> | |
| {/* Tab Navigation */} | |
| <FormTabNav | |
| className="order-2 md:order-1 shrink-0" | |
| activeTab={state.ui.activeTab} | |
| onTabChange={handleTabChange} | |
| disabled={isPending} | |
| tabStatus={getTabStatus()} | |
| /> |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx
Line: 556-564
Comment:
Wrapping `FormTabNav` in a separate `div` with `shrink-0` prevents the nav from being a direct flex child of the parent. This adds an extra DOM node that may not be necessary.
```suggestion
{/* Tab Navigation */}
<FormTabNav
className="order-2 md:order-1 shrink-0"
activeTab={state.ui.activeTab}
onTabChange={handleTabChange}
disabled={isPending}
tabStatus={getTabStatus()}
/>
```
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.|
|
||
| // ==================== 辅助函数 ==================== | ||
|
|
||
| beforeAll(async () => { | ||
| if (!ADMIN_KEY) return; | ||
| authToken = await loginAndGetAuthToken(API_BASE_URL, ADMIN_KEY); | ||
| }); |
There was a problem hiding this comment.
Using beforeAll for async setup means if login fails, subsequent tests will fail with unclear errors about missing authToken. The error message on line 40-41 helps, but tests will still run and fail individually.
Consider using beforeAll with a skip condition or making the login failure more visible.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/e2e/api-complete.test.ts
Line: 30-36
Comment:
Using `beforeAll` for async setup means if login fails, subsequent tests will fail with unclear errors about missing `authToken`. The error message on line 40-41 helps, but tests will still run and fail individually.
Consider using `beforeAll` with a skip condition or making the login failure more visible.
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
tests/e2e/users-keys-complete.test.ts (1)
37-37:afterAll声明在beforeAll之前,且存在sessionToken/authToken双重命名存在两个小问题:
声明顺序:模块级别的
afterAll(第 171 行)出现在beforeAll(第 192 行)之前。虽然 Vitest 执行时始终先运行beforeAll再运行afterAll,与代码顺序无关,但与api-complete.test.ts及notification-settings.test.ts中"先beforeAll后afterAll"的惯例不一致,会给读者造成困惑。命名不一致:本文件模块变量命名为
sessionToken,但callApi的参数命名为authToken(默认值为sessionToken),在同一文件内形成双重命名,可统一为sessionToken。♻️ 建议调整声明顺序并统一命名
将
beforeAll移至afterAll之前(与其他测试文件对齐):+beforeAll(async () => { + if (!ADMIN_KEY) return; + sessionToken = await loginAndGetAuthToken(API_BASE_URL, ADMIN_KEY); +}); + afterAll(async () => { if (!sessionToken) return; // ... cleanup ... }); - -beforeAll(async () => { - if (!ADMIN_KEY) return; - sessionToken = await loginAndGetAuthToken(API_BASE_URL, ADMIN_KEY); -});统一
callApi参数命名:async function callApi( module: string, action: string, body: Record<string, unknown> = {}, - authToken = sessionToken + sessionToken: string | undefined = sessionToken ) { - if (!authToken) { + if (!sessionToken) { throw new Error("E2E tests require ADMIN_TOKEN/TEST_ADMIN_TOKEN (used to login)"); } const response = await fetch(url, { headers: { - Authorization: `Bearer ${authToken}`, - Cookie: `auth-token=${authToken}`, + Authorization: `Bearer ${sessionToken}`, + Cookie: `auth-token=${sessionToken}`, }, });Also applies to: 65-65, 172-172, 192-195
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/e2e/users-keys-complete.test.ts` at line 37, Move the module-level beforeAll so it appears before the module-level afterAll (make the order match other tests) and rename the inconsistent authToken identifier to sessionToken: update the callApi function signature/default to use sessionToken and replace all authToken uses with sessionToken, ensuring the module variable let sessionToken: string | undefined; is the single source of truth and that afterAll (cleanup) stays after the now-earlier beforeAll setup.tests/e2e/api-complete.test.ts (1)
16-17:callApi/beforeAll认证逻辑在三个 E2E 测试文件中重复
callApi(含 Bearer + Cookie 头)和beforeAll登录流程在api-complete.test.ts、notification-settings.test.ts、users-keys-complete.test.ts中几乎完全相同,可考虑将其提取到tests/e2e/_helpers/api.ts,减少日后维护负担。Also applies to: 33-36, 47-48
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/e2e/api-complete.test.ts` around lines 16 - 17, Extract the duplicated authentication and request helper into a shared module: create tests/e2e/_helpers/api.ts that exports a callApi function which builds requests with Bearer + Cookie headers and a helper to perform the beforeAll login flow (reusing loginAndGetAuthToken) so tests can call that in their beforeAll; then update api-complete.test.ts, notification-settings.test.ts, and users-keys-complete.test.ts to import and use callApi and the shared beforeAll/login helper instead of duplicating the logic in each file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/e2e/_helpers/auth.ts`:
- Around line 24-27: The extractCookieValue function builds a RegExp from the
cookieName which can lead to ReDoS or incorrect matches if cookieName contains
regex metacharacters; change extractCookieValue to parse the Set-Cookie string
with string operations instead: trim the header, split on ';' to get individual
cookie segments, find the segment that starts with cookieName + '=' (or compare
the segment name using substring/indexOf), then return the value via substring
or split('=') instead of using new RegExp; update references to
extractCookieValue accordingly.
---
Nitpick comments:
In `@tests/e2e/api-complete.test.ts`:
- Around line 16-17: Extract the duplicated authentication and request helper
into a shared module: create tests/e2e/_helpers/api.ts that exports a callApi
function which builds requests with Bearer + Cookie headers and a helper to
perform the beforeAll login flow (reusing loginAndGetAuthToken) so tests can
call that in their beforeAll; then update api-complete.test.ts,
notification-settings.test.ts, and users-keys-complete.test.ts to import and use
callApi and the shared beforeAll/login helper instead of duplicating the logic
in each file.
In `@tests/e2e/users-keys-complete.test.ts`:
- Line 37: Move the module-level beforeAll so it appears before the module-level
afterAll (make the order match other tests) and rename the inconsistent
authToken identifier to sessionToken: update the callApi function
signature/default to use sessionToken and replace all authToken uses with
sessionToken, ensuring the module variable let sessionToken: string | undefined;
is the single source of truth and that afterAll (cleanup) stays after the
now-earlier beforeAll setup.
tests/e2e/_helpers/auth.ts
Outdated
| const shouldRetry = response.status === 503 && errorCode === "SESSION_CREATE_FAILED"; | ||
| if (!shouldRetry || attempt >= maxAttempts) { | ||
| throw new Error(`[e2e] login failed: ${response.status} ${text}`); | ||
| } |
There was a problem hiding this comment.
Exponential backoff grows too slowly - 100 * 2^(attempt-1) reaches only 512ms at attempt 4. Since sleep caps at 1000ms, attempts 4-10 all wait the same 1s. Use 200 * 2^(attempt-1) or increase the cap.
| } | |
| await sleep(Math.min(2000, 200 * 2 ** (attempt - 1))); |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/e2e/_helpers/auth.ts
Line: 62
Comment:
Exponential backoff grows too slowly - `100 * 2^(attempt-1)` reaches only 512ms at attempt 4. Since `sleep` caps at 1000ms, attempts 4-10 all wait the same 1s. Use `200 * 2^(attempt-1)` or increase the cap.
```suggestion
await sleep(Math.min(2000, 200 * 2 ** (attempt - 1)));
```
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.| if (attempt >= maxAttempts) { | ||
| break; | ||
| } | ||
|
|
There was a problem hiding this comment.
Same backoff issue - grows too slowly with 100ms base. Also, this retry is for non-503 errors which seems overly aggressive.
| await sleep(Math.min(2000, 200 * 2 ** (attempt - 1))); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/e2e/_helpers/auth.ts
Line: 83
Comment:
Same backoff issue - grows too slowly with 100ms base. Also, this retry is for non-503 errors which seems overly aggressive.
```suggestion
await sleep(Math.min(2000, 200 * 2 ** (attempt - 1)));
```
How can I resolve this? If you propose a fix, please make it concise.| disabled={isPending} | ||
| tabStatus={getTabStatus()} | ||
| /> | ||
| <div className="order-2 md:order-1 shrink-0"> |
There was a problem hiding this comment.
Verify that the submit button (likely in a footer component) is accessible on mobile now that nav is repositioned below content.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx
Line: 556
Comment:
Verify that the submit button (likely in a footer component) is accessible on mobile now that nav is repositioned below content.
How can I resolve this? If you propose a fix, please make it concise.| maxConcurrency: 5, // 最大并发测试数 | ||
| pool: "threads", // 使用线程池(推荐) | ||
| // 高核机器/Windows 下 threads worker 过多可能触发 EMFILE / 资源争用导致用例超时。 | ||
| // 允许通过环境变量覆盖:VITEST_MAX_WORKERS=... |
There was a problem hiding this comment.
Default of 8 workers might be too conservative for modern machines - consider Math.min(os.cpus().length, 16) or a higher default (12-16) for better performance on multi-core systems.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: vitest.config.ts
Line: 100
Comment:
Default of 8 workers might be too conservative for modern machines - consider `Math.min(os.cpus().length, 16)` or a higher default (12-16) for better performance on multi-core systems.
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/e2e/_helpers/auth.ts`:
- Around line 59-85: The thrown error inside the try when !shouldRetry is being
caught by the outer catch and causes unintended retries; update the login loop
so non-retriable failures are rethrown immediately instead of being swallowed:
either move the throw for the !shouldRetry branch out of the try (so it escapes
the loop) or tag the thrown Error (e.g., add a nonRetriable flag) and in the
catch check that flag (using shouldRetry/attempt/maxAttempts) and rethrow
immediately; adjust handling of lastError/sleep accordingly so only truly
retriable 503/SESSION_CREATE_FAILED paths are retried.
---
Duplicate comments:
In `@tests/e2e/_helpers/auth.ts`:
- Around line 24-27: The extractCookieValue function uses new RegExp with a
concatenated cookieName which opens a ReDoS/unsafe-regex risk; replace the regex
approach in extractCookieValue with deterministic string parsing: trim the
header, split it on ';' into cookie segments, iterate segments and for each trim
and check startsWith(`${cookieName}=`) (or use indexOf) to extract and return
the substring after '=' (or null if not found). Ensure you only perform plain
string operations (no RegExp) and preserve existing return semantics (string |
null).
| <div className="absolute top-0 left-0 right-0 h-0.5 bg-muted"> | ||
| <motion.div | ||
| className="h-full bg-primary" | ||
| initial={{ width: "20%" }} | ||
| animate={{ | ||
| width: `${((TAB_CONFIG.findIndex((t) => t.id === activeTab) + 1) / TAB_CONFIG.length) * 100}%`, | ||
| }} | ||
| initial={{ width: stepProgressWidth }} | ||
| animate={{ width: stepProgressWidth }} |
There was a problem hiding this comment.
animation now disabled - initial and animate both set to stepProgressWidth, causing no visual transition when tabs change
| <div className="absolute top-0 left-0 right-0 h-0.5 bg-muted"> | |
| <motion.div | |
| className="h-full bg-primary" | |
| initial={{ width: "20%" }} | |
| animate={{ | |
| width: `${((TAB_CONFIG.findIndex((t) => t.id === activeTab) + 1) / TAB_CONFIG.length) * 100}%`, | |
| }} | |
| initial={{ width: stepProgressWidth }} | |
| animate={{ width: stepProgressWidth }} | |
| initial={{ width: 0 }} | |
| animate={{ width: stepProgressWidth }} |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx
Line: 267-271
Comment:
animation now disabled - `initial` and `animate` both set to `stepProgressWidth`, causing no visual transition when tabs change
```suggestion
initial={{ width: 0 }}
animate={{ width: stepProgressWidth }}
```
How can I resolve this? If you propose a fix, please make it concise.| // Skip Redis connection during Next.js production build phase (avoid connection attempts) | ||
| if (process.env.NEXT_PHASE === "phase-production-build") { | ||
| return null; |
There was a problem hiding this comment.
removing CI === "true" check allows Redis connections during CI, but the comment only mentions Next.js build phase - verify CI environment can handle Redis connections or update logic
if CI should still skip Redis (but E2E needs it), consider:
| // Skip Redis connection during Next.js production build phase (avoid connection attempts) | |
| if (process.env.NEXT_PHASE === "phase-production-build") { | |
| return null; | |
| // Skip Redis during Next.js build, but allow in CI for E2E tests | |
| if (process.env.NEXT_PHASE === "phase-production-build") { |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/redis/client.ts
Line: 77-79
Comment:
removing `CI === "true"` check allows Redis connections during CI, but the comment only mentions Next.js build phase - verify CI environment can handle Redis connections or update logic
if CI should still skip Redis (but E2E needs it), consider:
```suggestion
// Skip Redis during Next.js build, but allow in CI for E2E tests
if (process.env.NEXT_PHASE === "phase-production-build") {
```
How can I resolve this? If you propose a fix, please make it concise.
tests/e2e/_helpers/auth.ts
Outdated
| throw new Error(`[e2e] login failed: ${response.status} ${text}`); | ||
| } | ||
|
|
||
| await sleep(Math.min(1000, 100 * 2 ** (attempt - 1))); |
There was a problem hiding this comment.
slow exponential backoff - 100 * 2^(attempt-1) only reaches 512ms at attempt 4, then Math.min(1000, ...) caps remaining attempts at 1s. First 4 attempts happen within ~1.5s total
| await sleep(Math.min(1000, 100 * 2 ** (attempt - 1))); | |
| await sleep(Math.min(2000, 200 * 2 ** (attempt - 1))); |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/e2e/_helpers/auth.ts
Line: 64
Comment:
slow exponential backoff - `100 * 2^(attempt-1)` only reaches 512ms at attempt 4, then `Math.min(1000, ...)` caps remaining attempts at 1s. First 4 attempts happen within ~1.5s total
```suggestion
await sleep(Math.min(2000, 200 * 2 ** (attempt - 1)));
```
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.| break; | ||
| } | ||
|
|
||
| await sleep(Math.min(1000, 100 * 2 ** (attempt - 1))); |
There was a problem hiding this comment.
retry logic applies to all errors (network, parse failures, etc), not just transient server issues - may mask bugs in test setup
| await sleep(Math.min(1000, 100 * 2 ** (attempt - 1))); | |
| await sleep(Math.min(2000, 200 * 2 ** (attempt - 1))); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/e2e/_helpers/auth.ts
Line: 84
Comment:
retry logic applies to all errors (network, parse failures, etc), not just transient server issues - may mask bugs in test setup
```suggestion
await sleep(Math.min(2000, 200 * 2 ** (attempt - 1)));
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/lib/redis/client.ts (1)
94-96: 建议:连接永久断开后重置单例,避免返回僵尸客户端
retryStrategy返回非数字值时,ioredis 将停止重连,"the connection will be lost forever unless the user calls redis.connect() manually"。当前实现中,
retryStrategy在第 5 次后返回null(第 108 行),但模块级redisClient单例并未被清空。此后所有getRedisClient()调用都会在第 94-96 行命中缓存并返回这个不可用的死客户端,而不会尝试重新初始化。现在 CI 环境也会建立真实连接,该问题在 CI 流水线中更容易被触发(例如 Redis 服务启动滞后时)。
♻️ 建议:监听 `end` 事件时重置单例
redisClient.on("close", () => { logger.warn("[Redis] Connection closed", { redisUrl: safeRedisUrl }); }); + + redisClient.on("end", () => { + logger.warn("[Redis] Connection permanently closed, resetting client", { redisUrl: safeRedisUrl }); + redisClient = null; + });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/redis/client.ts` around lines 94 - 96, The module-level singleton redisClient can become a "dead" client when retryStrategy returns null (stops reconnection), so update the Redis client initialization (the code that creates redisClient and defines retryStrategy) to attach an 'end' (and optionally 'close') event handler that sets the module-level redisClient = null so subsequent getRedisClient() calls will reinitialize a new client; also consider checking client status in getRedisClient() (e.g., not just truthy but connected/ready) before returning the cached instance to avoid returning a zombie client.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/lib/redis/client.ts`:
- Around line 94-96: The module-level singleton redisClient can become a "dead"
client when retryStrategy returns null (stops reconnection), so update the Redis
client initialization (the code that creates redisClient and defines
retryStrategy) to attach an 'end' (and optionally 'close') event handler that
sets the module-level redisClient = null so subsequent getRedisClient() calls
will reinitialize a new client; also consider checking client status in
getRedisClient() (e.g., not just truthy but connected/ready) before returning
the cached instance to avoid returning a zombie client.
|
已按 CodeRabbit 建议补齐并修正:
另外修复了 PR 中 API E2E Tests 的失败:
当前 Actions:build/lint/typecheck/unit/integration/api tests 均已通过。 |
src/app/globals.css
Outdated
| @supports (height: 100dvh) { | ||
| .max-h-\[50vh\] { | ||
| max-height: 50dvh; | ||
| } | ||
| .max-h-\[70vh\] { | ||
| max-height: 70dvh; | ||
| } | ||
| .max-h-\[80vh\] { | ||
| max-height: 80dvh; | ||
| } | ||
| .max-h-\[85vh\] { | ||
| max-height: 85dvh; | ||
| } | ||
| .max-h-\[90vh\] { | ||
| max-height: 90dvh; | ||
| } | ||
| .max-h-\[95vh\] { | ||
| max-height: 95dvh; | ||
| } |
There was a problem hiding this comment.
these CSS overrides using @supports (height: 100dvh) will only apply to browsers that support dvh, but the classes like max-h-[85vh] will still use vh in browsers that don't support dvh
the --cch-viewport-height CSS variable approach used elsewhere in the PR (lines 48, 82-86) is more consistent - it provides a single source of truth that falls back from dvh to vh
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/globals.css
Line: 151-169
Comment:
these CSS overrides using `@supports (height: 100dvh)` will only apply to browsers that support `dvh`, but the classes like `max-h-[85vh]` will still use `vh` in browsers that don't support `dvh`
the `--cch-viewport-height` CSS variable approach used elsewhere in the PR (lines 48, 82-86) is more consistent - it provides a single source of truth that falls back from `dvh` to `vh`
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.| @@ -553,18 +553,20 @@ function ProviderFormContent({ | |||
| <form onSubmit={handleSubmit} className="flex flex-col h-full max-h-[85vh]"> | |||
There was a problem hiding this comment.
form still uses max-h-[85vh] which will be overridden to 85dvh by the CSS in globals.css - but this creates inconsistency with other viewport calculations that use the --cch-viewport-height variable
| <form onSubmit={handleSubmit} className="flex flex-col h-full max-h-[85vh]"> | |
| <form onSubmit={handleSubmit} className="flex flex-col h-full max-h-[calc(var(--cch-viewport-height,100vh)*0.85)]"> |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx
Line: 553
Comment:
form still uses `max-h-[85vh]` which will be overridden to `85dvh` by the CSS in `globals.css` - but this creates inconsistency with other viewport calculations that use the `--cch-viewport-height` variable
```suggestion
<form onSubmit={handleSubmit} className="flex flex-col h-full max-h-[calc(var(--cch-viewport-height,100vh)*0.85)]">
```
How can I resolve this? If you propose a fix, please make it concise.| <div className="order-2 md:order-1 shrink-0"> | ||
| {/* Tab Navigation */} | ||
| <FormTabNav | ||
| activeTab={state.ui.activeTab} | ||
| onTabChange={handleTabChange} | ||
| disabled={isPending} | ||
| tabStatus={getTabStatus()} | ||
| /> | ||
| </div> |
There was a problem hiding this comment.
wrapping FormTabNav in a div with shrink-0 creates an extra DOM layer - the wrapper div prevents FormTabNav from being a direct flex child, which may cause layout issues if FormTabNav has its own flex/sizing logic
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx
Line: 556-564
Comment:
wrapping `FormTabNav` in a `div` with `shrink-0` creates an extra DOM layer - the wrapper div prevents `FormTabNav` from being a direct flex child, which may cause layout issues if `FormTabNav` has its own flex/sizing logic
How can I resolve this? If you propose a fix, please make it concise.
tests/e2e/_helpers/auth.ts
Outdated
| throw new Error(`[e2e] login failed: ${response.status} ${text}`); | ||
| } | ||
|
|
||
| await sleep(Math.min(1000, 100 * 2 ** (attempt - 1))); |
There was a problem hiding this comment.
exponential backoff base (100ms) is too small - with cap at 1000ms, attempts reach max delay quickly: 100ms, 200ms, 400ms, 800ms, then 1000ms for attempts 5-10
| await sleep(Math.min(1000, 100 * 2 ** (attempt - 1))); | |
| await sleep(Math.min(2000, 200 * 2 ** (attempt - 1))); |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/e2e/_helpers/auth.ts
Line: 64
Comment:
exponential backoff base (100ms) is too small - with cap at 1000ms, attempts reach max delay quickly: 100ms, 200ms, 400ms, 800ms, then 1000ms for attempts 5-10
```suggestion
await sleep(Math.min(2000, 200 * 2 ** (attempt - 1)));
```
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.
tests/e2e/_helpers/auth.ts
Outdated
| } catch (error) { | ||
| lastError = error; | ||
| if (attempt >= maxAttempts) { | ||
| break; | ||
| } | ||
|
|
||
| await sleep(Math.min(1000, 100 * 2 ** (attempt - 1))); | ||
| } |
There was a problem hiding this comment.
catch block retries for ALL errors (network failures, parse errors, etc), not just transient server issues - this masks legitimate failures and causes tests to hang for extended periods
the retry should only happen for specific retryable errors, or this block should be removed entirely since the non-ok response case above already handles 503 retries
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/e2e/_helpers/auth.ts
Line: 78-85
Comment:
catch block retries for ALL errors (network failures, parse errors, etc), not just transient server issues - this masks legitimate failures and causes tests to hang for extended periods
the retry should only happen for specific retryable errors, or this block should be removed entirely since the non-ok response case above already handles 503 retries
How can I resolve this? If you propose a fix, please make it concise.|
已按 CodeRabbit 的 nitpick 做了小幅性能优化(不改行为):
本地已跑:bun run typecheck / bun run test / bun run build 通过。 |
|
补充修复本地 standalone 启动时静态资源 404 的问题:
本机已验证: |
| maxTagLength={64} | ||
| maxTags={50} | ||
| disabled={disabled} | ||
| validateTag={() => true} |
There was a problem hiding this comment.
validateTag={() => true} bypasses format validation - the invalid_format case in routing-section.tsx:288 onInvalidTag handler will never fire
if arbitrary UA strings are intentional, this is fine, but consider removing the dead invalid_format branch from the handler or adding a comment explaining why format validation is disabled
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/form/client-restrictions-editor.tsx
Line: 54
Comment:
`validateTag={() => true}` bypasses format validation - the `invalid_format` case in `routing-section.tsx:288` onInvalidTag handler will never fire
if arbitrary UA strings are intentional, this is fine, but consider removing the dead `invalid_format` branch from the handler or adding a comment explaining why format validation is disabled
How can I resolve this? If you propose a fix, please make it concise.| const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = | ||
| useState(hasAnyClientRestrictions); |
There was a problem hiding this comment.
hasAnyClientRestrictions computed on every render but only used during mount - use function initializer to avoid unnecessary computation:
| const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = | |
| useState(hasAnyClientRestrictions); | |
| const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = useState(() => | |
| allowedClients.length > 0 || blockedClients.length > 0 | |
| ); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx
Line: 77-78
Comment:
`hasAnyClientRestrictions` computed on every render but only used during mount - use function initializer to avoid unnecessary computation:
```suggestion
const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = useState(() =>
allowedClients.length > 0 || blockedClients.length > 0
);
```
How can I resolve this? If you propose a fix, please make it concise.| animate={{ | ||
| width: `${((TAB_CONFIG.findIndex((t) => t.id === activeTab) + 1) / TAB_CONFIG.length) * 100}%`, | ||
| }} | ||
| initial={{ width: stepProgressWidth }} |
There was a problem hiding this comment.
progress bar animation disabled - both initial and animate set to same value stepProgressWidth, causing no visual transition when tabs change
remove initial prop to animate from current position:
| initial={{ width: stepProgressWidth }} | |
| animate={{ width: stepProgressWidth }} |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx
Line: 277
Comment:
progress bar animation disabled - both `initial` and `animate` set to same value `stepProgressWidth`, causing no visual transition when tabs change
remove `initial` prop to animate from current position:
```suggestion
animate={{ width: stepProgressWidth }}
```
How can I resolve this? If you propose a fix, please make it concise.| }); | ||
| } catch (error) { | ||
| lastError = error; | ||
| if (attempt >= maxAttempts) { | ||
| break; | ||
| } | ||
|
|
||
| await sleep(Math.min(1000, 100 * 2 ** (attempt - 1))); |
There was a problem hiding this comment.
catch block retries on ALL fetch errors (network failures, DNS issues, timeouts) - masks legitimate test setup problems and causes tests to hang ~6s instead of failing fast
only retry specific recoverable errors like ECONNRESET/ETIMEDOUT, or remove this catch since line 82-93 already handles HTTP 503 retries
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/e2e/_helpers/auth.ts
Line: 59-66
Comment:
catch block retries on ALL fetch errors (network failures, DNS issues, timeouts) - masks legitimate test setup problems and causes tests to hang ~6s instead of failing fast
only retry specific recoverable errors like `ECONNRESET`/`ETIMEDOUT`, or remove this catch since line 82-93 already handles HTTP 503 retries
How can I resolve this? If you propose a fix, please make it concise.
Additional Comments (1)
the tooltip wraps the entire move the Prompt To Fix With AIThis is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx
Line: 550
Comment:
`Info` icon positioned inside `Select` breaks interaction - clicking the icon area won't open the select dropdown because it's overlaid on top
the tooltip wraps the entire `div` containing the `Select`, so hovering anywhere (including the select trigger) will show the tooltip, which conflicts with the select's own UI
move the `Info` icon outside the `Select` and only wrap the icon with the tooltip trigger
How can I resolve this? If you propose a fix, please make it concise. |
|
已重新阅读 PR 描述(含 Greptile Summary / CodeRabbit 状态区块),并检查 Actions:截至 2026-02-24 04:15Z,所有 checks 均为 SUCCESS。 AI review 处理结论:
额外修复(来自本机验收反馈):
|
| initial={{ width: stepProgressWidth }} | ||
| animate={{ width: stepProgressWidth }} |
There was a problem hiding this comment.
progress bar animation broken - initial and animate both use stepProgressWidth, so no visual transition occurs when switching tabs
the animation worked before because initial had a fixed value while animate was dynamic - now they're identical, defeating the purpose of using motion.div
| initial={{ width: stepProgressWidth }} | |
| animate={{ width: stepProgressWidth }} | |
| initial={{ width: "0%" }} | |
| animate={{ width: stepProgressWidth }} |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx
Line: 277-278
Comment:
progress bar animation broken - `initial` and `animate` both use `stepProgressWidth`, so no visual transition occurs when switching tabs
the animation worked before because `initial` had a fixed value while `animate` was dynamic - now they're identical, defeating the purpose of using `motion.div`
```suggestion
initial={{ width: "0%" }}
animate={{ width: stepProgressWidth }}
```
How can I resolve this? If you propose a fix, please make it concise.| @@ -211,7 +215,7 @@ export function FormTabNav({ | |||
| </nav> | |||
There was a problem hiding this comment.
changed from fixed bottom-0 to relative - verify this doesn't break mobile UX where the nav should stick to viewport bottom
the shrink-0 ensures it won't collapse, but relative means it scrolls with content instead of staying fixed at screen bottom
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx
Line: 215
Comment:
changed from `fixed bottom-0` to `relative` - verify this doesn't break mobile UX where the nav should stick to viewport bottom
the `shrink-0` ensures it won't collapse, but `relative` means it scrolls with content instead of staying fixed at screen bottom
How can I resolve this? If you propose a fix, please make it concise.| const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = | ||
| useState(hasAnyClientRestrictions); |
There was a problem hiding this comment.
hasAnyClientRestrictions computed on every render but only used during mount - use function initializer to avoid unnecessary work
| const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = | |
| useState(hasAnyClientRestrictions); | |
| const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = useState(() => | |
| allowedClients.length > 0 || blockedClients.length > 0 | |
| ); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx
Line: 77-78
Comment:
`hasAnyClientRestrictions` computed on every render but only used during mount - use function initializer to avoid unnecessary work
```suggestion
const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = useState(() =>
allowedClients.length > 0 || blockedClients.length > 0
);
```
How can I resolve this? If you propose a fix, please make it concise.| } catch (error) { | ||
| lastError = error; | ||
| if (attempt >= maxAttempts) { | ||
| break; | ||
| } | ||
|
|
||
| await sleep(Math.min(1000, 100 * 2 ** (attempt - 1))); | ||
| continue; |
There was a problem hiding this comment.
catch block retries on ALL fetch errors (network, DNS, timeouts) - masks legitimate test setup problems
if API_BASE_URL is misconfigured, tests hang ~6s (10 attempts × ~600ms avg) instead of failing fast
consider only retrying transient errors like ECONNRESET/ETIMEDOUT, or rely on the HTTP 503 retry logic below (lines 82-94) and let other errors fail immediately
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/e2e/_helpers/auth.ts
Line: 60-67
Comment:
catch block retries on ALL fetch errors (network, DNS, timeouts) - masks legitimate test setup problems
if `API_BASE_URL` is misconfigured, tests hang ~6s (10 attempts × ~600ms avg) instead of failing fast
consider only retrying transient errors like `ECONNRESET`/`ETIMEDOUT`, or rely on the HTTP 503 retry logic below (lines 82-94) and let other errors fail immediately
How can I resolve this? If you propose a fix, please make it concise.|
补充跟进 Greptile Summary(最新 reviewed commit: 348c5f7)里提到的「步骤进度条动画禁用」问题:
本地自检: |
| ); | ||
|
|
||
| const handleAllowedChange = (next: string[]) => { | ||
| const nextAllowed = uniqueOrdered(next); | ||
| onAllowedChange(nextAllowed); | ||
|
|
||
| const allowedSet = new Set(nextAllowed); | ||
| const nextBlocked = blocked.filter((value) => !allowedSet.has(value)); | ||
| if (nextBlocked.length !== blocked.length) { | ||
| onBlockedChange(nextBlocked); |
There was a problem hiding this comment.
mutual exclusion happens after state update - if nextAllowed is identical to current allowed, React skips the update, but then nextBlocked is still calculated and compared
if blocked already has the correct filtered value, this causes a no-op dispatch, but the comparison overhead still runs - consider checking allowedSet.has() before filtering to avoid unnecessary work
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/form/client-restrictions-editor.tsx
Line: 99-108
Comment:
mutual exclusion happens after state update - if `nextAllowed` is identical to current `allowed`, React skips the update, but then `nextBlocked` is still calculated and compared
if `blocked` already has the correct filtered value, this causes a no-op dispatch, but the comparison overhead still runs - consider checking `allowedSet.has()` before filtering to avoid unnecessary work
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.
tests/e2e/_helpers/auth.ts
Outdated
| return combined | ||
| .split(/,(?=[^;]+?=)/g) | ||
| .map((s) => s.trim()) | ||
| .filter(Boolean); |
There was a problem hiding this comment.
regex can incorrectly split Set-Cookie headers with RFC 1123 dates like Expires=Wed, 21 Oct 2026 07:28:00 GMT
the lookahead (?=[^;]+?=) matches at the comma in 21 Oct because it sees characters followed by = in later cookie attributes
if server sends RFC-compliant Expires, cookie parsing breaks
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/e2e/_helpers/auth.ts
Line: 18-21
Comment:
regex can incorrectly split `Set-Cookie` headers with RFC 1123 dates like `Expires=Wed, 21 Oct 2026 07:28:00 GMT`
the lookahead `(?=[^;]+?=)` matches at the comma in `21 Oct` because it sees characters followed by `=` in later cookie attributes
if server sends RFC-compliant `Expires`, cookie parsing breaks
How can I resolve this? If you propose a fix, please make it concise.| }); | ||
| } catch (error) { | ||
| lastError = error; | ||
| if (attempt >= maxAttempts) { | ||
| break; | ||
| } | ||
|
|
||
| await sleep(Math.min(1000, 100 * 2 ** (attempt - 1))); | ||
| continue; |
There was a problem hiding this comment.
retries on ALL fetch errors (network, DNS, timeouts) mask legitimate test setup failures
if API_BASE_URL is misconfigured, tests hang ~6s (10 attempts × ~600ms avg) instead of failing fast
only retry transient errors like ECONNRESET/ETIMEDOUT, or remove since line 82-93 already handles HTTP 503 retries
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/e2e/_helpers/auth.ts
Line: 59-67
Comment:
retries on ALL fetch errors (network, DNS, timeouts) mask legitimate test setup failures
if `API_BASE_URL` is misconfigured, tests hang ~6s (10 attempts × ~600ms avg) instead of failing fast
only retry transient errors like `ECONNRESET`/`ETIMEDOUT`, or remove since line 82-93 already handles HTTP 503 retries
How can I resolve this? If you propose a fix, please make it concise.| if (attempt >= maxAttempts) { | ||
| break; | ||
| } | ||
|
|
There was a problem hiding this comment.
exponential backoff base too small - 100 * 2^(attempt-1) with 1000ms cap means: 100ms, 200ms, 400ms, 800ms, then 1000ms for attempts 5-10
attempts reach max delay very quickly - consider 200ms base or higher cap
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/e2e/_helpers/auth.ts
Line: 65
Comment:
exponential backoff base too small - `100 * 2^(attempt-1)` with 1000ms cap means: 100ms, 200ms, 400ms, 800ms, then 1000ms for attempts 5-10
attempts reach max delay very quickly - consider 200ms base or higher cap
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.| const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = | ||
| useState(hasAnyClientRestrictions); |
There was a problem hiding this comment.
hasAnyClientRestrictions computed on every render but only used during mount
use function initializer to avoid unnecessary computation:
| const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = | |
| useState(hasAnyClientRestrictions); | |
| const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = useState(() => | |
| allowedClients.length > 0 || blockedClients.length > 0 | |
| ); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx
Line: 77-78
Comment:
`hasAnyClientRestrictions` computed on every render but only used during mount
use function initializer to avoid unnecessary computation:
```suggestion
const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = useState(() =>
allowedClients.length > 0 || blockedClients.length > 0
);
```
How can I resolve this? If you propose a fix, please make it concise.| useEffect(() => { | ||
| setClientRestrictionsEnabled(hasAnyClientRestrictions); | ||
| }, [hasAnyClientRestrictions]); |
There was a problem hiding this comment.
if external code clears both lists while toggle is enabled, this effect disables the toggle, losing user intent to keep it enabled
if parent components directly modify allowedClients/blockedClients to empty arrays, the user's intention (to have restrictions enabled and add new ones) is lost - consider whether this automatic disabling behavior is desired
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx
Line: 80-82
Comment:
if external code clears both lists while toggle is enabled, this effect disables the toggle, losing user intent to keep it enabled
if parent components directly modify `allowedClients`/`blockedClients` to empty arrays, the user's intention (to have restrictions enabled and add new ones) is lost - consider whether this automatic disabling behavior is desired
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.|
已按 Greptile “Issues found” 修复并补充覆盖(commit
|
| const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = | ||
| useState(hasAnyClientRestrictions); |
There was a problem hiding this comment.
hasAnyClientRestrictions computed on every render but only used during mount
use function initializer:
| const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = | |
| useState(hasAnyClientRestrictions); | |
| const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = useState( | |
| () => allowedClients.length > 0 || blockedClients.length > 0 | |
| ); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx
Line: 77-78
Comment:
`hasAnyClientRestrictions` computed on every render but only used during mount
use function initializer:
```suggestion
const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = useState(
() => allowedClients.length > 0 || blockedClients.length > 0
);
```
How can I resolve this? If you propose a fix, please make it concise.| break; | ||
| } | ||
|
|
||
| await sleep(Math.min(1000, 100 * 2 ** (attempt - 1))); |
There was a problem hiding this comment.
exponential backoff base too small - with 100 * 2^(attempt-1) and 1000ms cap, delays reach max very quickly (100ms, 200ms, 400ms, 800ms, then 1000ms for attempts 5-10)
consider larger base like 200ms or higher cap for better backoff distribution
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/e2e/_helpers/auth.ts
Line: 153
Comment:
exponential backoff base too small - with `100 * 2^(attempt-1)` and 1000ms cap, delays reach max very quickly (100ms, 200ms, 400ms, 800ms, then 1000ms for attempts 5-10)
consider larger base like 200ms or higher cap for better backoff distribution
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.| } catch (error) { | ||
| lastError = error; | ||
| if (!shouldRetryFetchError(error)) { | ||
| break; | ||
| } | ||
| if (attempt >= maxAttempts) { | ||
| break; | ||
| } | ||
|
|
||
| await sleep(Math.min(1000, 100 * 2 ** (attempt - 1))); | ||
| continue; |
There was a problem hiding this comment.
catch block retries on ALL fetch errors (network, DNS, timeouts) - masks legitimate test setup problems
if API_BASE_URL is misconfigured, tests hang ~6s instead of failing fast
the shouldRetryFetchError function on line 114-127 helps by filtering to specific network errors, but consider whether DNS/config errors should retry or fail immediately
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/e2e/_helpers/auth.ts
Line: 144-154
Comment:
catch block retries on ALL fetch errors (network, DNS, timeouts) - masks legitimate test setup problems
if `API_BASE_URL` is misconfigured, tests hang ~6s instead of failing fast
the `shouldRetryFetchError` function on line 114-127 helps by filtering to specific network errors, but consider whether DNS/config errors should retry or fail immediately
How can I resolve this? If you propose a fix, please make it concise.| maxTagLength={64} | ||
| maxTags={50} | ||
| disabled={disabled} | ||
| validateTag={() => true} |
There was a problem hiding this comment.
validateTag={() => true} bypasses format validation - the invalid_format case in routing-section.tsx:288 onInvalidTag handler will never fire
if arbitrary UA strings are intentional, this is fine, but the dead invalid_format branch in the handler creates confusion
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/form/client-restrictions-editor.tsx
Line: 54
Comment:
`validateTag={() => true}` bypasses format validation - the `invalid_format` case in `routing-section.tsx:288` onInvalidTag handler will never fire
if arbitrary UA strings are intentional, this is fine, but the dead `invalid_format` branch in the handler creates confusion
How can I resolve this? If you propose a fix, please make it concise.| const handleAllowedChange = (next: string[]) => { | ||
| const nextAllowed = uniqueOrdered(next); | ||
| onAllowedChange(nextAllowed); | ||
|
|
||
| const allowedSet = new Set(nextAllowed); | ||
| const nextBlocked = blocked.filter((value) => !allowedSet.has(value)); | ||
| if (nextBlocked.length !== blocked.length) { | ||
| onBlockedChange(nextBlocked); | ||
| } | ||
| }; |
There was a problem hiding this comment.
mutual exclusion happens after state update - if nextAllowed is identical to current allowed, React skips the update, but nextBlocked filtering still runs
minor optimization opportunity: check if filtering is needed before computing nextBlocked
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/form/client-restrictions-editor.tsx
Line: 101-110
Comment:
mutual exclusion happens after state update - if `nextAllowed` is identical to current `allowed`, React skips the update, but `nextBlocked` filtering still runs
minor optimization opportunity: check if filtering is needed before computing `nextBlocked`
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.| useEffect(() => { | ||
| setClientRestrictionsEnabled(hasAnyClientRestrictions); | ||
| }, [hasAnyClientRestrictions]); |
There was a problem hiding this comment.
if external code (form reset, batch operations) clears both allowedClients and blockedClients to empty arrays while toggle is enabled, this effect auto-disables the toggle
verify this is the intended behavior - the user's intent to keep restrictions enabled (and add new ones) may be lost
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx
Line: 80-82
Comment:
if external code (form reset, batch operations) clears both `allowedClients` and `blockedClients` to empty arrays while toggle is enabled, this effect auto-disables the toggle
verify this is the intended behavior - the user's intent to keep restrictions enabled (and add new ones) may be lost
How can I resolve this? If you propose a fix, please make it concise.|
|
||
| return ( | ||
| <DialogContent className="w-full max-w-[95vw] sm:max-w-[85vw] md:max-w-[70vw] lg:max-w-3xl max-h-[90vh] max-h-[90dvh] p-0 flex flex-col overflow-hidden"> | ||
| <DialogContent className="w-full max-w-[95vw] sm:max-w-[85vw] md:max-w-[70vw] lg:max-w-3xl max-h-[var(--cch-viewport-height-90,90vh)] p-0 flex flex-col overflow-hidden"> |
There was a problem hiding this comment.
Inconsistent CSS variable fallback usage
This file uses max-h-[var(--cch-viewport-height-90,90vh)] with an inline fallback, while the other ~30 files changed in this PR use the variable without a fallback (e.g., max-h-[var(--cch-viewport-height-90)]). The same inconsistency exists in user-actions.tsx:43 with --cch-viewport-height-85,85vh.
Since globals.css already defines --cch-viewport-height-90: 90vh as the default, the inline fallback is redundant. For consistency, either add the fallback everywhere or remove it here.
| <DialogContent className="w-full max-w-[95vw] sm:max-w-[85vw] md:max-w-[70vw] lg:max-w-3xl max-h-[var(--cch-viewport-height-90,90vh)] p-0 flex flex-col overflow-hidden"> | |
| <DialogContent className="w-full max-w-[95vw] sm:max-w-[85vw] md:max-w-[70vw] lg:max-w-3xl max-h-[var(--cch-viewport-height-90)] p-0 flex flex-col overflow-hidden"> |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/dashboard/_components/user/edit-user-dialog.tsx
Line: 247
Comment:
**Inconsistent CSS variable fallback usage**
This file uses `max-h-[var(--cch-viewport-height-90,90vh)]` with an inline fallback, while the other ~30 files changed in this PR use the variable without a fallback (e.g., `max-h-[var(--cch-viewport-height-90)]`). The same inconsistency exists in `user-actions.tsx:43` with `--cch-viewport-height-85,85vh`.
Since `globals.css` already defines `--cch-viewport-height-90: 90vh` as the default, the inline fallback is redundant. For consistency, either add the fallback everywhere or remove it here.
```suggestion
<DialogContent className="w-full max-w-[95vw] sm:max-w-[85vw] md:max-w-[70vw] lg:max-w-3xl max-h-[var(--cch-viewport-height-90)] p-0 flex flex-col overflow-hidden">
```
How can I resolve this? If you propose a fix, please make it concise.| const handleClientRestrictionsEnabledChange = (enabled: boolean) => { | ||
| setClientRestrictionsEnabled(enabled); | ||
| if (!enabled) { | ||
| dispatch({ type: "SET_ALLOWED_CLIENTS", payload: [] }); | ||
| dispatch({ type: "SET_BLOCKED_CLIENTS", payload: [] }); | ||
| } |
There was a problem hiding this comment.
Toggle disabling clears data without confirmation
When the user toggles off client restrictions, both allowedClients and blockedClients are immediately dispatched as empty arrays. If the user accidentally toggles the switch off, all configured client restriction entries are irreversibly lost — there's no confirmation dialog and no undo.
Consider either showing a confirmation prompt before clearing the data, or deferring the clearing until the form is submitted (i.e., keep the data in state but just hide the editor).
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx
Line: 86-91
Comment:
**Toggle disabling clears data without confirmation**
When the user toggles off client restrictions, both `allowedClients` and `blockedClients` are immediately dispatched as empty arrays. If the user accidentally toggles the switch off, all configured client restriction entries are irreversibly lost — there's no confirmation dialog and no undo.
Consider either showing a confirmation prompt before clearing the data, or deferring the clearing until the form is submitted (i.e., keep the data in state but just hide the editor).
How can I resolve this? If you propose a fix, please make it concise.| className="h-full bg-primary" | ||
| initial={{ width: "20%" }} | ||
| animate={{ | ||
| width: `${((TAB_CONFIG.findIndex((t) => t.id === activeTab) + 1) / TAB_CONFIG.length) * 100}%`, | ||
| }} | ||
| initial={false} |
There was a problem hiding this comment.
changed initial={false} removes the entry animation - the progress bar will now snap to the correct width on mount instead of animating from 0
if instant rendering is desired, this is correct; if you want the initial animation, use initial={{ width: "0%" }}
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/components/form-tab-nav.tsx
Line: 276-277
Comment:
changed `initial={false}` removes the entry animation - the progress bar will now snap to the correct width on mount instead of animating from 0
if instant rendering is desired, this is correct; if you want the initial animation, use `initial={{ width: "0%" }}`
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.| const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = useState( | ||
| () => hasAnyClientRestrictions | ||
| ); |
There was a problem hiding this comment.
hasAnyClientRestrictions computed on every render but only used during mount
use function initializer to avoid unnecessary computation:
| const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = useState( | |
| () => hasAnyClientRestrictions | |
| ); | |
| const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = useState( | |
| () => allowedClients.length > 0 || blockedClients.length > 0 | |
| ); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx
Line: 77-79
Comment:
`hasAnyClientRestrictions` computed on every render but only used during mount
use function initializer to avoid unnecessary computation:
```suggestion
const [clientRestrictionsEnabled, setClientRestrictionsEnabled] = useState(
() => allowedClients.length > 0 || blockedClients.length > 0
);
```
How can I resolve this? If you propose a fix, please make it concise.| pool: "threads", // 使用线程池(推荐) | ||
| // 高核机器/Windows 下 threads worker 过多可能触发 EMFILE / 资源争用导致用例超时。 | ||
| // 允许通过环境变量覆盖:VITEST_MAX_WORKERS=... | ||
| maxWorkers: parsePositiveInt(process.env.VITEST_MAX_WORKERS, 8), |
There was a problem hiding this comment.
default of 8 workers is conservative for modern machines
consider using a higher default (12-16) for better performance on multi-core systems, or dynamically calculate based on CPU count
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: vitest.config.ts
Line: 101
Comment:
default of 8 workers is conservative for modern machines
consider using a higher default (12-16) for better performance on multi-core systems, or dynamically calculate based on CPU count
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.| break; | ||
| } | ||
|
|
||
| await sleep(Math.min(1000, 100 * 2 ** (attempt - 1))); |
There was a problem hiding this comment.
exponential backoff base is small - with 100 * 2^(attempt-1) and 1000ms cap, delays reach max very quickly (100ms, 200ms, 400ms, 800ms, then 1000ms for attempts 5-10)
consider larger base like 200ms or higher cap for better backoff distribution
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/e2e/_helpers/auth.ts
Line: 174
Comment:
exponential backoff base is small - with `100 * 2^(attempt-1)` and 1000ms cap, delays reach max very quickly (100ms, 200ms, 400ms, 800ms, then 1000ms for attempts 5-10)
consider larger base like 200ms or higher cap for better backoff distribution
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.| maxTagLength={64} | ||
| maxTags={50} | ||
| disabled={disabled} | ||
| validateTag={() => true} |
There was a problem hiding this comment.
validateTag={() => true} bypasses format validation - the invalid_format case in routing-section.tsx:288 onInvalidTag handler will never fire
if arbitrary UA strings are intentional, this is fine, but consider removing the dead invalid_format branch from the handler or adding a comment explaining why format validation is disabled
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/components/form/client-restrictions-editor.tsx
Line: 54
Comment:
`validateTag={() => true}` bypasses format validation - the `invalid_format` case in `routing-section.tsx:288` onInvalidTag handler will never fire
if arbitrary UA strings are intentional, this is fine, but consider removing the dead `invalid_format` branch from the handler or adding a comment explaining why format validation is disabled
How can I resolve this? If you propose a fix, please make it concise.|
补充跟进 Greptile 的后续建议(commit
|
…onfig (#822) * fix(ui): 服务商表单小屏体验与客户端限制配置 (#816) * fix(ui): 修复服务商表单小屏底部导航遮挡提交按钮 * test(vitest): 修复 node 内置模块 mock 并限制默认 worker 数 * fix(ui): 补齐 safe-area-bottom 并修正表单进度条 * test(e2e): 通过登录换取会话 token * test(e2e): 登录获取会话 token 增加重试 * fix(redis): CI 环境允许连接 Redis * fix(ui): 移动端 dvh 视口高度与安全区适配 * fix(e2e): 登录重试仅覆盖可重试错误 * fix(redis): 连接终止后重置单例避免僵尸客户端 * fix(ui,redis): safe-area 作用域与 Redis 配置复用 * fix(ui,redis): dvh 自适应与 closeRedis 守卫 * fix(ui,i18n,redis): 修复 max-h/i18n 与 closeRedis * fix(ui,test,a11y): 补齐小屏体验与单测稳定性 * fix(ui,test): 处理 CodeRabbit 复审建议 * fix(ui): 客户端限制输入补齐错误反馈 * fix(ui): 改善服务商表单的客户端限制与模型选择体验 * perf(ui): Provider 表单减少无效重算 * chore: format code (fix-issue-799-mobile-ui-38fb971) * fix(build): standalone 本地运行补齐静态资源 * fix(ui): 调整思考预算/自适应思考提示触发与定位 * 修复 Provider 覆盖项下拉宽度与提示交互 * 修复 TagInput 选择建议后下拉关闭 * 修复下拉信息图标阻挡点击 * 修复表单步骤进度条动画 * test(e2e): 修复登录 Cookie 解析与重试策略 * fix: 优化客户端限制开关与 E2E 重试判定 * test(e2e): 收敛 fetch retry 的错误类型 --------- Co-authored-by: tesgth032 <tesgth032@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> * fix(review): address coderabbit and bot review comments - fix(css): add spaces around operators in calc() expression for valid CSS - fix(a11y): replace nested <main> with <div> in root layout to avoid ARIA violation - fix(ui): replace emoji literals with lucide-react icons in scheduling-rules-dialog - fix(i18n): extract hardcoded Chinese strings in big-screen page to i18n keys (5 langs) - fix(test): update outdated comments in calendar-highlight tests - fix(test): add cancelable:true to MouseEvent in tag-input-dialog tests - test: add unit tests for ClientRestrictionsEditor allow/block sync logic - test: add unit tests for Redis singleton reset-on-end and closeRedis fallback * chore: format code (fix-issue-799-mobile-ui-179f3b4) --------- Co-authored-by: tesgth032 <tesgth032@hotmail.com> Co-authored-by: tesgth032 <tesgth032@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Summary
-/_等价)。node .next/standalone/server.js缺失静态资源导致样式加载失败(构建后复制.next/static与public到.next/standalone)。Set-Cookie拆分(兼容 RFC1123 Expires 含逗号)+ 仅对瞬断错误重试;新增单测覆盖。rediss://TLS 检测、retry 策略、cleanup)。Local Run
ENABLE_SECURE_COOKIES=false(见.env.example注释)。Testing
bun run buildbun run lintbun run lint:fixbun run typecheckbun run test