Skip to content

fix: increase group_tag varchar limit from 50 to 255#940

Merged
ding113 merged 3 commits into
ding113:devfrom
sususu98:fix/group-tag-length
Mar 18, 2026
Merged

fix: increase group_tag varchar limit from 50 to 255#940
ding113 merged 3 commits into
ding113:devfrom
sususu98:fix/group-tag-length

Conversation

@sususu98

@sususu98 sususu98 commented Mar 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • group_tag stores comma-separated tags in a single varchar column. Users with 6+ tags easily exceeded the 50-char limit.
  • Increases varchar(50) to varchar(255) across DB schema, Zod validation, frontend forms, and batch edit paths.
  • Fixes join(", ") vs join(",") inconsistency between batch and single edit.
  • Adds group_tag <= 255 validation in batch patch contract layer.
  • Keeps single tag name limit at 50 chars (maxTagLength / MAX_GROUP_NAME_LENGTH).

Changes

Layer File Change
DB src/drizzle/schema.ts varchar(50) -> varchar(255)
Migration drizzle/0083_young_killraven.sql ALTER COLUMN SET DATA TYPE varchar(255)
Validation src/lib/validation/schemas.ts max(50) -> max(255) (Create + Update)
Patch contract src/lib/provider-patch-contract.ts Add value.length <= 255 for group_tag
Frontend (legacy) provider-form.legacy.tsx GROUP_TAG_MAX_TOTAL_LENGTH = 255, maxTagLength = 50
Frontend (new) routing-section.tsx GROUP_TAG_MAX_TOTAL_LENGTH = 255, maxTagLength = 50
Batch edit build-patch-draft.ts join(", ") -> join(",")
Test build-patch-draft.test.ts Update assertion to match new join format

Migration Safety

The ALTER TABLE only changes PostgreSQL catalog metadata (increasing varchar length) - no table rewrite, no lock, millisecond execution regardless of table size.

Greptile Summary

This PR increases the group_tag column limit from varchar(50) to varchar(255) to support providers with 6 or more tags, and consistently propagates that change across the DB schema, migration, Zod validation, batch patch contract, and both frontend form implementations. It also fixes a pre-existing inconsistency where the batch edit path joined tags with ", " (space-padded) while the single-edit path used ",", which could silently introduce leading-space artifacts in tags on round-trip.

Key changes:

  • DB migration (0084_needy_jackpot.sql) widens the column — a metadata-only operation in PostgreSQL with no table rewrite.
  • Both CreateProviderSchema and UpdateProviderSchema now enforce max(255) on group_tag.
  • The batch patch contract (provider-patch-contract.ts) adds a value.length <= 255 guard.
  • Both the legacy and new provider form components set GROUP_TAG_MAX_TOTAL_LENGTH = 255 while keeping individual tag length capped at maxTagLength={50}.
  • The join(",") fix in build-patch-draft.ts ensures all paths produce identically-formatted stored values.

Confidence Score: 5/5

  • Safe to merge — changes are additive, the migration is a zero-lock metadata update, and all validation layers are consistently updated.
  • All layers (DB, migration, Zod schema, batch contract, both frontend forms) are updated in sync. The migration is a no-lock PostgreSQL operation. The join(",") fix resolves a real inconsistency without any behavioral regression. The test is updated to match. No new logic paths are introduced.
  • No files require special attention.

Important Files Changed

Filename Overview
drizzle/0084_needy_jackpot.sql Single-line migration that widens group_tag from varchar(50) to varchar(255). In PostgreSQL, increasing a varchar length is a metadata-only operation — no table rewrite or lock.
src/lib/validation/schemas.ts Updated group_tag Zod validation from max(50) to max(255) in both CreateProviderSchema and UpdateProviderSchema, aligned with the new DB column size.
src/lib/provider-patch-contract.ts Added value.length <= 255 check for group_tag in the batch patch contract. An empty-string set value is technically still accepted (length 0 passes the check), which was already flagged in a previous review thread.
src/app/[locale]/settings/providers/_components/batch-edit/build-patch-draft.ts Fixed join(",") (no space) when building the batch patch draft — previously used join(", "), which diverged from the single-edit path and could produce leading spaces when tags were reloaded.
src/app/[locale]/settings/providers/_components/forms/provider-form.legacy.tsx Updated GROUP_TAG_MAX_TOTAL_LENGTH to 255 and validates total serialized length in both the onChange handler and the submit handler; per-tag limit correctly kept at maxTagLength={50}.
src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx Same total-length guard as the legacy form — GROUP_TAG_MAX_TOTAL_LENGTH = 255 with per-tag maxTagLength={50} unchanged.
tests/unit/settings/providers/build-patch-draft.test.ts Test assertion updated from "tagA, tagB" to "tagA,tagB" matching the corrected join(",") format.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["User adds/removes a tag in TagInput\n(maxTagLength = 50 per tag)"] --> B["handleGroupTagChange\ntags.join(',').length > 255?"]
    B -- "Yes" --> C["toast.error — reject change"]
    B -- "No" --> D["Update form state\n(groupTag: string[])"]

    D --> E{Submit path}

    E -- "Single edit\n(provider-form)" --> F["group_tag = tags.join(',') || null\nPOST/PATCH API"]
    E -- "Batch edit\n(build-patch-draft)" --> G["joined = tags.join(',')\nif empty → clear, else set"]

    F --> H["UpdateProviderSchema\nZod max(255)"]
    G --> I["normalizeProviderBatchPatchDraft\nvalue.length <= 255"]

    H --> J[("DB: providers.group_tag\nvarchar(255)")]
    I --> J
