Skip to content

Provider and Profile pages are not manageable: no Profile delete at all, no in-use guard on Provider delete #68

Description

@yujiezhang-ops

Taking CC-Switch's config surface as the reference point, here is what these two pages can and cannot do today.

Profile: no delete exists anywhere

ProfilesPage offers create (startSetup, line 67) and edit (setEditor, line 260). There is no delete, and this is not a missing button — the capability is absent from the bottom of the stack up:

  • internal/profile/store.go exposes Root, PointerPath, ProfilesPath, ProfilePath, SecretPath, List, LoadActive, LoadActiveContext
  • internal/profile/write.go exposes Save, WriteActive, writeStored, writeSecret
  • there is no Delete method in the package
  • so no UseCases method, no ProfileService binding (services.go:385-395 has only ListProfiles and SaveProfile), no api.deleteProfile

A Profile created by mistake is permanent. Since Profile ID is disabled once set (ProfilesPage.tsx:178), it cannot be renamed either — a typo in an ID is unfixable through the app.

Implementing this is a four-layer change, not a frontend task. Per the write-order rules in CLAUDE.md it also has to remove the secret alongside the profile, and LoadActive needs a defined answer when the deleted Profile is the active one.

Provider: delete has no in-use guard

UseCases.DeleteProvider (internal/app/provider.go:202-212) takes writeMu and calls providers.Delete with no check of who is using it:

u.writeMu.Lock()
defer u.writeMu.Unlock()
return u.providers.Delete(ctx, providerID)

ProvidersPage already computes exactly the fact needed — users, the Agents whose agent.provider matches — and renders it as chips (lines 181-183, 214). It is displayed and then ignored by the confirm, which is a bare window.confirm with just the name (line 89). Deleting a Provider with three Agents bound to it succeeds silently and leaves those Agents pointing at an id that no longer resolves.

The symmetric case is handled correctly: SaveProvider reapplies to every affected Agent and reports reapplied / failures, and the page surfaces both (lines 63-77). Delete should be at least as informative.

Smaller gaps on the same theme

  • Deletion is gated on meta.custom (line 200), so a built-in Provider can be edited but not removed. With only two built-ins (ppio, novita) a user who wants neither is stuck with both in the list. Hiding rather than deleting would fit — the lock file is the source of truth for what ships.
  • The Provider list has no ordering control. byProviderCreatedAt fixes the order; there is no way to promote the one actually in use to the top.
  • AgentProfilePage and ProfilesPage create Profiles by different routes. AgentProfilePage.openCreate (line 180) opens an inline form and calls api.saveProfile; ProfilesPage.startSetup dispatches START_SETUP into the five-step wizard. Two paths, two mental models, one object type. Related to [P2] 「更新」按钮用前端硬编码的 npm 清单判断,已漏掉 OpenClaw,且未安装也照样显示 #61.
  • No duplicate/clone. The common real operation is "same Provider and model, different name," which today means retyping every field.

Suggested split

Worth separating, since they are different sizes and different risk:

  1. Profile delete (Go through UI, four layers, needs the active-pointer decision) — the actual blocker
  2. Provider delete in-use guard (name the bound Agents in the confirm, or refuse) — small, and prevents a broken state
  3. Clone, ordering, built-in hiding — convenience, decide together after 1 and 2

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions