Skip to content

fix(ai): preserve custom model IDs across refresh#649

Open
floze-the-genius wants to merge 15 commits into
altic-dev:mainfrom
floze-the-genius:fix/601-custom-model-entry
Open

fix(ai): preserve custom model IDs across refresh#649
floze-the-genius wants to merge 15 commits into
altic-dev:mainfrom
floze-the-genius:fix/601-custom-model-entry

Conversation

@floze-the-genius

@floze-the-genius floze-the-genius commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Description

Makes manual AI model entry reachable from the current provider UI and keeps explicitly added IDs across discovery refreshes.

Custom IDs are stored separately per provider, normalized and deduplicated, merged into the normal model picker, selected immediately after entry, restored on launch, and removed with their provider. Refresh still drops retired discovery-only models and falls back to the first current model.

Type of Change

  • 🐞 Bug fix
  • ✨ New feature
  • 💥 Breaking change
  • 🧹 Chore
  • 📝 Documentation update

Related Issue or Discussion

Closes #601.

Testing

  • Tested on Intel Mac
  • Tested on Apple Silicon Mac
  • Tested on macOS version: 26.5
  • Ran linter locally: swiftlint --strict --config .swiftlint.yml Sources
  • Ran formatter locally: swiftformat --config .swiftformat Sources
  • Ran tests locally: deterministic compiled AIModelCatalog harness, swiftc -parse for all changed Swift files, and plutil -lint for the Xcode project

Upstream validation on the exact PR head:

  • SwiftLint: passed
  • macOS Xcode build and test: passed
  • ad-hoc PR archive: passed (e604fb152c331d2d9c6e555d7887649a77a2fff3)
  • deterministic coverage for persistence, refresh merge semantics, stale discovery fallback, whitespace, blanks, and duplicate IDs
  • git diff --check

Screenshots / Video

The OpenRouter provider card now exposes the Add custom model control and inline model-name field:

FluidVoice custom model entry

Screenshot captured from the upstream-generated PR archive for head SHA e604fb152c331d2d9c6e555d7887649a77a2fff3.

  • No UI/visual changes; screenshots/video are not applicable.

Notes

Only IDs explicitly added as custom survive refresh. A selected discovery-only model that disappears from the provider catalog keeps the existing fallback-to-first behavior instead of being silently promoted to custom storage.

@github-actions github-actions Bot added the needs screenshots Pull request needs screenshot or video evidence. label Jul 18, 2026
@github-actions

Copy link
Copy Markdown

The PR Policy check is blocking this PR because required template information is missing.

Please update the PR description with:

  • Screenshots / Video

Visual files detected:

  • Sources/Fluid/Persistence/SettingsStore.swift
  • Sources/Fluid/UI/AISettings/AIEnhancementSettingsViewModel.swift
  • Sources/Fluid/UI/AISettings/AIModelCatalog.swift
  • Sources/Fluid/UI/AISettingsView+AIConfiguration.swift

Screenshots or video are required for UI, UX, settings, onboarding, overlay, menu bar, or visual behavior changes. If this PR has no visual changes, check the no-visual-change box in the template.

If this remains incomplete for 48 hours after opening, the PR may be closed.

@floze-the-genius
floze-the-genius marked this pull request as ready for review July 18, 2026 08:43
@greptile-apps

greptile-apps Bot commented Jul 18, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a dedicated custom model storage layer (customModelsByProvider in UserDefaults) so that manually entered model IDs survive discovery refreshes, are merged into the visible model picker, and are removed atomically when their provider is deleted. A one-time migration path infers pre-existing manual models from the legacy append-detection heuristic and reconciles them against the first fresh catalog fetch.

  • New AIModelCatalog enum provides pure static helpers for normalization, dedup, merge, and legacy migration — cleanly separating these concerns from the view model.
  • manualModelAddition correctly distinguishes already-visible models (no write to custom storage) from genuinely new ones (persisted to customModelsByProvider), addressing the silent-promotion problem.
  • Backup/restore encodes customModelsByProvider as an optional field; pre-PR backups decode as nil and trigger prepareLegacyModelCatalogRestore, while current backups restore the explicit set. Sixteen new deterministic tests cover persistence, refresh semantics, legacy migration, blank ID filtering, and deletion guards.

Confidence Score: 5/5

Safe to merge — the core persistence, refresh-merge, and backup/restore paths are all well-tested and the logic is correct.

The change introduces a new UserDefaults key with a clean optional-backed migration path, correctly separates custom from discovery models throughout the refresh cycle, and ships 16 deterministic tests covering every material edge case (blank IDs, duplicate entry, legacy heuristic, reconciliation, deletion guards). No data can be silently lost on the happy path or on restore of a current backup. The two items flagged are style-level: non-standard Xcode GUIDs and a redundant forwarding function.

No files require special attention.

Reviews (12): Last reviewed commit: "fix(ai): allow deleting custom provider ..." | Re-trigger Greptile

Comment thread Sources/Fluid/UI/AISettings/AIEnhancementSettingsViewModel.swift
Comment thread Tests/FluidDictationIntegrationTests/AIModelRefreshTests.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e604fb152c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/Fluid/UI/AISettings/AIEnhancementSettingsViewModel.swift
Comment thread Sources/Fluid/UI/AISettingsView+AIConfiguration.swift
Comment thread Sources/Fluid/Persistence/SettingsStore.swift
@github-actions github-actions Bot removed the needs screenshots Pull request needs screenshot or video evidence. label Jul 18, 2026
@floze-the-genius

Copy link
Copy Markdown
Contributor Author

Addressed both Greptile findings in 7c812b2: discovery-only IDs are selected without being persisted as custom, genuinely new IDs still persist, and the test now uses the source-of-truth defaults key. The focused compiled regression harness, SwiftLint, SwiftFormat checks, swiftc -parse, plutil, and git diff --check pass.

Expose manual model entry in the active provider UI and persist custom IDs separately from discovery results. Merge explicit custom IDs during refresh while retaining the existing fallback for retired discovered models.
Keep existing discovery-only IDs selectable without persisting them as custom models, while continuing to retain genuinely new manual IDs. Reuse the SettingsStore defaults key in integration-test cleanup and cover both entry paths.
@floze-the-genius
floze-the-genius force-pushed the fix/601-custom-model-entry branch from 7c812b2 to cc0e49a Compare July 18, 2026 20:03

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cc0e49aba5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Fluid.xcodeproj/project.pbxproj Outdated
Signed-off-by: Floze <88098863+floze-the-genius@users.noreply.github.com>
Signed-off-by: Floze <88098863+floze-the-genius@users.noreply.github.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 867f6c7400

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/Fluid/UI/AISettings/AIEnhancementSettingsViewModel.swift Outdated
Comment thread Sources/Fluid/Persistence/SettingsStore.swift Outdated
Signed-off-by: Floze <88098863+floze-the-genius@users.noreply.github.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 60fad93552

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/Fluid/UI/AISettings/AIEnhancementSettingsViewModel.swift Outdated
Signed-off-by: Floze <88098863+floze-the-genius@users.noreply.github.com>
@floze-the-genius

Copy link
Copy Markdown
Contributor Author

@codex review

Signed-off-by: Floze <88098863+floze-the-genius@users.noreply.github.com>
@floze-the-genius

Copy link
Copy Markdown
Contributor Author

Addressed the remaining legacy-migration review in ce138ac, with the CI identifier-length correction in b9ec6e8. The follow-up persists unresolved legacy model candidates per provider and reconciles them against the first successful fresh catalog: discovery IDs drop out, while legacy-only custom IDs survive even when they sort last. Pending candidates survive restarts and are cleared when their provider/model is removed or settings are restored. Added sorted-last and fully-sorted catalog regressions. Local swiftc -parse, plutil, git diff --check, and a deterministic 3-case reconciliation harness pass; upstream macOS CI is running on the new head.

@floze-the-genius

Copy link
Copy Markdown
Contributor Author

@codex review

