feat(provider-form): soften validation with "save anyway" prompt#2307
Conversation
farion1231
left a comment
There was a problem hiding this comment.
感谢您的贡献,目前看方向上是没有问题的,只是对校验的放宽导致了一个意外的情况:
OpenCode / OpenClaw / Hermes 这三个应用的供应商里面,有一个内部用来区分供应商的一个 Provider Key,#2307 在 ProviderForm.tsx 把 OpenCode / OpenClaw / Hermes 的空 providerKey 改成 soft issue,但新增保存链路里 AddProviderDialog.tsx:108-114 只有 truthy 才传 providerKey,随后 src/lib/query/mutations.ts:26-35 会直接抛 Provider key is required for ${appId}。结果是用户点“仍要保存”后仍然保存失败,只是从明确的字段错误变成后续通用失败 toast。建议把空 providerKey 继续保留为硬拒绝,或者在确认保存前生成合法 key。
[P2] 确认框的提交路径绕过了 react-hook-form 的提交状态。
ProviderForm.tsx:2149-2155 里 void performSubmit(values) 不会进入 RHF 的 isSubmitting 生命周期,也没有 await/catch。保存慢或失败时,外层按钮状态不会反映真实提交中状态,失败时也更容易出现未处理 Promise。建议给软确认单独加提交中状态,或让 confirm handler await performSubmit 并处理异常。
|
感谢这么细致的 review!两条反馈都是真实的 bug,确认会修: P1:同意你的建议 a(保留硬拒绝)。重新想了一下,providerKey 其实就是 OpenCode / OpenClaw / Hermes 三个 App 的主键 ID,本质和「providerKey 正则不合法 / 重复」一样属于完整性约束;我最初把"空"归 A 类是分类错误。自动生成 key 虽然也能走通,但会留下用户下次编辑时看不懂的陌生 ID,反而不如明确报错。 P2:会给软确认框单独加一个 修好后再 push 一版过来,请再帮忙看看。 |
P1: move empty providerKey back to hard rejection for OpenCode / OpenClaw / Hermes. Since providerKey is the primary identity for these apps and the mutations layer throws "Provider key is required" when absent, letting users click "save anyway" would surface a generic error toast instead of a precise, actionable one. Treat empty providerKey as an integrity constraint alongside regex / duplicate checks. P2: give the soft-confirm submit path its own submitting state. The confirm-dialog path bypassed react-hook-form's isSubmitting lifecycle, so slow or failing saves left the outer submit button responsive and could spawn unhandled rejections. Now the confirm handler awaits performSubmit inside try/catch/finally, uses an isConfirmSubmitting flag to gate both confirm and cancel clicks, and folds the flag into the outer disabled state and onSubmittingChange callback. Refs: farion1231#2307 review comments
|
已推 P1:opencode / openclaw / hermes 的空 providerKey 移回硬拒绝。regex / duplicate 的原有保护一并保留(之前顺带加的 `.trim() &&` 短路也移除了,避免和「空即硬拒绝」语义重叠)。 P2:
验证: 再麻烦 review 一下。如果方向认可我就转 Ready。 |
…y" prompt Refactor handleSubmit so empty-field / missing-item validations (provider name, endpoint, API key, opencode model, template variables, provider key required) no longer hard-reject with toast.error. Instead they are collected into an issues list and presented via a ConfirmDialog; the user can cancel or choose "Save anyway" to proceed. Integrity constraints stay as hard rejections: - providerKey regex / duplicate (would corrupt other providers) - Copilot / Codex OAuth not authenticated (no token, cannot establish) - omo Other Fields JSON not an object / parse failure This aligns the frontend with the backend's existing "relaxed save / strict switch" split (see gemini_config.rs: validate_gemini_settings vs validate_gemini_settings_strict) and unblocks legitimate configs such as AWS Bedrock, Vertex AI, and custom Gemini base URLs that the UI previously refused to save. Refs: farion1231#2196, farion1231#1204
P1: move empty providerKey back to hard rejection for OpenCode / OpenClaw / Hermes. Since providerKey is the primary identity for these apps and the mutations layer throws "Provider key is required" when absent, letting users click "save anyway" would surface a generic error toast instead of a precise, actionable one. Treat empty providerKey as an integrity constraint alongside regex / duplicate checks. P2: give the soft-confirm submit path its own submitting state. The confirm-dialog path bypassed react-hook-form's isSubmitting lifecycle, so slow or failing saves left the outer submit button responsive and could spawn unhandled rejections. Now the confirm handler awaits performSubmit inside try/catch/finally, uses an isConfirmSubmitting flag to gate both confirm and cancel clicks, and folds the flag into the outer disabled state and onSubmittingChange callback. Refs: farion1231#2307 review comments
Clippy 1.95 added single_char_add_str which flagged the push_str("…")
in truncate_body. Rebased onto latest upstream/main and applied the
suggested fix so the Backend Checks clippy job passes.
Unrelated to this PR's core changes; bundled in so the PR is mergeable
without waiting for a separate upstream fix.
910ccc9 to
a67c960
Compare
|
补充一条说明:CI Backend Checks 的 Clippy 报错和本 PR 的改动无关——是 Rust 1.95 新加的 为了让 PR 能合,我把这个 PR rebase 到了最新 这条 clippy 修复严格说不在本 PR 范围内,如果你觉得应该单独走,我可以把它拎出来发新 PR,本 PR 回滚那 1 行。不过合并进来更简便——两个 commit 历史清晰分开。 |
…ion1231#2307) * feat(provider-form): soften business-rule validation with "save anyway" prompt Refactor handleSubmit so empty-field / missing-item validations (provider name, endpoint, API key, opencode model, template variables, provider key required) no longer hard-reject with toast.error. Instead they are collected into an issues list and presented via a ConfirmDialog; the user can cancel or choose "Save anyway" to proceed. Integrity constraints stay as hard rejections: - providerKey regex / duplicate (would corrupt other providers) - Copilot / Codex OAuth not authenticated (no token, cannot establish) - omo Other Fields JSON not an object / parse failure This aligns the frontend with the backend's existing "relaxed save / strict switch" split (see gemini_config.rs: validate_gemini_settings vs validate_gemini_settings_strict) and unblocks legitimate configs such as AWS Bedrock, Vertex AI, and custom Gemini base URLs that the UI previously refused to save. Refs: farion1231#2196, farion1231#1204 * fix(provider-form): address review feedback on soft-validation P1: move empty providerKey back to hard rejection for OpenCode / OpenClaw / Hermes. Since providerKey is the primary identity for these apps and the mutations layer throws "Provider key is required" when absent, letting users click "save anyway" would surface a generic error toast instead of a precise, actionable one. Treat empty providerKey as an integrity constraint alongside regex / duplicate checks. P2: give the soft-confirm submit path its own submitting state. The confirm-dialog path bypassed react-hook-form's isSubmitting lifecycle, so slow or failing saves left the outer submit button responsive and could spawn unhandled rejections. Now the confirm handler awaits performSubmit inside try/catch/finally, uses an isConfirmSubmitting flag to gate both confirm and cancel clicks, and folds the flag into the outer disabled state and onSubmittingChange callback. Refs: farion1231#2307 review comments * chore(clippy): use push for single char '…' in truncate_body Clippy 1.95 added single_char_add_str which flagged the push_str("…") in truncate_body. Rebased onto latest upstream/main and applied the suggested fix so the Backend Checks clippy job passes. Unrelated to this PR's core changes; bundled in so the PR is mergeable without waiting for a separate upstream fix. --------- Co-authored-by: Allen <[email protected]>
…ion1231#2307) * feat(provider-form): soften business-rule validation with "save anyway" prompt Refactor handleSubmit so empty-field / missing-item validations (provider name, endpoint, API key, opencode model, template variables, provider key required) no longer hard-reject with toast.error. Instead they are collected into an issues list and presented via a ConfirmDialog; the user can cancel or choose "Save anyway" to proceed. Integrity constraints stay as hard rejections: - providerKey regex / duplicate (would corrupt other providers) - Copilot / Codex OAuth not authenticated (no token, cannot establish) - omo Other Fields JSON not an object / parse failure This aligns the frontend with the backend's existing "relaxed save / strict switch" split (see gemini_config.rs: validate_gemini_settings vs validate_gemini_settings_strict) and unblocks legitimate configs such as AWS Bedrock, Vertex AI, and custom Gemini base URLs that the UI previously refused to save. Refs: farion1231#2196, farion1231#1204 * fix(provider-form): address review feedback on soft-validation P1: move empty providerKey back to hard rejection for OpenCode / OpenClaw / Hermes. Since providerKey is the primary identity for these apps and the mutations layer throws "Provider key is required" when absent, letting users click "save anyway" would surface a generic error toast instead of a precise, actionable one. Treat empty providerKey as an integrity constraint alongside regex / duplicate checks. P2: give the soft-confirm submit path its own submitting state. The confirm-dialog path bypassed react-hook-form's isSubmitting lifecycle, so slow or failing saves left the outer submit button responsive and could spawn unhandled rejections. Now the confirm handler awaits performSubmit inside try/catch/finally, uses an isConfirmSubmitting flag to gate both confirm and cancel clicks, and folds the flag into the outer disabled state and onSubmittingChange callback. Refs: farion1231#2307 review comments * chore(clippy): use push for single char '…' in truncate_body Clippy 1.95 added single_char_add_str which flagged the push_str("…") in truncate_body. Rebased onto latest upstream/main and applied the suggested fix so the Backend Checks clippy job passes. Unrelated to this PR's core changes; bundled in so the PR is mergeable without waiting for a separate upstream fix. --------- Co-authored-by: Allen <[email protected]>
…ion1231#2307) * feat(provider-form): soften business-rule validation with "save anyway" prompt Refactor handleSubmit so empty-field / missing-item validations (provider name, endpoint, API key, opencode model, template variables, provider key required) no longer hard-reject with toast.error. Instead they are collected into an issues list and presented via a ConfirmDialog; the user can cancel or choose "Save anyway" to proceed. Integrity constraints stay as hard rejections: - providerKey regex / duplicate (would corrupt other providers) - Copilot / Codex OAuth not authenticated (no token, cannot establish) - omo Other Fields JSON not an object / parse failure This aligns the frontend with the backend's existing "relaxed save / strict switch" split (see gemini_config.rs: validate_gemini_settings vs validate_gemini_settings_strict) and unblocks legitimate configs such as AWS Bedrock, Vertex AI, and custom Gemini base URLs that the UI previously refused to save. Refs: farion1231#2196, farion1231#1204 * fix(provider-form): address review feedback on soft-validation P1: move empty providerKey back to hard rejection for OpenCode / OpenClaw / Hermes. Since providerKey is the primary identity for these apps and the mutations layer throws "Provider key is required" when absent, letting users click "save anyway" would surface a generic error toast instead of a precise, actionable one. Treat empty providerKey as an integrity constraint alongside regex / duplicate checks. P2: give the soft-confirm submit path its own submitting state. The confirm-dialog path bypassed react-hook-form's isSubmitting lifecycle, so slow or failing saves left the outer submit button responsive and could spawn unhandled rejections. Now the confirm handler awaits performSubmit inside try/catch/finally, uses an isConfirmSubmitting flag to gate both confirm and cancel clicks, and folds the flag into the outer disabled state and onSubmittingChange callback. Refs: farion1231#2307 review comments * chore(clippy): use push for single char '…' in truncate_body Clippy 1.95 added single_char_add_str which flagged the push_str("…") in truncate_body. Rebased onto latest upstream/main and applied the suggested fix so the Backend Checks clippy job passes. Unrelated to this PR's core changes; bundled in so the PR is mergeable without waiting for a separate upstream fix. --------- Co-authored-by: Allen <[email protected]>
背景
起因:#2196(Bedrock / Gemini 自定义 BASE_URL 被硬拒绝保存)、#1204(Vertex AI 支持)。详细讨论见 #2196 的评论区。
问题
ProviderForm.tsx的handleSubmit里有 24 处toast.error + return一刀切拒绝保存,但:src-tauri/src/gemini_config.rs已经有「宽松保存 / 严格切换」分层:validate_gemini_settings(保存)vsvalidate_gemini_settings_strict(切换)本 PR 让前端对齐后端现有模式。
改动思路
把校验分两类:
A 类:业务约束(软化 → 弹确认框)
空值、缺项,放行后数据结构仍正确,只是切换时后端 strict 校验会再兜底一次:
B 类:完整性约束(保持硬拒绝)
放行后会产生损坏数据或根本无法工作:
实现
handleSubmit中将 A 类改为issues.push(...),B 类保留toast.error + returnissues.length > 0,弹ConfirmDialog(variant=info)列出所有问题performSubmit(values);「取消」则清空 state,不影响原表单performSubmit承载原 handleSubmit 后半段(构造 payload、组装 meta、调用onSubmit)手动验证步骤
(由于
ProviderForm已有 2000+ 行依赖外部 Tauri API 和多个 react-query hook,仓库现有的AddProviderDialog.test.tsx也是把整个 ProviderForm mock 掉的,所以 PR 未新增单测;现有 213 个单测全部通过。)场景 1:Claude + Bedrock / Vertex AI(原 issue 核心诉求)
ANTHROPIC_AUTH_TOKEN的值为空,或仅通过env中的AWS_*/GOOGLE_APPLICATION_CREDENTIALS等环境变量认证场景 2:Gemini 自定义 BASE_URL
GEMINI_API_KEY和GEMINI_API_BASE_URL(不填 UI 里的端点输入框)场景 3:正常配置不被干扰
场景 4:B 类硬约束保持原有拒绝
Foo_Bar)providerKeyInvalid错误 toast,无法进入确认框流程自动化校验
pnpm typecheck✅pnpm test:unit✅(34 files / 213 tests all passing)pnpm format✅三语 i18n
新增
providerForm.softValidation.{title, hint, saveAnyway},覆盖 zh / en / ja。替代方案与取舍
曾考虑:
相关 issue / PR:
如果方向需要调整(比如更偏向 C),欢迎在 review 中指出,我来改动。