Loading

Last reviewed commit: "chore: format code (..."

@coderabbitai

coderabbitai Bot commented Mar 18, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

将 providers.group_tag 的最大长度从 50 扩展到 255,包含数据库迁移、Drizzle 模式与后端验证更新;同时调整构建草稿的分隔符(去除逗号后空格)、TagInput 去重为不区分大小写,并引入 UI 层与总长度检查间的不一致性。

Changes

Cohort / File(s) Summary
数据库迁移与模式
drizzle/meta/_journal.json, drizzle/0084_needy_jackpot.sql, src/drizzle/schema.ts
新增迁移记录并执行 ALTER TABLE,将 providers.group_tagvarchar(50) 改为 varchar(255)
后端验证与补丁合约
src/lib/validation/schemas.ts, src/lib/provider-patch-contract.ts
将 Create/Update schema 中 group_tag 最大长度由 50 提升至 255;在 patch 验证中新增对 group_tag 长度 ≤ 255 的检查。
UI 表单与常量
src/app/[locale]/settings/providers/_components/forms/provider-form.legacy.tsx, src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx
GROUP_TAG_MAX_TOTAL_LENGTH 从 50 改为 255,但单个标签 maxTagLength 在组件中仍以 50 限制,导致总长度校验与单项输入限制不一致;错误信息中使用了固定 50。
构建草稿与测试
src/app/[locale]/settings/providers/_components/batch-edit/build-patch-draft.ts, tests/unit/settings/providers/build-patch-draft.test.ts
routing.groupTag 连接分隔符由 ", " 改为 ",",相应测试期望移除逗号后的空格。
通用 UI 行为调整
src/components/ui/tag-input.tsx
改为对标签去重使用不区分大小写的比较(case-insensitive),使 TagInput 视大小写差异为重复项。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确总结了主要变更:从varchar(50)增加到varchar(255),这是PR的核心目标。
Description check ✅ Passed 描述详细说明了变更原因、所有受影响的层级以及迁移安全性,与changeset完全相关。

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

Important

Merge conflicts detected (Beta)

  • Resolve merge conflict in branch fix/group-tag-length
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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

❤️ Share

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

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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!

This pull request addresses a critical limitation where the group_tag field, used for comma-separated tags, frequently exceeded its previous 50-character limit. The changes comprehensively update the database schema, frontend forms, and validation logic to support a new 255-character limit for the combined group_tag string. Additionally, it standardizes the tag joining mechanism and introduces robust server-side validation to prevent future data integrity issues.

Highlights

  • Increased group_tag character limit: The maximum length for the group_tag field has been increased from 50 to 255 characters across the database schema, Zod validation, and frontend forms. This change accommodates users who require more tags, as the previous limit was easily exceeded by comma-separated values.
  • Standardized tag joining: An inconsistency in how group_tag values are joined (comma-separated) has been resolved. The batch edit path now consistently joins tags without an extra space, matching the single edit behavior.
  • Enhanced server-side validation: Server-side validation for the group_tag length has been added to the batch patch contract layer, ensuring that the combined length of tags does not exceed the new 255-character limit.
  • Maintained individual tag length: While the total group_tag length has increased, the limit for individual tag names remains at 50 characters, providing a clear distinction between the two constraints.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions github-actions Bot added bug Something isn't working area:provider area:UI labels Mar 18, 2026
case "request_timeout_non_streaming_ms":
return typeof value === "number" && Number.isFinite(value);
case "group_tag":
return typeof value === "string" && value.length <= 255;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Empty string is a valid set value for group_tag

The current validation only enforces a maximum length, but allows group_tag to be set to an empty string "" via the batch patch API. Through the UI this never happens (an empty tag array is sent as { clear: true } → stored as null), but a direct API call with { group_tag: { set: "" } } would store "" in the database instead of null.

The single-edit path handles this correctly with an explicit ternary in provider-form/index.tsx (line 364): group_tag: state.routing.groupTag.length > 0 ? ... : null. Consider adding a minimum length check to the batch contract to keep both paths consistent:

Suggested change
return typeof value === "string" && value.length <= 255;
case "group_tag":
return typeof value === "string" && value.length > 0 && value.length <= 255;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/lib/provider-patch-contract.ts
Line: 237

Comment:
**Empty string is a valid `set` value for `group_tag`**

The current validation only enforces a maximum length, but allows `group_tag` to be set to an empty string `""` via the batch patch API. Through the UI this never happens (an empty tag array is sent as `{ clear: true }` → stored as `null`), but a direct API call with `{ group_tag: { set: "" } }` would store `""` in the database instead of `null`.

The single-edit path handles this correctly with an explicit ternary in `provider-form/index.tsx` (line 364): `group_tag: state.routing.groupTag.length > 0 ? ... : null`. Consider adding a minimum length check to the batch contract to keep both paths consistent:

```suggestion
    case "group_tag":
      return typeof value === "string" && value.length > 0 && value.length <= 255;
```

How can I resolve this? If you propose a fix, please make it concise.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request effectively increases the group_tag character limit from 50 to 255 across various parts of the application, including the database schema, validation, and frontend forms. The changes are consistent and address the issue described. My review includes a couple of suggestions to improve maintainability by extracting hardcoded values into constants. I've also noted that src/lib/validation/schemas.ts contains a large number of stylistic formatting changes that are out of scope for this fix; it would be better to handle such changes in a separate pull request to keep this one focused.

I am having trouble creating individual review comments. Click here to see my feedback.

src/app/[locale]/settings/providers/_components/forms/provider-form.legacy.tsx (893-899)

medium

The magic number 50 is used here for the maximum length of a single tag. To improve readability and maintainability, it would be better to define this as a constant at the top of the file (e.g., const MAX_SINGLE_TAG_LENGTH = 50;) and use that constant in both maxTagLength and the too_long message.

src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx (161-167)

medium

Similar to the legacy form, the magic number 50 is used here for the maximum length of a single tag. It would be best to define this as a constant to improve maintainability, for example const MAX_SINGLE_TAG_LENGTH = 50;, and use it here.

src/lib/validation/schemas.ts (24-46)

medium

This file contains numerous formatting changes that seem out of scope for this PR's objective of increasing the group_tag limit. While consistent formatting is good, mixing it with functional changes makes the review harder and pollutes the commit history for this file. It's best practice to separate formatting/refactoring changes into their own PRs. Please consider reverting the stylistic changes in this file and applying them in a separate commit or pull request.

@github-actions github-actions Bot added the size/S Small PR (< 200 lines) label Mar 18, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review Summary

This is a well-coordinated fix that increases the group_tag varchar limit from 50 to 255 across all layers of the application. The changes are consistent and properly propagated through the stack.

PR Size: S

  • Lines changed: 4064 (4052 additions, 12 deletions)
  • Files changed: 11
  • Note: ~4000 lines are auto-generated Drizzle snapshot metadata; actual meaningful code changes are ~15 lines

Changes Verified

Layer File Change Status
DB Schema src/drizzle/schema.ts varchar(50) -> varchar(255) Correct
Migration drizzle/0083_young_killraven.sql ALTER COLUMN statement Correct
Validation src/lib/validation/schemas.ts max(50) -> max(255) in Create + Update Correct
Patch Contract src/lib/provider-patch-contract.ts Added value.length <= 255 check Correct
Frontend (legacy) provider-form.legacy.tsx Constant + comment updated Correct
Frontend (new) routing-section.tsx Total limit 255, individual tag limit 50 Correct
Batch Edit build-patch-draft.ts join(", ") -> join(",") Correct
Test build-patch-draft.test.ts Assertion updated Correct

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

Observations

  1. Proper layer coordination: The limit change is correctly propagated through DB schema, backend validation, batch patch contract, and frontend forms.

  2. Smart separation of concerns: The PR correctly distinguishes between:

    • Total serialized length (255 chars) - the comma-separated string stored in DB
    • Individual tag length (50 chars) - each tag name's limit
  3. Join format consistency: Fixed the join(", ") vs join(",") inconsistency between batch and single edit paths.

  4. Comment accuracy: Updated the Chinese comment in provider-form.legacy.tsx to reflect the new limit.

  5. Migration safety: The ALTER TABLE ... SET DATA TYPE varchar(255) is metadata-only for PostgreSQL (increasing varchar length), no table rewrite required.

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Adequate
  • Code clarity - Good

