Skip to content

feat(ai): AI setup UX: empty-state entry, lean newest-first model lists, Test connection, retired cleanup, sync status - #1818

Merged
chhoumann merged 6 commits into
masterfrom
feat/ai-setup-ux
Sep 26, 2026
Merged

chhoumann merged 6 commits into
masterfrom
feat/ai-setup-ux

Conversation

@chhoumann

@chhoumann chhoumann commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

Summary

These are the AI setup UX items from the review of #1811. A fresh user can now reach AI settings, the synced model lists are shorter and ordered usefully, keys can be checked in place, and sync state is visible.

  1. The empty choice list has an AI entry point. The AI Assistant button lived only in the choice-list bottom bar, which the "No choices yet" state doesn't render. A new vault therefore had no way into AI settings. The empty state now shows Configure AI Assistant while AI and online features are on.
  2. Model lists are leaner and ordered newest first. Models now carry releaseDate and a deprecated flag from models.dev.
    • Syncs never add deprecated models.
    • Dated snapshots (gpt-4o-2024-11-20) are dropped when the undated id is listed.
    • Realtime models are filtered on the models.dev path as well. gpt-realtime-2.1 returns 404 "not a chat model" on Chat Completions live.
    • The provider list, the settings model dropdown, and the runtime "Ask me" picker all show newest first, then undated (hand-added) models, then retired models. The edit view has a filter box.
    • Delete now removes by model identity. With a sorted view, a stored index would delete the wrong row.
  3. Test connection. It asks the provider's own models endpoint with the linked key, even for a models.dev-sourced provider (the directory needs no key, so it can't vouch for one). It shows the model count or the provider's error. The same check is available from the CLI as quickadd:ai-test-connection provider=<id|name>, which returns {ok, modelCount | error, apiKeyLinked} and never the key.
  4. Retired models get a "Retired by the provider" badge and sort last. Remove retired models (N) clears them after a confirmation, and Cancel still restores them. Nothing is removed automatically, because saved commands may use them.
  5. Sync status. Each sync (on load, on open, or Sync now) records lastModelSync on the provider. A line under Auto-sync shows "Last synced 5 minutes ago · 29 model(s)." or "Last sync failed …: ". Background failures used to be visible only in the log. Changing the endpoint, linked key, type, or model source clears the status and the connection result, because they described the old configuration; Cancel restores both.

Evidence (Obsidian 1.13.7, isolated e2e vault with made-up content, fresh install, online features on)

Empty state, before on master and after on this branch:

master: {"ai":false,"empty":true}
branch: {"emptyState":true,"button":"Configure AI Assistant","visible":true}

before: empty state, no AI entry
after: Configure AI Assistant in the empty state

Provider edit view after the on-open sync, reached through that button:

"first rows":   ["1 retired model(s)", "gpt-6-sol", "gpt-6-luna", "gpt-6-astra", "gpt-5.6-sol", ...]
"last rows":    ["gpt-4o-mini", "gpt-4o", "o4-mini"]
"o4-mini desc": "Context: 200,000 tokens · Output: 100,000 tokens · Fixed sampling (no temperature) · Released 2025-04-16 · Retired by the provider"
"datedSnapshotsStored": []
"status":       "Last synced just now · 30 model(s)."
filter "gpt-6" -> ["gpt-6-sol", "gpt-6-luna", "gpt-6-astra"]
model counts after a fresh sync: master OpenAI 42 / Gemini 30 -> branch OpenAI 29 / Gemini 23
default-model dropdown: OpenAI starts gpt-6-sol, gpt-6-luna, … and ends gpt-4o, "o4-mini (retired)"

Test connection with the real key linked from SecretStorage. Only the result text is shown:

no key linked: "✗ Provider rejected the models request: OpenAI request failed (HTTP 401) [invalid_request_error]: Missing bearer authentication in header (No API key is linked.)"
key linked:    "✓ Connected. The provider lists 64 model(s)."

Same check through the CLI (key-occurrence count taken with grep -cF on the output):

$ pnpm run obsidian:e2e -- quickadd:ai-test-connection provider=openai
{"command":"quickadd:ai-test-connection","provider":"openai","ok":true,"modelCount":64,"apiKeyLinked":true}   # key occurrences in output: 0
$ pnpm run obsidian:e2e -- quickadd:ai-test-connection provider=nope
{"command":"quickadd:ai-test-connection","ok":false,"error":"No AI provider matches \"nope\".","providers":["openai","gemini"]}

Editing the endpoint in Obsidian: {"before":{"status":"Last synced just now · 29 model(s).","conn":"✓ Connected. The provider lists 64 model(s)."},"after":{"status":"Not synced yet.","conn":""}}

A key linked through the UI persists to data.json after closing the dialogs with Escape, via both the empty-state button and the bar button, whether the dialogs open immediately after reload or 10 s later. I checked this because the new code runs around the settings writes.

after: Test connection, model filter, retired cleanup (key chip blurred)

dev:errors: no errors captured.

Tests

New tests, each aimed at a plausible wrong implementation:

  • ChoiceView.aiEntry.test.ts: the empty state shows the button with online features on and hides it with them off. It fails on master.
  • AIAssistantProvidersModal.ux.test.ts (7 tests): newest-first display without reordering storage; delete removes the clicked row even when the stored index differs (fails with index-based delete); filter; retired cleanup plus Cancel; Test connection uses providerApi even for a models.dev provider (fails without the override); connection error text; status line for a failed background sync and a successful Sync now.
  • modelsDirectory.test.ts: snapshot dropping keeps a lone snapshot and version numbers; deprecated is set only when true; a sync merge never adds deprecated models but flags existing ones, while an explicit import can still add one; realtime filtering.
  • modelSyncService.test.ts: a failed background sync is recorded without touching models; deprecated models are never added by auto-sync; success records lastModelSync; results are keyed by provider id; an empty error message is still recorded as a failure.
  • aiConnectionCli.test.ts: selection by id or name (case-insensitive), the providerApi override, the error path, unknown and missing selectors, and a check that the result never contains the key.
  • Review follow-ups in AIAssistantProvidersModal.ux.test.ts: delete and retired cleanup act on what the user confirmed, even when a sync lands mid-prompt; a connection-affecting edit clears the stale status and Cancel restores it.
  • Provider.test.ts (sortModelsForDisplay) and syncStatus.test.ts (time-unit boundaries, clock skew).
$ pnpm run build-with-lint   # ok, 0 warnings
$ pnpm run test              # Test Files 458 passed | 6 skipped; Tests 5923 passed | 28 skipped (rebased on master incl. #1816, #1817, #1819, #1820)
$ pnpm run check             # svelte-check found 0 errors and 0 warnings

Decisions to veto

  • Nothing is removed automatically. Retired models and realtime models already in a user's list stay until the user removes them. Only deprecated models get the badge; realtime ones need a manual delete.
  • Aliases like gpt-5.6 are not collapsed. No metadata links an alias to its target, and guessing by name would be wrong in general.
  • The deprecated flag is only ever set, never cleared. If models.dev un-deprecates a model, the badge stays until the user removes the model. This is rare, and it keeps stored data free of deprecated: false on every model.
  • Test connection sends no chat request. It checks the key against the models endpoint, which is free and needs no model choice.
  • The browse/import modal also hides dated snapshots, because it shares discovery. Snapshots can still be added by hand.

Release / migration impact

This is a feat: release with no migration. New optional fields are persisted: Model.releaseDate, Model.deprecated, and AIProvider.lastModelSync. Existing lists gain the metadata on their next sync, so the first "Sync now" after upgrading reports existing models as "updated" once. The test stub gains Setting.addSearch and Element.setText. Adds a CLI command, quickadd:ai-test-connection. Docs are updated in AIAssistant.md.

Follow-ups split out from the same list: the Responses API for OpenAI tool turns merged as #1816; the models.dev disk cache with ETag merged as #1817. Moving provider editing to a settings sub-page waits for this PR to merge.

Note

Add AI setup UX: empty-state entry, newest-first model lists, Test connection, sync status

  • Adds a "Configure AI Assistant" button to the empty choice view and a Test connection command (GUI + CLI) that queries the provider's models endpoint and shows the model count or error, without exposing the API key.
  • Model lists now show current dated models newest-first, label deprecated models as "retired", and list retired models last via sortModelsForDisplay in Provider.ts and dropDatedSnapshots in modelsDirectory.ts.
  • Sync now records per-provider success/failure state in lastModelSync (syncProviderModels in modelSyncService.ts), never newly adds deprecated models, preserves existing models on failure, and displays relative-time sync status in the providers modal.
  • Changes to endpoint, API key, provider type, or model source invalidate stale sync and connection status in the modal.
  • Behavioral Change: background sync merges results by provider id instead of name+endpoint, so distinct providers with identical names and endpoints no longer collide; the shared NON_CHAT_MODEL_ID_RE filter now also excludes realtime and audio endpoint model ids.

Macroscope summarized eb05aca.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-26T21:55:19.974060Z 20de4af PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 36a34c0c-9951-4db4-9d48-92997ec478a1

📥 Commits

Reviewing files that changed from the base of the PR and between 1ea8c0c and eb05aca.

📒 Files selected for processing (25)
  • docs/src/content/docs/docs/AIAssistant.md
  • src/ai/Provider.test.ts
  • src/ai/Provider.ts
  • src/ai/modelDiscoveryService.audit-ai-assistant.test.ts
  • src/ai/modelDiscoveryService.test.ts
  • src/ai/modelDiscoveryService.ts
  • src/ai/modelSyncService.test.ts
  • src/ai/modelSyncService.ts
  • src/ai/modelsDirectory.test.ts
  • src/ai/modelsDirectory.ts
  • src/ai/providerConnection.ts
  • src/cli/aiConnectionCli.test.ts
  • src/cli/aiConnectionCli.ts
  • src/cli/registerQuickAddCliHandlers.test.ts
  • src/cli/registerQuickAddCliHandlers.ts
  • src/engine/macroAI.ts
  • src/gui/AIAssistantProvidersModal.ts
  • src/gui/AIAssistantProvidersModal.ux.test.ts
  • src/gui/ai/syncStatus.test.ts
  • src/gui/ai/syncStatus.ts
  • src/gui/choiceList/ChoiceView.aiEntry.test.ts
  • src/gui/choiceList/ChoiceView.svelte
  • src/gui/modelSelect.ts
  • tests/obsidian-stub.ts
  • tests/vitest-setup.ts
 _________________________________________________________________________________________________________
< If a system is to serve the creative spirit, it must be entirely comprehensible to a single individual. >
 ---------------------------------------------------------------------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Deploying quickadd with  Cloudflare Pages  Cloudflare Pages

Latest commit: eb05aca
Status: ✅  Deploy successful!
Preview URL: https://9533eb5f.quickadd.pages.dev
Branch Preview URL: https://feat-ai-setup-ux.quickadd.pages.dev

View logs

Comment thread src/gui/AIAssistantProvidersModal.ts
Comment thread src/ai/modelSyncService.ts
Comment thread src/ai/modelSyncService.ts Outdated
Comment thread src/gui/AIAssistantProvidersModal.ts 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: 20de4af608

ℹ️ 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 src/gui/AIAssistantProvidersModal.ts
Comment thread src/gui/AIAssistantProvidersModal.ts

Copy link
Copy Markdown
Owner Author

Macroscope's four findings (retired cleanup scope, provider identity, empty error message, delete after a concurrent sync) are covered by the Cursor Agent commit 7a0e272. I had fixed the same four independently, and my regression tests for them also pass against that commit, so I kept it and dropped my duplicate.

Comment thread src/gui/AIAssistantProvidersModal.ts
Comment thread src/gui/AIAssistantProvidersModal.ts
Comment thread src/gui/AIAssistantProvidersModal.ts Outdated
Comment thread src/gui/AIAssistantProvidersModal.ts
Comment thread src/gui/AIAssistantProvidersModal.ts Outdated
Comment thread src/gui/AIAssistantProvidersModal.ts Outdated
Comment thread src/gui/AIAssistantProvidersModal.ts
Comment thread src/gui/AIAssistantProvidersModal.ts

Copy link
Copy Markdown
Owner Author

Reverted the branch to 4231bb7 (force-push). That drops the concurrency fixes added after it (Cursor Agent commits 75f2f49, 298b302, 50a9265, 47148ca, b47fd85 and f7c926f) and my follow-up to them. None of those races had been reproduced in Obsidian: each one needs an edit or a click during a sub-second request, and the fixes kept adding fingerprints and generation counters to the providers modal, which drew new findings each round. My reply that cites a2643f05 is void; that commit never reached the branch. The validated fixes stay: the CLI seam (quickadd:ai-test-connection) and clearing a stale sync status when the connection settings change. The open race findings are left as known, unvalidated edge cases.

ampagent and others added 6 commits September 26, 2026 22:43
The AI Assistant button lived only in the choice list's bottom bar, which the "No choices yet" state does not render, so a new vault had no way into AI settings. The empty state now shows a Configure AI Assistant button while AI and online features are on.

Amp-Thread-ID: https://ampcode.com/threads/T-01a0df48-9e0d-75cc-af45-26ec84217b88
Co-authored-by: Christian Bager Bach Houmann <christian@bagerbach.com>
Syncs imported every directory entry, so the OpenAI list grew to 42 models
in no useful order, including retired, pinned-snapshot, and realtime
models:
- Models now carry releaseDate and a deprecated flag from models.dev.
- Syncs never add a model the directory marks deprecated, but they do
  flag one the user already has.
- Dated snapshots (gpt-4o-2024-11-20) are dropped when their undated id is
  listed.
- Realtime models are filtered on the models.dev path too; gpt-realtime-2.1
  returns 404 "not a chat model" live.
- Each sync records lastModelSync (time, or the error) on the provider.

sortModelsForDisplay orders the model dropdown and the "Ask me" picker
newest first, with undated (hand-added) models next and retired ones last,
labelled as retired. Stored order is unchanged.

Amp-Thread-ID: https://ampcode.com/threads/T-01a0df48-9e0d-75cc-af45-26ec84217b88
Co-authored-by: Christian Bager Bach Houmann <christian@bagerbach.com>
…ction test, and sync status

In the provider edit view:
- The model list is newest first and has a filter box.
- Deletion works by model identity, not display index.
- Retired models are badged, and "Remove retired models (N)" clears them.
  Cancel still restores them.
- Test connection asks the provider's own models endpoint with the linked
  key, even for a models.dev-sourced provider, and shows the model count
  or the provider's error.
- A status line under Auto-sync shows when the provider last synced or why
  the last sync failed, including quiet background failures.

The test stub gains Setting.addSearch and Element.setText.

Amp-Thread-ID: https://ampcode.com/threads/T-01a0df48-9e0d-75cc-af45-26ec84217b88
Co-authored-by: Christian Bager Bach Houmann <christian@bagerbach.com>
… sync status

Amp-Thread-ID: https://ampcode.com/threads/T-01a0df48-9e0d-75cc-af45-26ec84217b88
Co-authored-by: Christian Bager Bach Houmann <christian@bagerbach.com>
Key auto-sync merge by provider id so duplicate name+endpoint accounts do
not share lastModelSync. Store a nonempty failure message when Error.message
is empty. Delete and Remove-retired act on the confirmed model names, not
live object identity or the live deprecated set.

Co-authored-by: Christian Bager Bach Houmann <christian@bagerbach.com>
…status

Review follow-ups:
- Test connection now lives in src/ai/providerConnection.ts. The settings
  button and a new `quickadd:ai-test-connection provider=<id|name>` CLI
  command both call it, which gives the check a CLI-verifiable seam (per
  AGENTS.md). The result reports whether a key is linked, never the key.
- Changing a provider's endpoint, linked key, type, or model source clears
  its last-sync status and connection result, because they described the
  old configuration. Cancel restores both.

Amp-Thread-ID: https://ampcode.com/threads/T-01a0df48-9e0d-75cc-af45-26ec84217b88
Co-authored-by: Christian Bager Bach Houmann <christian@bagerbach.com>
@chhoumann
chhoumann merged commit 594b952 into master Sep 26, 2026
12 of 13 checks passed
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.

3 participants