Skip to content

refactor(desktop): unify API config IPC + drop separate fast model slot#113

Merged
hqhq1025 merged 3 commits intomainfrom
wt/unify-api-config
Apr 19, 2026
Merged

refactor(desktop): unify API config IPC + drop separate fast model slot#113
hqhq1025 merged 3 commits intomainfrom
wt/unify-api-config

Conversation

@hqhq1025
Copy link
Copy Markdown
Collaborator

Summary

Two cleanups shipped together because they reshape the same surface area (API config / model selection):

1. Canonical IPC for API config mutations

The 3 surfaces that touch API config (top-bar pill, Settings → API 服务, onboarding) used to call 3 different IPC handlers with 3 different return shapes. The Settings 'add-provider' path returned 'ProviderRow[]' and never updated the Zustand store, so the top-bar pill could lag behind reality until a manual reload.

  • New `config:v1:set-provider-and-models` handler accepts `{ provider, apiKey, modelPrimary, baseUrl?, setAsActive }`, atomically writes config, returns full `OnboardingState`.
  • Existing `onboarding:save-key`, `settings:v1:add-provider`, `settings:v1:set-active-provider` are now thin delegates of the new handler — full back-compat.
  • Settings → AddProviderModal calls the new handler directly + re-pulls `OnboardingState` so the top-bar pill reflects the new active provider immediately. Fixes the staleness bug.
  • Preload bridge: new `window.codesign.config.setProviderAndModels()`.

2. Drop `modelFast` model slot

The `modelFast` field was used in exactly one place (`applyComment` fallback) and was indistinguishable from `modelPrimary` for users — the UI showed two near-identical dropdowns and confused everyone.

  • `Config.modelFast` dropped from v2 schema. v1 configs accepted (Zod treats `modelFast` as optional; never written back).
  • `OnboardingState.modelFast`, `ProviderShortlist.fast`, `ProviderShortlist.defaultFast` removed.
  • `applyComment` falls back to `modelPrimary` instead.
  • AddProviderModal / ChooseModel / ModelsTab UI all drop the second dropdown.
  • Schema bumped: `Config.version: 1 → 2`. Migration is read-only.

PRINCIPLES §5b

  • Compatibility ✅ — v1 configs read fine; legacy IPC channels still work
  • Upgradeability ✅ — schemaVersion bumped, no data loss
  • No bloat ✅ — net diff is -negative LOC, removes a confusing knob
  • Elegance ✅ — one canonical mutation path, one model field

Test plan

  • `pnpm --filter @open-codesign/desktop test` — 337 pass
  • `pnpm --filter @open-codesign/desktop typecheck` — clean
  • biome — clean
  • DCO signed-off
  • Manual: existing config.toml with `modelFast` field still loads
  • Manual: add provider in Settings → top-bar pill updates without reload
  • Manual: applyComment uses modelPrimary

🤖 Generated with Claude Code

…l slot

Two cleanups shipped together because both reshape the API config surface:

1. Canonical IPC `config:v1:set-provider-and-models` atomically writes
   provider + key + model + activate flag and returns full OnboardingState.
   Existing onboarding:save-key, settings:v1:add-provider, and
   settings:v1:set-active-provider become thin delegates so back-compat is
   preserved. Settings → AddProviderModal now syncs Zustand after add — fixes
   the top-bar pill staleness bug.

2. Drop modelFast field. It was used in exactly one place (applyComment
   fallback) and indistinguishable from modelPrimary for users, which is why
   the UI confused everyone with two dropdowns. Schema bumps to v2; v1
   configs are accepted and the dead field is dropped on next write.
   applyComment now falls back to modelPrimary.

UI: AddProviderModal / ChooseModel / ModelsTab drop the "fast" dropdown.

337 desktop tests pass. Typecheck + biome clean.