Automated review by Claude AI

Multiple comma-joined tags easily exceeded the 50-char DB limit.

- Schema: varchar(50) -> varchar(255) with migration (0084)
- Zod: max(50) -> max(255) in Create/UpdateProviderSchema
- Frontend: GROUP_TAG_MAX_TOTAL_LENGTH 50 -> 255 (total serialized length)
- Frontend: maxTagLength kept at 50 (single tag name limit)
- Batch edit: join(", ") -> join(",") to match single-edit format
- Patch contract: add group_tag <= 255 length validation
- Test: update build-patch-draft assertion to match new join format
@sususu98
sususu98 force-pushed the fix/group-tag-length branch from 6a697ca to b21fca3 Compare March 18, 2026 04:44

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
tests/unit/settings/providers/build-patch-draft.test.ts (1)

156-164: ⚠️ Potential issue | 🟡 Minor

建议补充 group_tag 长度边界测试(255/256)。

当前用例已覆盖分隔符变更,但建议再补两组断言:length === 255 通过、length === 256 拒绝(走 patch contract / schema 校验),以锁住这次上限调整的核心行为。
As per coding guidelines, "**/*.test.{ts,tsx,js,jsx}: All new features must have unit test coverage of at least 80%".

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

In `@tests/unit/settings/providers/build-patch-draft.test.ts` around lines 156 -
164, Add two unit tests to
tests/unit/settings/providers/build-patch-draft.test.ts around the existing case
for buildPatchDraftFromFormState: one that sets state.routing.groupTag to a
joined string of length exactly 255 and asserts draft.group_tag equals { set:
"<255-char-string>" } (i.e., accepted), and another that sets
state.routing.groupTag to produce a joined string of length 256 and asserts that
buildPatchDraftFromFormState fails validation or produces a rejection according
to the patch contract/schema (e.g., throws or returns an error/invalid draft).
Use the same pattern as the existing test (mutate state.routing.groupTag and
dirty = new Set(["routing.groupTag"])) and target the output symbol group_tag
from buildPatchDraftFromFormState to lock the boundary behavior.
🧹 Nitpick comments (3)
src/lib/provider-patch-contract.ts (1)

236-237: 建议在契约层补齐“单个标签 <= 50”校验,避免绕过前端限制。

现在只校验总长度 255,外部调用仍可写入超长单标签(例如单个 200 字符),会和前端规则不一致。建议在这里同步做分项校验。

可选改法(示例)
     case "group_tag":
-      return typeof value === "string" && value.length <= 255;
+      if (typeof value !== "string" || value.length > 255) {
+        return false;
+      }
+      return value
+        .split(",")
+        .map((tag) => tag.trim())
+        .filter(Boolean)
+        .every((tag) => tag.length <= 50);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/provider-patch-contract.ts` around lines 236 - 237, In the
"group_tag" validation branch in provider-patch-contract.ts (the case
"group_tag" clause) add per-tag length checks: ensure value is a string, its
total length <= 255, then split the string by commas (trim each piece), reject
if any individual tag is empty or longer than 50 characters; keep the overall
total length check and return false if any of these checks fail. This aligns the
contract with the front-end rule "single tag <= 50".
src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx (2)

161-167: 实现正确,建议提取魔数常量。

maxTagLength={50} 是单个标签的字符限制,与 too_long 错误消息中的 { max: 50 } 一致。逻辑正确,但建议将 50 提取为命名常量(如 MAX_GROUP_TAG_NAME_LENGTH),以便与 GROUP_TAG_MAX_TOTAL_LENGTH 形成清晰对比,并与 provider-form.legacy.tsx 保持同步。

♻️ 可选重构建议
+const MAX_GROUP_TAG_NAME_LENGTH = 50;
 const GROUP_TAG_MAX_TOTAL_LENGTH = 255;
 
 // ...
 
-                maxTagLength={50}
+                maxTagLength={MAX_GROUP_TAG_NAME_LENGTH}
                 suggestions={groupSuggestions}
                 onInvalidTag={(_tag, reason) => {
                   const messages: Record<string, string> = {
                     empty: tUI("emptyTag"),
                     duplicate: tUI("duplicateTag"),
-                    too_long: tUI("tooLong", { max: 50 }),
+                    too_long: tUI("tooLong", { max: MAX_GROUP_TAG_NAME_LENGTH }),
🤖 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/sections/routing-section.tsx
around lines 161 - 167, The magic number 50 is used in maxTagLength and the
too_long message; extract it to a named constant (e.g.,
MAX_GROUP_TAG_NAME_LENGTH) and replace both uses in the component (references:
maxTagLength prop, onInvalidTag messages object, too_long message interpolation)
so the limit is centralized and clearly contrasted with
GROUP_TAG_MAX_TOTAL_LENGTH; also ensure the same constant is used or
synchronized with provider-form.legacy.tsx to keep behaviour consistent across
files.

28-28: 常量定义正确,但存在跨文件重复。

GROUP_TAG_MAX_TOTAL_LENGTH = 255 与数据库 schema 的 varchar(255) 限制一致。但此常量在 provider-form.legacy.tsx 中也有定义,建议提取到共享常量文件(如 @/lib/constants/provider.constants.ts)以避免重复和不同步风险。

🤖 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/sections/routing-section.tsx
at line 28, GROUP_TAG_MAX_TOTAL_LENGTH is defined in routing-section.tsx and
duplicated in provider-form.legacy.tsx; extract this value into a single shared
constants module (e.g., provider.constants) and export
GROUP_TAG_MAX_TOTAL_LENGTH = 255 from there, then update routing-section.tsx and
provider-form.legacy.tsx to import and use that exported constant and remove
their local definitions to avoid duplication and drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@drizzle/0083_young_killraven.sql`:
- Line 1: Do not hand-edit the SQL migration file 0083_young_killraven.sql;
instead update the providers table definition in src/drizzle/schema.ts to change
the group_tag column to varchar(255) (or the equivalent Drizzle column type),
then run the project generator command bun run db:generate to produce the
correct migration and journal entries; finally remove the manually created
0083_young_killraven.sql so the repo only contains generated migrations
consistent with Drizzle metadata.

