fix(providers): advise picking the official account as the Stack default

The Stack dialog now tells subscription users to keep the official
account as the default instead of describing what gets dropped.
The duplicate warning under "Set as default" in the more menu is
removed, along with the now unused menu item detail field.
This commit is contained in:
Jason committed 2026-10-03 23:29:00 +08:00
1 parent 88e40de89a
commit 227aa01407
8 files changed
+9 -64

No files matched your search

@@ -178,16 +178,10 @@ export function ProviderCardActions({
className="flex-col items-start gap-0.5"
>
{item.label}
{item.disabledReason ? (
{item.disabledReason && (
<span className="max-w-64 text-caption text-fg-3">
{item.disabledReason}
</span>
) : (
item.detail && (
<span className="max-w-64 text-caption text-warning-text">
{item.detail}
</span>
)
)}
</DropdownMenuItem>
))}
+1 -10
View File
@@ -42,8 +42,6 @@ export interface CardMenuOption {
export interface CardMenuItem {
key: string;
label: string;
/** 点了之后的后果,写在名字下面 */
detail?: string;
/** 不能点的原因:照常列出来,原因写在名字下面 */
disabledReason?: string;
onSelect: () => void;
@@ -447,18 +445,11 @@ function buildSwitchSectionsByView(input: SwitchModeInput): ProviderSection[] {
const defaultProvider = providers.find((p) => p.id === defaultId);
// 行上的主操作只有名单的添加 / 移除;「设为默认」在「更多」菜单里:进了聚合当场生效,
// 在直连时只记下选择(切换时的确认框里还能改)。正在路由时不能点:默认和路由目标是同一个
// 指针,改了就是当场换路由。官方账号只能做默认、不能做成员(Codex):默认从它换成别家,
// 官方订阅的模型就不在聚合里了,后果写在菜单项上
const defaultIsOfficial =
defaultProvider !== undefined && official(defaultProvider);
// 指针,改了就是当场换路由
const setDefaultItems = (p: Provider): CardMenuItem[] => [
{
key: "setDefault",
label: t("providerCard.action.setDefault"),
detail:
defaultIsOfficial && !official(p)
? t("providerCard.reason.defaultDropsOfficial")
: undefined,
disabledReason:
active === "route"
? t("providerCard.reason.defaultWhileRouting")
+1 -2
View File
@@ -3969,7 +3969,7 @@
"routeTo": "Route to",
"stackDefault": "Default provider",
"officialOption": "{{name}} (official)",
"stackOfficialNote": "An official account can only be the default. With another provider as the default, the official subscription's models won't be part of the aggregation.",
"stackOfficialNote": "If you use an official subscription, set the official account as the default provider.",
"routeNoteFile": "Rewrites the address and key in {{file}}. The real key stays in CC Switch; other settings are untouched.",
"routeNoteSwitch": "Changing providers later on the Routing tab won't rewrite the config file.",
"routeNoteKeepRunning": "Keep CC Switch running. Quitting writes the direct config back; it reconnects on the next start.",
@@ -4034,7 +4034,6 @@
"noRoute": "Official subscriptions don't go through routing; available again in direct mode.",
"noStack": "Official subscriptions can't be used in Aggregation.",
"officialStack": "Official accounts can only be the default; they can't join the aggregation.",
"defaultDropsOfficial": "The official subscription's models won't be part of the aggregation.",
"defaultWhileRouting": "While routing, the default is the provider you route to. You can pick another one when switching to Aggregation.",
"needsRoute": "Needs routing: {{reason}}; can't connect directly.",
"defaultCannotRemove": "The default can't be removed. Make another provider the default first.",
+1 -2
View File
@@ -3969,7 +3969,7 @@
"routeTo": "転送先",
"stackDefault": "既定のプロバイダー",
"officialOption": "{{name}}(公式)",
"stackOfficialNote": "公式アカウントは既定にしかできません。別のプロバイダーを既定にすると、公式サブスクリプションのモデルは集約に入りません。",
"stackOfficialNote": "公式サブスクリプションを使っている場合は、公式アカウントを既定のプロバイダーにしてください。",
"routeNoteFile": "{{file}} のアドレスとキーを書き換えます。本物のキーは CC Switch に残り、ほかの設定は変わりません。",
"routeNoteSwitch": "以後「ルーティング」タブでプロバイダーを変えても設定ファイルは書き換えません。",
"routeNoteKeepRunning": "CC Switch を起動したままにしてください。終了時に直接接続へ書き戻し、次回起動時に再接続します。",
@@ -4034,7 +4034,6 @@
"noRoute": "公式サブスクリプションはルーティングを通りません。直接接続に戻すと使えます。",
"noStack": "公式サブスクリプションは集約では使えません。",
"officialStack": "公式アカウントは既定にだけでき、集約には追加できません。",
"defaultDropsOfficial": "公式サブスクリプションのモデルは集約に入りません。",
"defaultWhileRouting": "ルーティング中は、転送先のプロバイダーがそのまま既定になります。集約に切り替えるときに別のプロバイダーを選べます。",
"needsRoute": "ルーティングが必要:{{reason}}。直接接続はできません。",
"defaultCannotRemove": "既定のプロバイダーは削除できません。先にほかを既定にしてください。",
+1 -2
View File
@@ -3970,7 +3970,7 @@
"routeTo": "路由到",
"stackDefault": "預設供應商",
"officialOption": "{{name}}(官方)",
"stackOfficialNote": "官方帳號只能當預設;選別家當預設後,官方訂閱的模型不會出現在聚合裡。",
"stackOfficialNote": "如果你在用官方訂閱,請把官方帳號設為預設供應商。",
"routeNoteFile": "會改寫 {{file}} 裡的位址和金鑰;真實金鑰留在 CC Switch 裡,其他設定不動。",
"routeNoteSwitch": "之後在「路由」分頁裡換供應商,不再改寫設定檔。",
"routeNoteKeepRunning": "CC Switch 需要保持執行;結束時自動寫回直連,下次啟動再接上。",
@@ -4035,7 +4035,6 @@
"noRoute": "官方訂閱不經過路由,回到直連後可用。",
"noStack": "官方訂閱不能用於聚合模式。",
"officialStack": "官方帳號只能當預設,不能加入聚合。",
"defaultDropsOfficial": "官方訂閱的模型不會出現在聚合裡。",
"defaultWhileRouting": "正在路由時,預設就是路由到的那家;切換到聚合模式時可以另選。",
"needsRoute": "需要路由:{{reason}},不能直連。",
"defaultCannotRemove": "預設那家不能移除,先把別家設為預設。",
+1 -2
View File
@@ -3969,7 +3969,7 @@
"routeTo": "路由到",
"stackDefault": "默认供应商",
"officialOption": "{{name}}(官方)",
"stackOfficialNote": "官方账号只能做默认;选别家做默认后,官方订阅的模型不会出现在聚合里。",
"stackOfficialNote": "如果你在用官方订阅,请把官方账号设为默认供应商。",
"routeNoteFile": "会改写 {{file}} 里的地址和密钥;真实密钥留在 CC Switch 里,其他设置不动。",
"routeNoteSwitch": "之后在「路由」页签里换供应商,不再改写配置文件。",
"routeNoteKeepRunning": "CC Switch 需要保持运行;退出时自动写回直连,下次启动再接上。",
@@ -4034,7 +4034,6 @@
"noRoute": "官方订阅不经过路由,回到直连后可用。",
"noStack": "官方订阅不能用于聚合模式。",
"officialStack": "官方账号只能做默认,不能加入聚合。",
"defaultDropsOfficial": "官方订阅的模型不会出现在聚合里。",
"defaultWhileRouting": "正在路由时,默认就是路由到的那家;切换到聚合模式时可以另选。",
"needsRoute": "需要路由:{{reason}},不能直连。",
"defaultCannotRemove": "默认那家不能移除,先把别家设为默认。",
+1 -10
View File
@@ -22,7 +22,7 @@ async function openMenu(presentation: CardPresentation) {
}
describe("ProviderCardActions — mode items in the more menu", () => {
it("lists them first, with the consequence under the name", async () => {
it("lists them first", async () => {
const onSelect = vi.fn();
const { user, menu } = await openMenu({
buttons: [],
@@ -31,7 +31,6 @@ describe("ProviderCardActions — mode items in the more menu", () => {
{
key: "setDefault",
label: "providerCard.action.setDefault",
detail: "providerCard.reason.defaultDropsOfficial",
onSelect,
},
],
@@ -39,9 +38,6 @@ describe("ProviderCardActions — mode items in the more menu", () => {
const items = within(menu).getAllByRole("menuitem");
expect(items[0]).toHaveTextContent("providerCard.action.setDefault");
expect(items[0]).toHaveTextContent(
"providerCard.reason.defaultDropsOfficial",
);
expect(items[1]).toHaveTextContent("provider.duplicate");
await user.click(items[0]);
@@ -57,7 +53,6 @@ describe("ProviderCardActions — mode items in the more menu", () => {
{
key: "setDefault",
label: "providerCard.action.setDefault",
detail: "providerCard.reason.defaultDropsOfficial",
disabledReason: "providerCard.reason.defaultWhileRouting",
onSelect,
},
@@ -66,11 +61,7 @@ describe("ProviderCardActions — mode items in the more menu", () => {
const item = within(menu).getAllByRole("menuitem")[0];
expect(item).toHaveAttribute("aria-disabled", "true");
// 不能点时只写原因,不再写后果
expect(item).toHaveTextContent("providerCard.reason.defaultWhileRouting");
expect(item).not.toHaveTextContent(
"providerCard.reason.defaultDropsOfficial",
);
await user.click(item);
expect(onSelect).not.toHaveBeenCalled();
});
+2 -29
View File
@@ -345,7 +345,6 @@ describe("buildSwitchSections — stack", () => {
expect(input.actions.stackRemove).toHaveBeenCalledWith(backup);
expect(setDefaultItem(sections, "backup")).toMatchObject({
label: "providerCard.action.setDefault",
detail: undefined,
disabledReason: undefined,
});
setDefaultItem(sections, "backup").onSelect();
@@ -433,34 +432,8 @@ describe("buildSwitchSections — stack", () => {
expect(button(sections, "account", "add").disabledReason).toBe(
"providerCard.reason.officialStack",
);
// 它能做默认;默认本来就不是官方账号时,换默认没有额外后果
expect(setDefaultItem(sections, "account").detail).toBeUndefined();
});
it("says the official subscription leaves the Stack when the default moves off a Codex official account", () => {
const account = provider("account", {
category: "official",
settingsConfig: { auth: {}, config: "" },
});
for (const active of ["direct", "stack"] as AppMode[]) {
const { sections } = build({
app: "codex",
active,
view: "stack",
providers: [account, relay, backup],
directId: "relay",
routeId: "account",
stackMembers: new Map([["relay", 1]]),
});
expect(item(sections, "account").section).toBe("default");
// 已添加的、还没添加的第三方都一样:官方账号不做默认就不在聚合里了
for (const id of ["relay", "backup"]) {
expect(setDefaultItem(sections, id).detail).toBe(
"providerCard.reason.defaultDropsOfficial",
);
}
}
// 它能做默认
expect(setDefaultItem(sections, "account").disabledReason).toBeUndefined();
});
it("never lets ChatGPT accounts be added in Codex Stack mode, even legacy cards without a category", () => {