Signed-off-by: hqhq1025 <1506751656@qq.com>
Copy link
Copy Markdown

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Findings

  • [Major] Adding a provider now always flips the active provider unexpectedly — this changes runtime model/provider selection and can route subsequent generations to the wrong provider. Evidence: apps/desktop/src/renderer/src/components/Settings.tsx:334 (setAsActive: true is unconditional).
    Suggested fix:

    const onboarding = await window.codesign.onboarding.getState();
    await window.codesign.config.setProviderAndModels({
      provider: form.provider,
      apiKey: form.apiKey.trim(),
      modelPrimary: form.modelPrimary,
      ...(trimmedUrl.length > 0 ? { baseUrl: trimmedUrl } : {}),
      setAsActive: !onboarding.hasKey,
    });
  • [Major] Silent fallback in Zustand sync path violates the "no silent fallbacks" constraint — failures are swallowed with no UI signal or contextual throw. Evidence: apps/desktop/src/renderer/src/components/Settings.tsx:764.
    Suggested fix:

    } catch (err) {
      pushToast({
        variant: 'error',
        title: t('settings.common.unknownError'),
        description: err instanceof Error ? err.message : String(err),
      });
    }

Summary

  • Review mode: initial
  • 2 issues found in modified lines: (1) active-provider behavior regression from unconditional activation on add; (2) silent error swallowing in config sync path, conflicting with project error-handling constraints.
  • Not found in repo/docs: docs/VISION.md, docs/PRINCIPLES.md in this checkout.

Testing

  • Not run (automation)

open-codesign Bot

modelPrimary: form.modelPrimary,
modelFast: form.modelFast,
...(trimmedUrl.length > 0 ? { baseUrl: trimmedUrl } : {}),
setAsActive: true,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unconditional setAsActive: true makes every newly-added provider become active, which changes generation routing unexpectedly. Consider deriving this from onboarding state (e.g., only true when no configured key exists).

try {
const state = await window.codesign.onboarding.getState();
setConfig(state);
} catch {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This empty catch silently swallows sync failures. The repo constraint requires surfacing every error in UI or throwing with context; please show a toast or rethrow contextualized error.

…ings

Codex flagged the unconditional setAsActive: true — that flips the active
provider every time a user adds a backup, silently rerouting subsequent
generations. Mirror the legacy add-provider semantics: only flip when no
provider is configured yet (first-time setup). Existing active stays put.

Signed-off-by: hqhq1025 <1506751656@qq.com>
Copy link
Copy Markdown

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Findings

  • [Major] Silent fallback swallows sync failure after provider save — this violates the hard constraint that errors must surface in UI or throw with context; if onboarding:get-state fails, the code still emits a success toast and hides the failure, leaving state potentially stale. Evidence apps/desktop/src/renderer/src/components/Settings.tsx:765
    Suggested fix:
    try {
      const state = await window.codesign.onboarding.getState();
      setConfig(state);
    } catch (err) {
      pushToast({
        variant: 'error',
        title: t('settings.providers.toast.modelSaveFailed'),
        description: err instanceof Error ? err.message : t('settings.common.unknownError'),
      });
      return;
    }

Summary

  • Review mode: follow-up after new commits
  • 1 issue found in changed lines.

Testing

  • Not run (automation)

open-codesign Bot

try {
const state = await window.codesign.onboarding.getState();
setConfig(state);
} catch {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[Major] This catch {} silently ignores failure when reloading onboarding state after save. That conflicts with the project rule that errors must surface in UI or throw with context. Consider surfacing a toast and aborting the success path so users don't get a false success state.

Codex flagged the silent catch in handleAddSave's getState() sync — if the
post-mutation store sync fails, a success toast still fires and the user
gets stale UI without any error indicator. Surface the failure as an error
toast and abort the success path.

Signed-off-by: hqhq1025 <1506751656@qq.com>
Copy link
Copy Markdown

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Findings

  • No actionable issues found in added/modified lines for this revision.

Summary

  • Review mode: follow-up after new commits
  • No new issues found in this diff.
  • Not found in repo/docs: docs/VISION.md, docs/PRINCIPLES.md in this checkout. Residual risk: behavior coverage for the new canonical config:v1:set-provider-and-models path is still partial (mainly payload validation in current tests).

Testing

  • Not run (automation)

open-codesign Bot

@hqhq1025 hqhq1025 merged commit 9bbafa8 into main Apr 19, 2026
6 checks passed
@hqhq1025 hqhq1025 deleted the wt/unify-api-config branch April 19, 2026 17:12
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.

1 participant