refactor(endpoints): implement deferred submission and fix clear-all bug

Implement Solution A (complete deferred submission) for custom endpoint
management, replacing the dual-mode system with unified local staging.

Changes:
- Remove immediate backend saves from EndpointSpeedTest
  * handleAddEndpoint: local state update only
  * handleRemoveEndpoint: local state update only
  * handleSelect: remove lastUsed timestamp update
- Add explicit clear detection in ProviderForm
  * Distinguish "user cleared endpoints" from "user didn't modify"
  * Pass empty object {} as clear signal vs null for no-change
- Fix mergeProviderMeta to handle three distinct cases:
  * null/undefined: don't modify endpoints (no meta sent)
  * empty object {}: explicitly clear endpoints (send empty meta)
  * with data: add/update endpoints (overwrite)

Fixed Critical Bug:
When users deleted all custom endpoints, changes were not saved because:
- draftCustomEndpoints=[] resulted in customEndpointsToSave=null
- mergeProviderMeta(meta, null) returned undefined
- Backend interpreted missing meta as "don't modify", preserving old values

Solution:
Detect when user had endpoints and cleared them (hadEndpoints && length===0),
then pass empty object to mergeProviderMeta as explicit clear signal.

Architecture Improvements:
- Transaction atomicity: all fields submitted together on form save
- UX consistency: add/edit modes behave identically
- Cancel button: true rollback with no immediate saves
- Code simplification: removed ~40 lines of immediate save error handling

Testing:
- TypeScript type check: passed
- Rust backend tests: 10/10 passed
- Build: successful
This commit is contained in:
Jason
2025-11-04 15:30:54 +08:00
parent 49c2855b10
commit 0778347f84
7 changed files with 115 additions and 99 deletions

View File

@@ -123,6 +123,7 @@ export function EditProviderDialog({
<div className="flex-1 overflow-y-auto px-6 py-4">
<ProviderForm
appId={appId}
providerId={provider.id}
submitLabel={t("common.save")}
onSubmit={handleSubmit}
onCancel={() => onOpenChange(false)}