Skip to content

feat: badge inference profiles by real usage, not a provider-slot heuristic#3570

Merged
matthiasn merged 1 commit into
mainfrom
feat/profile-in-use-badge
Jul 25, 2026
Merged

feat: badge inference profiles by real usage, not a provider-slot heuristic#3570
matthiasn merged 1 commit into
mainfrom
feat/profile-in-use-badge

Conversation

@matthiasn

@matthiasn matthiasn commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Summary

Second piece of the inference-profile audit (after #3568): the ACTIVE badge becomes a statement about real usage instead of a heuristic nobody could influence.

  • profileIdsInUse() — a profile is in use iff a category default or an agent inference setup references it
  • the Profiles tab badge reads In use, fed by profileIdsInUseProvider
  • the provider detail page's "Active profile" section becomes "Profiles using this provider" and lists every match
  • deletes pickActiveProfileForProvider / activeProfileIdsForProviders; modelByProfileSlotId moves to the new util/profile_usage.dart

Why

This is the bug that started the audit: "profiles are shown as active even though there is no clear mechanism to set them as active."

pickActiveProfileForProvider badged the first profile with a model slot owned by a configured provider, preferring isDefault. Three problems:

  • isDefault is not user-settable. Only seed templates set it (profile_seeding_service.dart); the profile form only carries the existing value forward. So connecting a provider seeded a profile that was immediately badged, and no UI could grant or revoke the badge.
  • Order-dependent. With two non-default profiles touching a provider, whichever came first in getConfigsByType order won — that order is a cache, not a sort.
  • It meant nothing at runtime. Nothing reads it during inference. Real routing is CategoryDefinition.defaultProfileId, TaskData.profileId, or AgentInferenceSetup. A profile no request would ever use could be badged while the profile every category used showed nothing.

Definition of "in use"

Included: category defaults, and agent setups (baseProfileId, plus the legacy config.profileId chain for agents predating typed setups). Disabled setups are excluded — they run no inference, so a profile they still name is not routing anything.

Excluded, deliberately: tasks. TaskData.profileId is copied from the owning category when the task is created, so a task reference duplicates a category reference already counted — and scanning every task to render a settings badge trades a real query cost for no new signal. An agent-level override is the case that genuinely diverges, and that is covered. Documented in the function's doc comment.

Provider detail page

That section answered a different and legitimate question — "which profiles depend on this provider?" — under a label claiming something else. Rather than silently repurposing it, it is retitled and now lists every matching profile instead of an arbitrary winner, which is what you want before deleting a provider. Its cards render unbadged: referencing a provider's model is not the same as something routing through the profile.

Verification

  • fvm flutter analyze lib test — no issues found
  • fvm flutter test test/features/ai/ — 4336 passed
  • fvm flutter test test/features/ai/ui/settings/ — 817 passed
  • new profile_usage.dart and profile_usage_provider.dart at 100% line coverage (verified against coverage/lcov.info, not estimated)
  • l10n regenerated; badge and section strings in all 11 locales

Four existing tests encoded the old semantics and were updated rather than deleted — the Profiles-tab badge test now asserts the badge follows a reference, not isDefault.

Still open from the audit

lotti3-8f7.4 — seeded profiles, models, and automation reviving after deletion (seedDefaults on every provider save, backfillNewModels, the upgradeExisting skill backfill, and no tombstones so a synced peer resurrects the row).

Summary by CodeRabbit

  • New Features

    • Profile badges now display “In use” only when a profile is referenced by a category or task agent.
    • Provider detail pages now list all profiles using the provider, rather than a single profile.
    • Updated labels are available across supported languages.
  • Bug Fixes

    • Improved profile usage detection and model matching, including legacy configurations.
    • Prevented temporary badge flicker while profile usage loads.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Inference profile badges now indicate profiles referenced by categories or agents, while provider detail pages list every profile using that provider’s models. Supporting utilities, Riverpod state, UI components, tests, localization keys, documentation, and 0.9.1066 release metadata were updated.

Changes

Inference profile usage

Layer / File(s) Summary
Usage resolution and provider state
lib/features/ai/ui/settings/util/profile_usage.dart, lib/features/ai/state/profile_usage_provider.dart, test/features/ai/...
Profile usage is derived from category defaults and agent inference setups, provider model references are resolved across slot IDs, and the logic is covered by unit and provider tests.
In-use profile badge
lib/features/ai/ui/settings/..., lib/features/ai/ui/settings/widgets/v2/ai_profile_card.dart, lib/l10n/*, test/features/ai/ui/settings/...
Profile cards use profileIdsInUseProvider, rename isActive to isInUse, and display the localized “In use” badge only for referenced profiles.
Provider profile listing
lib/features/ai/ui/settings/provider/..., lib/l10n/*, test/features/ai/ui/settings/provider/...
Provider details compute and render all profiles referencing provider models under “Profiles using this provider”.
Release metadata
pubspec.yaml, CHANGELOG.md, flatpak/com.matthiasn.lotti.metainfo.xml
The package version and 0.9.1066 release notes document the profile badge and provider listing changes.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ProviderDetailPage
  participant ProfileUsage
  participant ProfilesUsingProviderSection
  ProviderDetailPage->>ProfileUsage: resolve profilesUsingProviderModels
  ProfileUsage-->>ProviderDetailPage: return matching profiles
  ProviderDetailPage->>ProfilesUsingProviderSection: pass matching profiles
  ProfilesUsingProviderSection->>ProfilesUsingProviderSection: render profile cards
Loading

Possibly related PRs

  • matthiasn/lotti#3372: Updates overlapping profile-slot resolution and active-profile helper logic.

Suggested reviewers: cyberk1ng

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: profile badges now reflect real usage instead of a provider-slot heuristic.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/profile-in-use-badge

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…ristic

The Profiles tab badged a profile ACTIVE when any of its model slots pointed
at a model owned by a configured provider, preferring the isDefault flag.
isDefault is never user-settable — only seed templates set it, and the profile
form just carries it forward — so connecting a provider instantly badged a
freshly seeded profile with no user action able to grant or revoke it. The
winner was also order-dependent (first match in getConfigsByType order), and
nothing read the result at inference time: real routing is a category's
defaultProfileId, a task's profileId, or an agent's AgentInferenceSetup. A
profile no request would ever use could be badged while the profile every
category used showed nothing.

The badge now reads 'In use' and is driven by profileIdsInUse(): a profile is
badged iff a category default or an agent setup references it. Disabled agent
setups are excluded (they run no inference) and typed setups shadow the legacy
config.profileId chain. Tasks are deliberately not scanned — TaskData.profileId
is copied from the category at creation, so it duplicates a signal already
counted, and scanning every task to render a settings badge is not worth the
query.

The provider detail page asked a different, legitimate question under the same
misleading label. Its section becomes 'Profiles using this provider' and lists
every profile referencing the provider's models instead of picking an
arbitrary winner — what would break if the provider were removed.

Deletes pickActiveProfileForProvider and activeProfileIdsForProviders along
with active_profile.dart; modelByProfileSlotId moves to the new profile_usage
util.
@matthiasn
matthiasn force-pushed the feat/profile-in-use-badge branch from a1dddc1 to 5809e8e Compare July 25, 2026 00:21
@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.41270% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 99.22%. Comparing base (4438260) to head (5809e8e).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
.../settings/provider/ai_provider_models_section.dart 91.66% 1 Missing ⚠️

❌ Your patch status has failed because the patch coverage (98.41%) is below the target coverage (99.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3570      +/-   ##
==========================================
- Coverage   99.22%   99.22%   -0.01%     
==========================================
  Files        1780     1781       +1     
  Lines      130324   130336      +12     
==========================================
+ Hits       129311   129322      +11     
- Misses       1013     1014       +1     
Flag Coverage Δ
glados 14.11% <0.00%> (-0.03%) ⬇️
standard 98.97% <98.41%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@matthiasn
matthiasn merged commit 069edde into main Jul 25, 2026
30 of 32 checks passed
@matthiasn
matthiasn deleted the feat/profile-in-use-badge branch July 25, 2026 00:27

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
test/features/ai/ui/settings/widgets/v2/ai_settings_cards_model_test.dart (1)

365-373: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Rename the stale “Active” test title.

The test now verifies isInUse and the “In use” badge, but its title still says “Active”, which makes failures and test reports misleading.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/features/ai/ui/settings/widgets/v2/ai_settings_cards_model_test.dart`
around lines 365 - 373, Rename the test title in the AiProfileCard widget test
from “Active” to “In use” so it accurately describes the isInUse state and badge
being verified; leave the test behavior unchanged.
test/features/ai/ui/settings/ai_settings_page_test.dart (1)

484-507: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the badge assertion distinguish routing from isDefault.

Because profile-1 is still the default profile, a regression that badges defaults instead of consuming profileIdsInUseProvider could pass this test. Make the in-use profile non-default, make another profile default but absent from the override set, and assert the badge within each card rather than globally.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/features/ai/ui/settings/ai_settings_page_test.dart` around lines 484 -
507, Update the profile fixtures in the test around buildProfile so the in-use
profile-1 is not default, while profile-2 is default but excluded from
profileIdsInUseProvider. Scope the “In use” assertions to each corresponding
AiProfileCard, verifying only profile-1 is badged and profile-2 is not.
test/features/ai/ui/settings/provider/ai_provider_detail_page_test.dart (1)

440-466: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add a second matching profile to prove “every profile”.

This fixture contains only defaultProfile and asserts one card, so it cannot detect a regression where the section renders only one matching profile. Add another profile referencing gemini-pro and assert both cards/names are rendered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/features/ai/ui/settings/provider/ai_provider_detail_page_test.dart`
around lines 440 - 466, Extend the test around buildProfile and AiProfileCard to
include a second non-default profile whose thinking slot references gemini-pro,
include it in pumpWith profiles, and assert both profile names/cards render so
the section verifies every matching profile.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/l10n/app_cs.arb`:
- Around line 874-876: Re-sort the localization ARB files so
aiProviderDetailProfilesUsingTitle appears alphabetically between
aiProviderDetailPageTitle and aiProviderDetailRemoveButton. Apply this in
lib/l10n/app_cs.arb at lines 874-876, lib/l10n/app_da.arb at lines 1035-1036,
and lib/l10n/app_it.arb at lines 1035-1036, then run make sort_arb_files and
verify all locale files, including app_en.arb, remain correctly sorted.

In `@lib/l10n/app_nl.arb`:
- Line 1034: Re-sort the aiProviderDetailProfilesUsingTitle entry alphabetically
in all affected ARB files: lib/l10n/app_nl.arb:1034-1034,
lib/l10n/app_pt.arb:1034-1034, and lib/l10n/app_ro.arb:867-867. Place it after
aiProviderDetailPageTitle and before aiProviderDetailRemoveButton, preferably by
running make sort_arb_files.

---

Outside diff comments:
In `@test/features/ai/ui/settings/ai_settings_page_test.dart`:
- Around line 484-507: Update the profile fixtures in the test around
buildProfile so the in-use profile-1 is not default, while profile-2 is default
but excluded from profileIdsInUseProvider. Scope the “In use” assertions to each
corresponding AiProfileCard, verifying only profile-1 is badged and profile-2 is
not.

In `@test/features/ai/ui/settings/provider/ai_provider_detail_page_test.dart`:
- Around line 440-466: Extend the test around buildProfile and AiProfileCard to
include a second non-default profile whose thinking slot references gemini-pro,
include it in pumpWith profiles, and assert both profile names/cards render so
the section verifies every matching profile.

In `@test/features/ai/ui/settings/widgets/v2/ai_settings_cards_model_test.dart`:
- Around line 365-373: Rename the test title in the AiProfileCard widget test
from “Active” to “In use” so it accurately describes the isInUse state and badge
being verified; leave the test behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e0bf9bb2-8cc1-443f-8512-4b4862543ffc

📥 Commits

Reviewing files that changed from the base of the PR and between 4438260 and 5809e8e.

📒 Files selected for processing (42)
  • CHANGELOG.md
  • flatpak/com.matthiasn.lotti.metainfo.xml
  • lib/features/ai/state/profile_usage_provider.dart
  • lib/features/ai/ui/settings/README.md
  • lib/features/ai/ui/settings/ai_settings_page.dart
  • lib/features/ai/ui/settings/ai_settings_tab_builders.dart
  • lib/features/ai/ui/settings/provider/ai_provider_detail_page.dart
  • lib/features/ai/ui/settings/provider/ai_provider_detail_widgets.dart
  • lib/features/ai/ui/settings/provider/ai_provider_models_section.dart
  • lib/features/ai/ui/settings/util/active_profile.dart
  • lib/features/ai/ui/settings/util/profile_usage.dart
  • lib/features/ai/ui/settings/widgets/v2/ai_profile_card.dart
  • lib/l10n/app_cs.arb
  • lib/l10n/app_da.arb
  • lib/l10n/app_de.arb
  • lib/l10n/app_en.arb
  • lib/l10n/app_es.arb
  • lib/l10n/app_fr.arb
  • lib/l10n/app_it.arb
  • lib/l10n/app_localizations.dart
  • lib/l10n/app_localizations_cs.dart
  • lib/l10n/app_localizations_da.dart
  • lib/l10n/app_localizations_de.dart
  • lib/l10n/app_localizations_en.dart
  • lib/l10n/app_localizations_es.dart
  • lib/l10n/app_localizations_fr.dart
  • lib/l10n/app_localizations_it.dart
  • lib/l10n/app_localizations_nl.dart
  • lib/l10n/app_localizations_pt.dart
  • lib/l10n/app_localizations_ro.dart
  • lib/l10n/app_localizations_sv.dart
  • lib/l10n/app_nl.arb
  • lib/l10n/app_pt.arb
  • lib/l10n/app_ro.arb
  • lib/l10n/app_sv.arb
  • pubspec.yaml
  • test/features/ai/state/profile_usage_provider_test.dart
  • test/features/ai/ui/settings/ai_settings_page_test.dart
  • test/features/ai/ui/settings/provider/ai_provider_detail_page_test.dart
  • test/features/ai/ui/settings/util/active_profile_test.dart
  • test/features/ai/ui/settings/util/profile_usage_test.dart
  • test/features/ai/ui/settings/widgets/v2/ai_settings_cards_model_test.dart
💤 Files with no reviewable changes (2)
  • lib/features/ai/ui/settings/util/active_profile.dart
  • test/features/ai/ui/settings/util/active_profile_test.dart

Comment thread lib/l10n/app_cs.arb
Comment on lines +874 to 876
"aiProviderDetailProfilesUsingTitle": "Profily využívající tohoto poskytovatele",
"aiProviderDetailAddModelButton": "Přidat model",
"aiProviderDetailApiKeyLabel": "API klíč",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Re-sort ARB files after the aiProviderDetailActiveProfileTitleaiProviderDetailProfilesUsingTitle rename. In all three locales, the renamed key now sorts before aiProviderDetailAddModelButton, which is alphabetically wrong ("AddModelButton" < "ProfilesUsingTitle"); it belongs between aiProviderDetailPageTitle and aiProviderDetailRemoveButton. This is likely present in the other locale files (and possibly app_en.arb) that weren't part of this review batch as well.

  • lib/l10n/app_cs.arb#L874-L876: move aiProviderDetailProfilesUsingTitle to its correct alphabetical position (between aiProviderDetailPageTitle and aiProviderDetailRemoveButton).
  • lib/l10n/app_da.arb#L1035-L1036: same fix.
  • lib/l10n/app_it.arb#L1035-L1036: same fix.

Run make sort_arb_files to fix this and verify all 11 locale files (plus app_en.arb) are correctly sorted.

As per coding guidelines: "Keep localization ARB files sorted by running make sort_arb_files after adding labels."

📍 Affects 3 files
  • lib/l10n/app_cs.arb#L874-L876 (this comment)
  • lib/l10n/app_da.arb#L1035-L1036
  • lib/l10n/app_it.arb#L1035-L1036
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/l10n/app_cs.arb` around lines 874 - 876, Re-sort the localization ARB
files so aiProviderDetailProfilesUsingTitle appears alphabetically between
aiProviderDetailPageTitle and aiProviderDetailRemoveButton. Apply this in
lib/l10n/app_cs.arb at lines 874-876, lib/l10n/app_da.arb at lines 1035-1036,
and lib/l10n/app_it.arb at lines 1035-1036, then run make sort_arb_files and
verify all locale files, including app_en.arb, remain correctly sorted.

Source: Coding guidelines

Comment thread lib/l10n/app_nl.arb
"aiProviderConnectStepConnect": "Verbinden",
"aiProviderConnectStepReview": "Evaluatie",
"aiProviderDetailActiveProfileTitle": "Actief profiel",
"aiProviderDetailProfilesUsingTitle": "Profielen die deze provider gebruiken",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Renamed ARB key aiProviderDetailProfilesUsingTitle is out of alphabetical order in all three locale files. The key kept its old position (from the removed aiProviderDetailActiveProfileTitle), placed right before aiProviderDetailAddModelButton, instead of sorting after aiProviderDetailPageTitle and before aiProviderDetailRemoveButton as its new name requires. This is harmless for gen-l10n (JSON map) but violates the project's ARB-sort convention.

  • lib/l10n/app_nl.arb#L1034-L1034: move the aiProviderDetailProfilesUsingTitle entry to sort after aiProviderDetailPageTitle and before aiProviderDetailRemoveButton (or simply re-run make sort_arb_files).
  • lib/l10n/app_pt.arb#L1034-L1034: same reordering fix.
  • lib/l10n/app_ro.arb#L867-L867: same reordering fix.

Based on learnings, this relies on the path instruction: "**/*.arb: Keep localization ARB files sorted by running make sort_arb_files after adding labels."

📍 Affects 3 files
  • lib/l10n/app_nl.arb#L1034-L1034 (this comment)
  • lib/l10n/app_pt.arb#L1034-L1034
  • lib/l10n/app_ro.arb#L867-L867
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/l10n/app_nl.arb` at line 1034, Re-sort the
aiProviderDetailProfilesUsingTitle entry alphabetically in all affected ARB
files: lib/l10n/app_nl.arb:1034-1034, lib/l10n/app_pt.arb:1034-1034, and
lib/l10n/app_ro.arb:867-867. Place it after aiProviderDetailPageTitle and before
aiProviderDetailRemoveButton, preferably by running make sort_arb_files.

Source: Coding guidelines

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