In `@src/lib/validation/schemas.ts`:
- Line 444: The hardcoded Chinese error message in the zod schema for group_tag
must be replaced with an i18n key; locate the zod rule using group_tag:
z.string().max(...).nullable().optional() (and the similar occurrence later) and
change the second argument (the user-facing error message) to use the i18n
translation key (for example via the project i18n helper like
t('validation.group_tag.max_length') or a shared validation key) so that all
user-facing strings are resolved through i18n for supported locales; ensure both
occurrences (the group_tag max message and the duplicate instance in the file)
are updated to use the same i18n key/format.

---

Outside diff comments:
In `@tests/unit/settings/providers/build-patch-draft.test.ts`:
- Around line 156-164: Add two unit tests to
tests/unit/settings/providers/build-patch-draft.test.ts around the existing case
for buildPatchDraftFromFormState: one that sets state.routing.groupTag to a
joined string of length exactly 255 and asserts draft.group_tag equals { set:
"<255-char-string>" } (i.e., accepted), and another that sets
state.routing.groupTag to produce a joined string of length 256 and asserts that
buildPatchDraftFromFormState fails validation or produces a rejection according
to the patch contract/schema (e.g., throws or returns an error/invalid draft).
Use the same pattern as the existing test (mutate state.routing.groupTag and
dirty = new Set(["routing.groupTag"])) and target the output symbol group_tag
from buildPatchDraftFromFormState to lock the boundary behavior.

---