Comment thread Sources/Fluid/Persistence/SettingsStore.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b9ec6e8d7c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/Fluid/UI/AISettings/AIEnhancementSettingsViewModel.swift Outdated
Comment thread Sources/Fluid/UI/AISettings/AIEnhancementSettingsViewModel.swift Outdated
Signed-off-by: Floze <88098863+floze-the-genius@users.noreply.github.com>

@floze-the-genius floze-the-genius left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Publishing the previously drafted responses to resolved review threads.

Comment thread Sources/Fluid/UI/AISettings/AIEnhancementSettingsViewModel.swift
Comment thread Sources/Fluid/UI/AISettingsView+AIConfiguration.swift
Comment thread Fluid.xcodeproj/project.pbxproj Outdated
Comment thread Sources/Fluid/UI/AISettings/AIEnhancementSettingsViewModel.swift Outdated
Comment thread Sources/Fluid/Persistence/SettingsStore.swift Outdated

@floze-the-genius floze-the-genius left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Publishing the remaining previously drafted response.

Comment thread Sources/Fluid/Persistence/SettingsStore.swift
Signed-off-by: Floze <88098863+floze-the-genius@users.noreply.github.com>
@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown

Want your agent to iterate on Greptile's feedback? Try greploops.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b6b1cd114c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/Fluid/Persistence/SettingsStore.swift
Comment thread Sources/Fluid/UI/AISettings/AIEnhancementSettingsViewModel.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c1f44ce464

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/Fluid/Persistence/SettingsStore.swift Outdated
Comment thread Sources/Fluid/Persistence/SettingsStore.swift
Comment thread Sources/Fluid/UI/AISettings/AIEnhancementSettingsViewModel.swift Outdated
Signed-off-by: Floze <88098863+floze-the-genius@users.noreply.github.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d13a5f21d4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/Fluid/Persistence/SettingsStore.swift Outdated
Comment thread Sources/Fluid/Persistence/SettingsStore.swift Outdated
Signed-off-by: Floze <88098863+floze-the-genius@users.noreply.github.com>
Signed-off-by: Floze <88098863+floze-the-genius@users.noreply.github.com>

floze-the-genius commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Review follow-up on head e8e3312:

  • d13a5f2 clears stale restore state, rehydrates new-format custom catalogs before UI reload, and reconciles legacy candidates after the first fresh catalog.
  • c056f51 rebuilds legacy saved-provider catalogs from SavedProvider.models and derives custom models during backup export when the dedicated key is still absent.
  • e8e3312 corrects the regression fixture to model the legacy appended-tail ordering used by the migration.

Validation on the exact head: PR Policy passed, SwiftLint passed, Build FluidVoice passed all 161 tests, PR Archive passed, and Greptile Review passed. All addressed review threads are resolved.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e8e3312f35

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Sources/Fluid/UI/AISettingsView+AIConfiguration.swift
Comment thread Tests/FluidDictationIntegrationTests/AIModelRefreshTests.swift
Signed-off-by: Floze <88098863+floze-the-genius@users.noreply.github.com>
@floze-the-genius

Copy link
Copy Markdown
Contributor Author

Addressed both findings in 4917898:

  • Added a provider-specific delete control to both active provider UIs (expanded cards and verified rows). The control is shown only when the selected model exists in that provider's custom-model store, removes it from the persisted custom/legacy catalogs, updates provider model/selection state, and cannot delete API-discovered models. Added regression coverage for both the removable-custom and protected-discovered cases.
  • Updated testRestoringLegacyBackupRehydratesSavedProviderCatalog to preserve and restore the exact prior CustomModelsByProvider and LegacyModelCandidatesByProvider defaults state, including key absence.

Validation on the pushed head: git diff --check, Swift parse, Docker SwiftLint 0.63.2 with 0 violations, and all GitHub checks passed (PR Policy, SwiftLint, Build FluidVoice, Archive, and Greptile Review).

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.

[BUG] Manual AI model entry is unreachable in the current provider UI, and refresh overwrites custom model IDs

1 participant