feat(desktop): let a provider connection be renamed after it is created - #3677
Merged
Merged
Conversation
A connection's name was fixed at creation. The name is the one field the user chose, and a connection acquires its real name in use — after the account it points at is clear, or once a second connection to the same provider makes "OpenAI" ambiguous. Deleting and recreating to rename loses the enabled model set and the stored credential with it. Nothing below the renderer needed changing. `UpdateConnectionInput` already carries `name`, `ConnectionCatalogEntryUpdate` requires it, and `connection.catalog.update` was already sending the current value back unchanged on every save. This adds the row that lets the user supply a different one. Renaming cannot break a reference: sessions bind to `llmConnectionSlug`, and `slug` is absent from the update input entirely, so the catalog has no way to change it. Draft handling is extracted to `connection-name-draft.ts` so it can be tested without React, following `relay-profile-draft.ts`: - a slug switch always reseeds, because carrying connection A's typed name onto B would let one save rename the wrong connection; - a same-slug change reseeds only while the draft still matches what was saved, so a rename landing from elsewhere does not discard typing; - an emptied field is not offered as a change — the catalog requires a name, and clearing it is a half-finished edit; - whitespace alone is not a change, and a rename commits the trimmed value. Nine tests. Reverting the slug-switch reseed fails 1; dropping the trim and the empty guard fails 2. Generated-by: Claude Opus 5 via Claude Code
Self-review of the previous commit found two defects. The name row sat inside the `supportsApiKey || showsEndpoint` guard while its own comment claimed it was outside one — so a connection with neither, an OAuth subscription for instance, lost the only editable field it has and rendered an empty section. The row is now outside that guard and behind `!retired`, which is the condition that actually applies: a retired connection accepts no writes. A completed save then decided whether to refresh the live model catalog with `wroteNewKey || field === 'endpoint' || models.length === 0`. A rename satisfies the third clause whenever the model cache is empty, so renaming a connection issued a network fetch nobody asked for, able to raise an error about a connection the user had only renamed. The rule is now `shouldRefreshModelsAfterSave`, which excludes a rename and is covered by tests; a new credential, a new endpoint, and an empty cache behind either of those still fetch. Four tests added, 13 total. Reverting the rename exclusion fails 1. Generated-by: Claude Opus 5 via Claude Code
CI's Storybook AX audit failed on `relay-connection-detail`: the 连接 section now holds two buttons whose accessible name is 编辑 — the new name row and the existing service-URL row — reported as `ambiguous_actionable`. The section had one before this branch added the second, so the rename is what broke it. Both rows now carry `actionAriaLabel`, the same disambiguation the 高级请求 rows in this file already use for their two 编辑 buttons. The visible label stays 编辑; only the accessible name gains the field it acts on. `npm run smoke:storybook` passes locally, 162 stories. Generated-by: Claude Opus 5 via Claude Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A provider connection's name was fixed at creation, and the name is the one field the user chose. A connection acquires its real name in use — once the account it points at is clear, or once a second connection to the same provider makes "OpenAI" ambiguous. The only way to rename was to delete and recreate, which loses the enabled model set and the stored credential with it.
Nothing below the renderer needed changing.
UpdateConnectionInputalready carriesname,ConnectionCatalogEntryUpdaterequires it, andconnection.catalog.updatewas already sending the current value back unchanged on every save. This adds the row that lets the user supply a different one.Renaming cannot break a reference. Sessions bind to
llmConnectionSlug, andslugis absent fromConnectionCatalogEntryUpdateentirely — the catalog has no way to change it, so a rename moves only what is displayed.The row is a
SettingsExpandableRowbeside 模型密钥 and 服务地址, so it reads and behaves like the other settled values on the page rather than introducing a second editing idiom. It sits above them because it is the field the user picked, and it is not behind the API-key or endpoint guards — every connection has a name, including the ones with neither.Draft handling
Extracted to
connection-name-draft.tsso it is testable without React, followingrelay-profile-draft.ts:Opening any row abandons what another row was holding, since only one is editable at a time and a draft left behind would be committed by an action the user never connected to it.
Self-review
Reviewing the first commit as a reviewer rather than re-running its tests found two defects, fixed in
1e0f70d.The row hid itself on the connections that needed it most. It sat inside the
supportsApiKey || showsEndpointguard while its own comment claimed it was outside one. A connection with neither — an OAuth subscription — lost the only editable field it has and rendered an empty section. The row is now outside that guard and behind!retired, which is the condition that actually applies.A rename issued a model fetch. The post-save rule was
wroteNewKey || field === 'endpoint' || models.length === 0, and a rename satisfies the third clause whenever the model cache is empty. Renaming a connection therefore started a network fetch nobody asked for, able to raise an error about a connection the user had only renamed. The rule is nowshouldRefreshModelsAfterSave, which excludes a rename; a new credential, a new endpoint, and an empty cache behind either of those still fetch.Verification
Thirteen tests, and each rule is pinned by reverting it:
Checked in the running app against a real relay connection: the row renders at the top of 连接, an edit saves and the name updates in the header and the connection list, and the enabled models and stored credential are untouched.
npm run check:asf-sourcefails on this machine, and does so identically on a cleanmain—sync-model-metadata.mjspassesmode: 'transform'to a Node 26 type-stripping API that now accepts only'strip'. Unrelated to this change, and reported here rather than left unmentioned.AI use
Select exactly one:
Tool(s) and scope: Claude Opus 5 via Claude Code — traced the existing update path to confirm the backend already accepted a new name and that
slugcould not be affected, wrote the row, the draft rules and their tests, and ran the mutation checks. The commit carries aGenerated-bytrailer.Checklist
Does this PR entail a change in behavior?