Skip to content

fix(macapp): setCost failure status erased by its own reload + deferred manual smokes from #991 #1020

Description

@dennisonbertram

Observed behavior

In macapp/Sources/GoCodeUI/ModelSettingsView.swift, ModelSettingsModel.setCost (lines 132–140) was not brought in line with the fix applied elsewhere in this same file for fetch/setExposed/setAllVisible/saveProvider/delete (epic #991, slice U8, PR TBD):

func setCost(_ provider: String, _ model: String, input: Double, output: Double) async {
    do {
        try await client.setModelCost(
            provider: provider, model: model, input: input, output: output)
        await load()
    } catch {
        status = "Could not save cost: \(error.localizedDescription)"
    }
}

The five siblings all call load(clearingStatus: false) on both branches so the message they just set survives the reload (see the load() doc comment at line 40). setCost does not: its success path calls plain load() (clearingStatus: true, the default), and its catch block sets status but never reloads at all. This is the same bug shape the U8 fix addressed for the other five actions — setCost just wasn't included, deliberately, to keep that slice's diff scoped to the findings it was written against.

Expected behavior

setCost should follow the same load(clearingStatus: false) pattern as its siblings on both the success and failure branches, so a status message it sets is not vulnerable to being cleared by a later default-clearing load() and its failure path still refreshes provider/model state.

Reproduction

Preconditions: Settings → Models, a provider with at least one model.
Exact action: Click a model's price, enter a value, Save; observe setCost's catch branch never calls load(), so on failure the provider list is not refreshed to pick up any server-side state change.
Reproduction rate: Deterministic by code inspection (ModelSettingsView.swift:132-140); not exercised against a live daemon in this pass.

Impact

Low severity, UI-consistency only — no data loss or security implication. Confusing feedback if a save fails silently-stale, and this is the last inconsistency in the load(clearingStatus:) pattern introduced for #999/U8.

Suspected seam

Owning file/type: ModelSettingsModel.setCost, macapp/Sources/GoCodeUI/ModelSettingsView.swift:132-140.
Sibling pattern to match: fetch/setExposed/setAllVisible/saveProvider/delete in the same file (all call load(clearingStatus: false)).

Regression test

Add a ModelSettingsModelTests case that sets a fake client to fail setModelCost, asserts model.status still reads the failure message after setCost returns, and — the part not yet covered — asserts providers was refreshed from the fake client's post-failure state (currently it is not, since no reload happens on the catch branch).

Deferred manual smokes from #991 (require a live app/daemon; not run in this pass)

  • Transcript autoscroll: scroll-up behavior during live streaming.
  • Failed-load retry with the daemon killed mid-session.
  • Delete/undo confirmation cancel paths (Sessions).
  • Force-rewind "Restore Anyway" path.
  • Prompt-history Up/Down navigation with a half-typed draft in the composer.
  • VoiceOver and keyboard-only navigation on the Sessions and Models panes.

Latent deferral: caret-aware prompt history (D1 in the plan)

macapp/Package.swift pins .macOS(.v14). Literal caret-position-aware history recall (the #998 wording) needs a SwiftUI TextSelection/caret binding, which is a macOS 15 API, or an NSTextView bridge (NSViewRepresentable). The shipped approximation (recall only when the draft is empty/unchanged) is documented as KTD-7 in the plan; true caret-awareness is out of scope until the platform floor moves or the composer is rebuilt on an NSTextView bridge.

Fix boundaries

In scope (for a follow-up fix): setCost's two branches, matching the sibling pattern; the regression test above.
Out of scope: the deferred manual smokes and the caret-awareness deferral are tracked here for visibility, not for this fix — they need either live-app access or a platform-floor decision, not a code change alone.

Related: #991

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions