Skip to content

feat(desktop): let a provider connection be renamed after it is created - #3677

Merged
likun666661 merged 3 commits into
apache:mainfrom
Joob1n:feat/rename-provider-connection
Aug 24, 2026
Merged

feat(desktop): let a provider connection be renamed after it is created#3677
likun666661 merged 3 commits into
apache:mainfrom
Joob1n:feat/rename-provider-connection

Conversation

@Joob1n

@Joob1n Joob1n commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

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. 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 ConnectionCatalogEntryUpdate entirely — the catalog has no way to change it, so a rename moves only what is displayed.

The row is a SettingsExpandableRow beside 模型密钥 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.ts so it is testable without React, following relay-profile-draft.ts:

  • A slug switch always reseeds. Typing a name for connection A, switching to B, and saving would otherwise rename B to A's draft.
  • A same-slug change reseeds only while the draft is clean, so a rename landing from this page — or from another client watching the same catalog — does not discard a name being typed.
  • An emptied field is not offered as a change. The catalog requires a name; clearing the field is a half-finished edit, not a request to have none.
  • Whitespace alone is not a change, and a rename commits the trimmed value. Otherwise an accidental trailing space reads as an edit whose save stores a name that renders identically to the one already stored.

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 || showsEndpoint guard 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 now shouldRefreshModelsAfterSave, 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:

$ node --test apps/desktop/dist/main/__tests__/connection-name-draft.test.js
ℹ tests 13
ℹ pass 13
ℹ fail 0
Mutation Result
stop reseeding on a slug switch 8 pass, 1 fail
drop the trim and the empty guard 7 pass, 2 fail
let a rename fetch models 12 pass, 1 fail

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 format:check              Checked 1614 files. No fixes applied.
npm --workspace @maka/desktop run typecheck    clean
npm run astryx:surface-inventory  ok (206 files, 1 exclusions)
npm run check:stale               dist is fresh.
npm run check:third-party-notices OK

npm run check:asf-source fails on this machine, and does so identically on a clean mainsync-model-metadata.mjs passes mode: '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:

  • No generative tool made a substantive contribution
  • Generative tooling made a substantive contribution

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 slug could not be affected, wrote the row, the draft rules and their tests, and ran the mutation checks. The commit carries a Generated-by trailer.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above
  • No

Joob1n added 2 commits August 24, 2026 14:15
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

@likun666661 likun666661 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

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
@likun666661
likun666661 merged commit 3f5c4bb into apache:main Aug 24, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants