Skip to content

feat(ai): edit AI providers on settings sub-pages instead of stacked modals - #1824

Merged
chhoumann merged 5 commits into
masterfrom
feat/ai-settings-subpage
Sep 27, 2026
Merged

chhoumann merged 5 commits into
masterfrom
feat/ai-settings-subpage

Conversation

@chhoumann

@chhoumann chhoumann commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

Summary

AI provider editing moves out of three stacked modals (sparkles button → AI Assistant settings → Edit providers → provider editor) into Obsidian 1.13 settings sub-pages:

Settings → QuickAdd → AI & online → AI Assistant → provider

  • AI Assistant is a declarative type: "page" entry. It shows "N providers" on its row and is hidden while Disable AI & online features is on. It holds a Providers list (native + add button, or an "Add provider" row on mobile; Delete/Backspace deletes with confirmation; each entry shows a live "N retired" value with a warning status) and a Defaults group (default model, prompt template folder, show assistant, confirm tool calls, default system prompt). Because the page is declarative, Obsidian's settings search indexes it; "default model", "provider", and "API key" all land here.
  • Each provider opens an imperative SettingPage with a connection group (name, endpoint, type, API key, Test connection), Model sync (source, auto-sync with Sync now and the last-synced status line, Browse), a Models list (filter, + add, retired row with Remove retired, Retired badges, newest first), and Delete provider. The delete action also works on touch, since Obsidian draws no trash button on page entries.
  • Pages save as you edit, the Obsidian settings convention; the old Save/Cancel pair is gone. All writes go through settingsStore, and providers are addressed by stable id, so a background sync never overwrites edits. syncStoredProvider syncs a detached copy and merges the result into the provider as it is when the request returns.
  • The sparkles button and the empty-state Configure AI Assistant button open the page directly. This uses Obsidian's internal app.setting.navigateToSearchResult, the path its own settings search takes; if that API ever disappears, a Notice tells the user where to go.
  • New command QuickAdd: Open AI Assistant settings (quickadd:openAIAssistantSettings) opens the page from the palette or the Obsidian CLI. While AI is disabled, it explains why instead of opening the page.
  • Add model now rejects context sizes beyond Number.MAX_SAFE_INTEGER (a 400-digit input used to become Infinity, which is stored as null).
  • Deleted: AIAssistantSettingsModal, AIAssistantProvidersModal, and their snapshot/restore and race-tracking code.

Screenshots (Obsidian 1.13.7, throwaway vault)

Before: three stacked modals

before

After: entry under AI & online

entry

AI Assistant page

ai page

Provider page after a live Test connection (key linked from the keychain)

provider

Models: filter, add, and retired cleanup

models

Retired badge and Delete provider

badge

Mobile (Obsidian's emulateMobile, 400 px)

mobile

Verification in real Obsidian 1.13.7 (isolated e2e vault, live OpenAI API)

Flow Observed
Empty-state Configure AI Assistant lands on AI Assistant; 1 settings modal, no stacked modals
Sparkles button (with a choice) lands on AI Assistant
Link key (keychain picker → Save) data.json: apiKeyRef set, apiKey empty
Test connection ✓ Connected. The provider lists 64 models. (green); without a key: red ✗ … HTTP 401 … (No API key is linked.)
Sync now (models.dev) Synced from the models.dev directory: already up to date. · status Last synced just now · 29 models.
Switch source to provider API, Sync now status resets to Not synced yet., then Synced from the provider's models endpoint: 28 new, 0 updated. · 57 models
Pick default model data.json: defaultModel: "gpt-5.2", defaultModelRef: {providerId: "openai", name: "gpt-5.2"}
Retired cleanup entry shows 1 retired ⚠ → Remove retired → confirm → badge and row gone; back on the list the warning is gone; stored retired = 0
Edit (rename while typing) focus stays in the field; page title, window title, and list label update
Delete provider (page action) confirm → returns to AI Assistant; provider removed from data.json
Duplicate names two "Custom" providers list as Custom (custom) / Custom (custom-2); after deleting one, the other relabels to Custom
Toggle Disable AI & online features entry and sparkles hide and reappear live
quickadd:openAIAssistantSettings command opens one settings modal on the QuickAdd tab with AI Assistant showing
Console dev:errors → No errors captured. with the debugger attached

Also reviewed in dark theme and at mobile width; keyboard focus rings are visible.

Tests

New regression tests: AIProviderSettingPage.test.ts, aiAssistantSettingsPage.test.ts, aiSettingsState.test.ts, modelSyncService.syncStoredProvider.test.ts, and quickAddSettingsTab.aiPage.test.ts. They port every behavior the deleted modal tests pinned: sort order, filter, delete by name after model objects are replaced, retired names captured at click time, Test connection using the provider API, endpoint edits clearing stale status, Sync now counting only the list on screen, and Add model validation. They add coverage for pre-migration data without ai.providers (the tab is constructed before migrations run), duplicate-id repair, page-signature scoping (a sync never rebuilds the page), live retired counts, default-model refresh after a sync, delete-from-page, titles after a rename, and navigation with its fallback. ChoiceView.aiEntry.test.ts checks that both buttons call the navigation.

pnpm run build-with-lint ✅ (one pre-existing warning in modelsDirectory.fetch.test.ts) · pnpm run check (svelte-check) ✅ 0 errors · pnpm run test: 460 files, 5925 tests passed.

Release / migration notes

  • No data migration. Settings keep the same shape.
  • Behavior change: provider edits save immediately, and there is no Cancel. This matches Obsidian's own settings pages.
  • Relies on the internal app.setting.navigateToSearchResult for the two entry buttons only, with a fallback Notice.
  • Follow-up: docs/.../Images/AI_Assistant_Setup.gif (re-recorded in docs: retake outdated screenshots and demo GIFs #1822) still shows the old modal flow and should be re-recorded against these pages. The text around it is updated.
  • Docs updated (AIAssistant.md, QuickAddAPI.md), along with the model-not-found hint in aiHelpers.ts.

Note

Replace stacked AI provider modals with editable settings sub-pages

  • Adds an AI Assistant page to QuickAdd settings with a provider list and a Defaults group (default model, prompt-template folder, assistant visibility, tool confirmation, system prompt). Each provider opens its own sub-page in AIProviderSettingPage.ts for connection, model sync, model list, and deletion.
  • Adds an open-AI-settings command in main.ts that navigates to the AI Assistant page, with a Notice fallback when internal navigation is unavailable. ChoiceView delegates AI settings navigation to a callback prop instead of opening a modal.
  • Adds id-based provider state helpers in aiSettingsState.ts and syncStoredProvider in modelSyncService.ts, which merges discovered models into the current provider state without overwriting concurrent edits.
  • Settings tab construction repairs missing or duplicate provider IDs, and storedProviders tolerates pre-migration settings without a provider array.
  • Behavioral Change: resolveModelInputOrThrow error guidance now points to Settings → QuickAdd → AI Assistant; removed provider-list CSS selectors and the AIAssistantProvidersModal sync test file; ChoiceView requires the new openAISettings prop.

Macroscope summarized 8df21d7.

Summary by CodeRabbit

  • New Features
    • AI Assistant settings are now available in the QuickAdd settings tab, with a command and in-app shortcuts to open them.
    • Configure each AI provider on its own settings page, including connection details, models, and automatic syncing. Model lists can be filtered, sorted, and updated manually.
    • Provider pages show model-sync results and retired-model warnings. You can add, remove, and manage models, including retired models.
  • Documentation
    • Updated setup, provider, model, and troubleshooting guidance to match the current settings navigation and terminology.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 27, 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-27T00:06:53.021005Z e14e0b1 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.

@cloudflare-workers-and-pages

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

Copy link
Copy Markdown

Deploying quickadd with  Cloudflare Pages  Cloudflare Pages

Latest commit: c9212e3
Status: ✅  Deploy successful!
Preview URL: https://6a83a48e.quickadd.pages.dev
Branch Preview URL: https://feat-ai-settings-subpage.quickadd.pages.dev

View logs

@coderabbitai

coderabbitai Bot commented Sep 27, 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: fac7c852-a2d0-4471-ad1d-b097377a0d75

📥 Commits

Reviewing files that changed from the base of the PR and between c1fdc58 and 8df21d7.

📒 Files selected for processing (42)
  • docs/src/content/docs/docs/AIAssistant.md
  • docs/src/content/docs/docs/QuickAddAPI.md
  • src/ai/AIAssistant.systemPromptLiteral.test.ts
  • src/ai/aiHelpers.resolveModel.test.ts
  • src/ai/aiHelpers.ts
  • src/ai/modelSyncService.syncStoredProvider.test.ts
  • src/ai/modelSyncService.ts
  • src/ai/providerConnection.ts
  • src/commandLabels.ts
  • src/gui/AIAssistantProvidersModal.audit-ai-assistant.test.ts
  • src/gui/AIAssistantProvidersModal.discard.test.ts
  • src/gui/AIAssistantProvidersModal.sync.test.ts
  • src/gui/AIAssistantProvidersModal.ts
  • src/gui/AIAssistantProvidersModal.ux.test.ts
  • src/gui/AIAssistantSettingsModal.ts
  • src/gui/ProviderPickerModal.ts
  • src/gui/ai/AIProviderSettingPage.test.ts
  • src/gui/ai/AIProviderSettingPage.ts
  • src/gui/ai/aiAssistantSettingsPage.test.ts
  • src/gui/ai/aiAssistantSettingsPage.ts
  • src/gui/ai/aiSettingsState.test.ts
  • src/gui/ai/aiSettingsState.ts
  • src/gui/ai/providerSettings.ts
  • src/gui/ai/syncStatus.test.ts
  • src/gui/ai/syncStatus.ts
  • src/gui/ai/systemPromptFields.test.ts
  • src/gui/choiceList/ChoiceView.addRace.test.ts
  • src/gui/choiceList/ChoiceView.aiEntry.test.ts
  • src/gui/choiceList/ChoiceView.malformed.test.ts
  • src/gui/choiceList/ChoiceView.svelte
  • src/gui/choiceList/ChoiceView.test.ts
  • src/gui/components/settingsDefinitions.ts
  • src/main.commandLabels.test.ts
  • src/main.ts
  • src/quickAddSettingsTab.aiPage.test.ts
  • src/quickAddSettingsTab.audit-cleanup.test.ts
  • src/quickAddSettingsTab.test.ts
  • src/quickAddSettingsTab.ts
  • src/styles.css
  • src/utils/openPluginSettings.ts
  • tests/helpers/settings/choiceView.ts
  • tests/obsidian-stub.ts
 _______________________________________________________________________________
< Why do we never have time to do it right, but always have time to do it over? >
 -------------------------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ 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.

ampagent and others added 2 commits September 27, 2026 00:04
…modals

Co-authored-by: Christian Bager Bach Houmann <christian@bagerbach.com>
Co-authored-by: Christian Bager Bach Houmann <christian@bagerbach.com>
Comment thread src/gui/ai/AIProviderSettingPage.ts
Comment thread src/gui/ai/AIProviderSettingPage.ts
Co-authored-by: Christian Bager Bach Houmann <christian@bagerbach.com>
@chhoumann
chhoumann force-pushed the feat/ai-settings-subpage branch from e14e0b1 to c9212e3 Compare September 27, 2026 00:05

@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: e14e0b1637

ℹ️ 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 on lines +46 to +47
export function aiPageSignature(providers: readonly AIProvider[]): string {
return JSON.stringify(providers.map((p) => [p.id, p.name, p.endpoint]));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Defer settings rebuilds while editing provider fields

When a user types in either the provider Name or Endpoint field, every keystroke changes this signature; the store subscription in QuickAddSettingsTab immediately calls this.update(), which rebuilds and re-renders the active settings page. This can replace the input currently receiving the event, causing focus/cursor loss after each character and potentially invalidating the provider page path during a rename. Update the parent entry after leaving the provider page, or exclude live-edited fields from the rebuild signature.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Checked in Obsidian 1.13.7; this doesn't happen. SettingTab.update() → refreshCurrentPage re-displays only the root tab or a declarative page. The provider page is an imperative SettingPage, so it is never re-rendered. Live check while typing a rename into Name: document.activeElement === nameInput stays true after each input, and the inline title, the window title, and (after Back) the list label all update. The page stack holds the page object rather than its name path, so Back works after a rename too. Regression tests for rename and title sync are in AIProviderSettingPage.test.ts.

Comment on lines +56 to +63
export function createAIAssistantPage(app: App): SettingDefinitionPage<SettingsKey> {
return {
type: "page",
name: AI_ASSISTANT_PAGE_NAME,
desc: "Providers, models, and defaults for AI commands.",
displayValue: () => describeProviderCount(storedProviders().length),
visible: () => !settingsStore.getState().disableOnlineFeatures,
items: [createProvidersList(app), createDefaultsGroup(app)],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add a CLI verification seam for the settings workflow

The new provider add/edit/delete workflow is exposed only through declarative/custom settings pages, while the added tests invoke those pages through the Obsidian stub; a repo-wide search found no command or API entrypoint with inspectable state and deterministic logging that can exercise this flow through obsidian:e2e. Add that seam so the replacement workflow can be verified in the required real-Obsidian CLI path.

AGENTS.md reference: AGENTS.md:L103-L107

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Added in 8df21d7: a command quickadd:openAIAssistantSettings ("Open AI Assistant settings") that opens Settings → QuickAdd → AI Assistant, so the flow is reachable from the Obsidian CLI (obsidian command id=quickadd:openAIAssistantSettings). Verified live: it opens as one modal on the QuickAdd tab with the AI Assistant page showing. All provider state is inspectable in data.json/app.plugins.plugins.quickadd.settings.ai, and every flow in the PR table was exercised in real Obsidian through obsidian:e2e eval. Unit tests cover the command's navigation order and its behavior while AI is disabled.

ampagent and others added 2 commits September 27, 2026 00:11
Co-authored-by: Christian Bager Bach Houmann <christian@bagerbach.com>
…izes

Co-authored-by: Christian Bager Bach Houmann <christian@bagerbach.com>
@chhoumann
chhoumann merged commit fa54d55 into master Sep 27, 2026
13 of 14 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.

2 participants