Nitpick comments:
In
`@src/app/`[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx:
- Around line 161-167: The magic number 50 is used in maxTagLength and the
too_long message; extract it to a named constant (e.g.,
MAX_GROUP_TAG_NAME_LENGTH) and replace both uses in the component (references:
maxTagLength prop, onInvalidTag messages object, too_long message interpolation)
so the limit is centralized and clearly contrasted with
GROUP_TAG_MAX_TOTAL_LENGTH; also ensure the same constant is used or
synchronized with provider-form.legacy.tsx to keep behaviour consistent across
files.
- Line 28: GROUP_TAG_MAX_TOTAL_LENGTH is defined in routing-section.tsx and
duplicated in provider-form.legacy.tsx; extract this value into a single shared
constants module (e.g., provider.constants) and export
GROUP_TAG_MAX_TOTAL_LENGTH = 255 from there, then update routing-section.tsx and
provider-form.legacy.tsx to import and use that exported constant and remove
their local definitions to avoid duplication and drift.

In `@src/lib/provider-patch-contract.ts`:
- Around line 236-237: In the "group_tag" validation branch in
provider-patch-contract.ts (the case "group_tag" clause) add per-tag length
checks: ensure value is a string, its total length <= 255, then split the string
by commas (trim each piece), reject if any individual tag is empty or longer
than 50 characters; keep the overall total length check and return false if any
of these checks fail. This aligns the contract with the front-end rule "single
tag <= 50".
🪄 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: f0126662-6a88-4489-ac94-67d1570aedf5

📥 Commits

Reviewing files that changed from the base of the PR and between 0f725c6 and 6a697ca.

📒 Files selected for processing (11)
  • CHANGELOG.md
  • drizzle/0083_young_killraven.sql
  • drizzle/meta/0083_snapshot.json
  • drizzle/meta/_journal.json
  • src/app/[locale]/settings/providers/_components/batch-edit/build-patch-draft.ts
  • src/app/[locale]/settings/providers/_components/forms/provider-form.legacy.tsx
  • src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx
  • src/drizzle/schema.ts
  • src/lib/provider-patch-contract.ts
  • src/lib/validation/schemas.ts
  • tests/unit/settings/providers/build-patch-draft.test.ts

Comment thread drizzle/0083_young_killraven.sql Outdated
@@ -0,0 +1 @@
ALTER TABLE "providers" ALTER COLUMN "group_tag" SET DATA TYPE varchar(255);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

请不要手写 Drizzle 迁移文件。

这条 SQL 本身可执行,但当前方式违背仓库迁移流程,后续容易出现 schema 与迁移元数据漂移。建议保留 src/drizzle/schema.ts 的变更后,重新执行 bun run db:generate 生成迁移与 journal 记录。
As per coding guidelines, "Modify database schema in src/drizzle/schema.ts, then run bun run db:generate to generate migrations. Never create SQL migration files manually".

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

In `@drizzle/0083_young_killraven.sql` at line 1, Do not hand-edit the SQL
migration file 0083_young_killraven.sql; instead update the providers table
definition in src/drizzle/schema.ts to change the group_tag column to
varchar(255) (or the equivalent Drizzle column type), then run the project
generator command bun run db:generate to produce the correct migration and
journal entries; finally remove the manually created 0083_young_killraven.sql so
the repo only contains generated migrations consistent with Drizzle metadata.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/app/[locale]/settings/providers/_components/forms/provider-form.legacy.tsx (1)

893-893: 建议提取单标签长度常量,避免后续漂移。

Line 893 和 Line 899 里 50 出现了重复字面量,建议抽成统一常量(例如 GROUP_TAG_MAX_SINGLE_LENGTH),减少未来修改遗漏。

可选重构示例
 const GROUP_TAG_MAX_TOTAL_LENGTH = 255;
+const GROUP_TAG_MAX_SINGLE_LENGTH = 50;
@@
-                    maxTagLength={50}
+                    maxTagLength={GROUP_TAG_MAX_SINGLE_LENGTH}
@@
-                        too_long: tUI("tooLong", { max: 50 }),
+                        too_long: tUI("tooLong", { max: GROUP_TAG_MAX_SINGLE_LENGTH }),

Also applies to: 899-899

🤖 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.legacy.tsx
at line 893, Extract the repeated literal 50 used for the single tag length into
a named constant (e.g., GROUP_TAG_MAX_SINGLE_LENGTH) and replace both
occurrences of maxTagLength={50} in provider-form.legacy.tsx with
maxTagLength={GROUP_TAG_MAX_SINGLE_LENGTH}; declare the constant near the top of
the module (or next to other form constants) and export it if needed for reuse
so future changes only require updating the single constant instead of multiple
literals.
🤖 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/lib/validation/schemas.ts`:
- Line 444: The current zod field group_tag (in src/lib/validation/schemas.ts)
only enforces total length ≤255 and allows bypassing the business rule that each
individual tag must be ≤50 characters; update the schema to add a per-tag
validation (e.g., a .refine or custom validator on group_tag) that splits the
string into tags (by the same delimiter used by the app), trims each tag, and
returns false if any tag.length > 50, providing a clear error message; extract
this enhanced rule into a reusable schema/constant (e.g., groupTagSchema or
similar) and replace both occurrences of group_tag so the same single-tag
constraint is applied consistently (including the instances currently at the two
locations you found).

---

Nitpick comments:
In
`@src/app/`[locale]/settings/providers/_components/forms/provider-form.legacy.tsx:
- Line 893: Extract the repeated literal 50 used for the single tag length into
a named constant (e.g., GROUP_TAG_MAX_SINGLE_LENGTH) and replace both
occurrences of maxTagLength={50} in provider-form.legacy.tsx with
maxTagLength={GROUP_TAG_MAX_SINGLE_LENGTH}; declare the constant near the top of
the module (or next to other form constants) and export it if needed for reuse
so future changes only require updating the single constant instead of multiple
literals.
🪄 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: 67a929d9-0f52-4d89-8016-e68ebb8fce4c

📥 Commits

Reviewing files that changed from the base of the PR and between 6a697ca and b21fca3.

📒 Files selected for processing (10)
  • drizzle/0084_needy_jackpot.sql
  • drizzle/meta/0084_snapshot.json
  • drizzle/meta/_journal.json
  • src/app/[locale]/settings/providers/_components/batch-edit/build-patch-draft.ts
  • src/app/[locale]/settings/providers/_components/forms/provider-form.legacy.tsx
  • src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx
  • src/drizzle/schema.ts
  • src/lib/provider-patch-contract.ts
  • src/lib/validation/schemas.ts
  • tests/unit/settings/providers/build-patch-draft.test.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/app/[locale]/settings/providers/_components/batch-edit/build-patch-draft.ts
  • drizzle/meta/_journal.json
  • src/drizzle/schema.ts
  • src/app/[locale]/settings/providers/_components/forms/provider-form/sections/routing-section.tsx
  • src/lib/provider-patch-contract.ts

.default(null),
cost_multiplier: z.coerce.number().min(0, "成本倍率不能为负数").optional().default(1.0),
group_tag: z.string().max(50, "分组标签不能超过50个字符").nullable().optional(),
group_tag: z.string().max(255, "分组标签不能超过255个字符").nullable().optional(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

后端缺少“单个标签长度 ≤ 50”的强校验,规则可被绕过。

Line 444 和 Line 682 仅校验整串 group_tag 的总长度 <= 255。这会允许通过 API 直接写入超长单个标签(例如 120 字符),与“单标签上限 50”的业务约束不一致。

建议修复(统一复用 schema,增加单标签约束)
+const GROUP_TAG_MAX_TOTAL_LENGTH = 255;
+const GROUP_TAG_MAX_SINGLE_LENGTH = 50;
+
+const GROUP_TAG_SCHEMA = z
+  .string()
+  .max(GROUP_TAG_MAX_TOTAL_LENGTH, "分组标签不能超过255个字符")
+  .refine(
+    (value) =>
+      value
+        .split(",")
+        .every((tag) => {
+          const trimmed = tag.trim();
+          return trimmed.length > 0 && trimmed.length <= GROUP_TAG_MAX_SINGLE_LENGTH;
+        }),
+    "单个分组标签长度不能超过50个字符"
+  )
+  .nullable()
+  .optional();
+
 export const CreateProviderSchema = z
   .object({
@@
-    group_tag: z.string().max(255, "分组标签不能超过255个字符").nullable().optional(),
+    group_tag: GROUP_TAG_SCHEMA,
@@
 export const UpdateProviderSchema = z
   .object({
@@
-    group_tag: z.string().max(255, "分组标签不能超过255个字符").nullable().optional(),
+    group_tag: GROUP_TAG_SCHEMA,

Also applies to: 682-682

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

In `@src/lib/validation/schemas.ts` at line 444, The current zod field group_tag
(in src/lib/validation/schemas.ts) only enforces total length ≤255 and allows
bypassing the business rule that each individual tag must be ≤50 characters;
update the schema to add a per-tag validation (e.g., a .refine or custom
validator on group_tag) that splits the string into tags (by the same delimiter
used by the app), trims each tag, and returns false if any tag.length > 50,
providing a clear error message; extract this enhanced rule into a reusable
schema/constant (e.g., groupTagSchema or similar) and replace both occurrences
of group_tag so the same single-tag constraint is applied consistently
(including the instances currently at the two locations you found).

sususu98 and others added 2 commits March 18, 2026 13:06
Prevents adding tags that differ only in casing (e.g. 'openapiha' vs
'openapiHA'), which would cause confusion since the backend group
matching uses exact string comparison.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 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 `@src/components/ui/tag-input.tsx`:
- Around line 209-212: filteredSuggestions filtering is case-sensitive while
duplicate detection in the add flow uses toLowerCase(), causing suggestions like
"Test" to show after "test" was added then be rejected; update the
filteredSuggestions computation (the symbol filteredSuggestions) to use the same
case-insensitive membership check as the duplicate check (compare lowercased
candidate s.value against existing currentTags and/or the typed value) so
suggestions that are case-insensitive duplicates are excluded, keeping
onInvalidTag, allowDuplicates and currentTags logic unchanged.
🪄 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: 3114f1c7-7de9-4398-900d-d6f44d2322c5

📥 Commits

Reviewing files that changed from the base of the PR and between b21fca3 and d3acb20.

📒 Files selected for processing (1)
  • src/components/ui/tag-input.tsx

Comment on lines +209 to 212
if (!allowDuplicates && currentTags.some((t) => t.toLowerCase() === tag.toLowerCase())) {
onInvalidTag?.(tag, "duplicate");
return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

大小写不敏感的重复检测与建议列表过滤不一致

此处改为 toLowerCase() 比较,逻辑正确。但第 192 行的 filteredSuggestions 仍使用 !value.includes(s.value) 进行大小写敏感过滤。

这会导致用户体验问题:当已添加 "test" 时,建议列表仍会显示 "Test",但用户选择后会被此处拒绝为重复项。

建议同步修改第 192 行的过滤逻辑:

建议修复
   const filteredSuggestions = React.useMemo(() => {
     if (!normalizedSuggestions.length) return [];
     const search = inputValue.toLowerCase();
+    const lowerCaseTags = value.map((v) => v.toLowerCase());
     return normalizedSuggestions.filter((s) => {
       const keywords = s.keywords?.join(" ") || "";
       const haystack = `${s.label} ${s.value} ${keywords}`.toLowerCase();
-      return haystack.includes(search) && (allowDuplicates || !value.includes(s.value));
+      return haystack.includes(search) && (allowDuplicates || !lowerCaseTags.includes(s.value.toLowerCase()));
     });
   }, [normalizedSuggestions, inputValue, value, allowDuplicates]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/tag-input.tsx` around lines 209 - 212, filteredSuggestions
filtering is case-sensitive while duplicate detection in the add flow uses
toLowerCase(), causing suggestions like "Test" to show after "test" was added
then be rejected; update the filteredSuggestions computation (the symbol
filteredSuggestions) to use the same case-insensitive membership check as the
duplicate check (compare lowercased candidate s.value against existing
currentTags and/or the typed value) so suggestions that are case-insensitive
duplicates are excluded, keeping onInvalidTag, allowDuplicates and currentTags
logic unchanged.

@ding113
ding113 merged commit 3ef6074 into ding113:dev Mar 18, 2026
1 check passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Claude Code Hub Roadmap Mar 18, 2026
@tesgth032

Copy link
Copy Markdown
Contributor

整体思路我觉得是对的,group_tag 扩到 255,以及把 batch / single edit 的 join 统一起来,这两点都很合理。我这边 review 下来有两个想在 merge 前再确认一下的点:

  1. TagInput 这次把重复判断改成了大小写不敏感(currentTags.some((t) => t.toLowerCase() === tag.toLowerCase())),但建议列表过滤还在用大小写敏感的 !value.includes(s.value)。这样当现有标签里已经有 test 时,建议里仍然会出现 Test,用户点选后又会被判成 duplicate。位置大致在 src/components/ui/tag-input.tsx186-192209 附近。建议让 filteredSuggestions 也复用同一套 case-insensitive 判重逻辑,并顺手补一个 regression test。

  2. PR 描述里提到“单个 tag 仍限制 50 字符”,但目前这个约束看起来还只停留在前端 TagInput:两个表单还是 maxTagLength={50},而 server side 的单编 / 批量入口现在都只校验整串 <= 255src/lib/validation/schemas.ts444 / 682 附近,以及 src/lib/provider-patch-contract.ts236-237)。这意味着类似单个 200 字符 tag 的值,仍然可以通过 server action / batch patch 写进去,和前端规则不一致。若 50 这个限制本身仍然是业务规则,建议把单标签长度校验也下沉到 schema / contract 层;如果这里只是想保留前端交互限制,那可能需要在 PR 描述里明确一下,避免给 reviewer 造成“服务端也已经收口”的理解。

顺手补充下我这边的验证:我在临时 worktree 上跑了
bunx vitest run tests/unit/settings/providers/build-patch-draft.test.ts tests/unit/actions/providers-patch-contract.test.ts src/components/ui/__tests__/tag-input-dialog.test.tsx src/components/ui/__tests__/command-tag-highlight.test.tsx
都通过了,所以这两个点目前更像是“现有测试还没覆盖到的行为缝隙”。

sususu98 added a commit to sususu98/claude-code-hub that referenced this pull request Mar 18, 2026
The suggestion dropdown still showed tags that only differed in casing
from already-selected tags (e.g. showing 'openapiHA' when 'openapiha'
was already selected). Selecting such a suggestion would then fail as
a duplicate, creating a confusing UX.

Follows up on the case-insensitive duplicate check added in ding113#940.
ding113 pushed a commit that referenced this pull request Mar 18, 2026
The suggestion dropdown still showed tags that only differed in casing
from already-selected tags (e.g. showing 'openapiHA' when 'openapiha'
was already selected). Selecting such a suggestion would then fail as
a duplicate, creating a confusing UX.

Follows up on the case-insensitive duplicate check added in #940.
@github-actions github-actions Bot mentioned this pull request Mar 18, 2026
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:provider area:UI bug Something isn't working size/S Small PR (< 200 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants