Skip to content

Commit a4509b3

Browse files
feat(settings): collapsible sections (closes #430 foundation) (#764)
* feat(settings): collapsible sections (closes #430 foundation) Adds a `createCollapsibleSection` helper backed by native `<details>`/`<summary>` elements and converts all existing `.setHeading()` calls across the eight `settings-*.ts` files. A new "General" wrapper covers the previously un-headed top of `settings-general.ts` (provider, API key, models, etc.). Expand/collapse state is persisted in `expandedSettingsSections: string[]` on the plugin settings, so the user's chosen layout survives plugin reloads. All sections are collapsed by default per the issue plan; advanced sections only render after the existing "Show Advanced Settings" toggle. Provider change in the General section now re-renders only that section's content, fixing a latent bug where it would tear down later-rendered sections (UI Settings, Context Management, Advanced) by emptying the outer container. Modal extractions for Tool Permissions, MCP Servers, and Vault Search Index are intentionally deferred to follow-up PRs per the issue plan. * feat(settings): section descriptions, always-open General, advanced badges Iterates on the collapsibles foundation in response to PR review: - Each section header now includes a short description visible whether the section is open or closed, so users can scan what each section does without expanding it. - "General" is no longer collapsible — it's always open at the top of the tab and contains only the setup essentials (provider, API key, models). Debug Mode and the Show Advanced Settings toggle moved into General; the separate Advanced Settings heading + button block is gone. - Advanced sections (Custom Prompts, API Configuration, Tool Execution, Tool Permissions, Tool Loop Detection, MCP Servers) now render with an ADVANCED pill next to the title when revealed via Show Advanced Settings. - Vault Search Index is promoted out of advanced — it always renders, and the (Experimental) label is dropped from the title, README, and FAQ. - Plugin State Folder relocates from General to Session History; Your Name and Summary Frontmatter Key relocate to the User Experience section (renamed from "UI Settings" to better reflect its broader scope). - New `createAlwaysOpenSection` helper for the General header so the always-open variant reuses the same heading + description styling without a `<details>` wrapper or chevron. Tests updated for the new render order: Vault Search renders unconditionally; advanced-only sections (API/Tools/MCP) remain hidden until the toggle is on. * feat(settings): combine Tasks+Hooks into Automation, add Debug section, restructure General Iterates again on the layout in response to PR review: - Drop the "Model Versions" info row from General — it was a static documentation link that didn't belong as a setting. - Plugin State Folder moves back into General (was briefly relocated to Session History). - Session History collapses into the User Experience section as a single Enable Session History toggle; the standalone Session History section is removed. Plugin State Folder is no longer duplicated there. - Reorder collapsible sections so User Experience is first under General, matching where users will look most often. - New "Automation" collapsible (`src/ui/settings-automation.ts`) merges the Scheduled Tasks and Lifecycle Hooks sections into one; both are features that run AI agent work automatically. - New "Debug" advanced collapsible (`src/ui/settings-debug.ts`) collects Debug Mode (moved out of General), Show Token Usage (moved out of Context Management), and Stop on Tool Error (moved out of Tool Execution). The Tool Execution section is removed entirely — its sole setting now lives in Debug. Final order: General → User Experience → Automation → Context Management → Vault Search Index → (advanced) Custom Prompts → API Configuration → Tool Permissions → Tool Loop Detection → MCP Servers → Debug. Tests, docs, and the display-race regression test updated for the new section list. * fix(settings): reorder UX + enforce session-history dependency, move catch-up to Automation - In User Experience, "Enable Session History" now appears before "Log tool execution to session history". The log toggle is automatically disabled when session history is off, since there's nowhere to log to. Description on the log toggle calls out the dependency. - "Auto-run missed scheduled tasks on startup" moves from User Experience to Automation — it's a scheduled-task behavior toggle, not a UX choice. * feat(settings): merge Custom Prompts/API/Context/Tool Loop into Agent Config Iterates the layout again — Custom Prompts, API Configuration, Context Management, and Tool Loop Detection were four narrow advanced sections that all tune how the agent talks to the model. Combine them into a single "Agent Config" advanced collapsible with labeled sub-groups inside, so the top-level advanced list shrinks from 6 to 4 (Agent Config, Tool Permissions, MCP Servers, Debug). Files: - New `src/ui/settings-agent-config.ts` containing the merged renderer plus the temperature/topP slider helpers (moved from settings-api.ts). - Delete `src/ui/settings-api.ts` and `src/ui/settings-context.ts`. - Trim `src/ui/settings-tools.ts` to just Tool Permissions (Tool Loop Detection moved into Agent Config). - Rename `test/ui/settings-api.test.ts` → `settings-agent-config.test.ts` and update imports/describe to match. - Update `test/ui/settings-display-race.test.ts` for the new mock list. - Settings docs updated for the new section ordering and id list. Layout outside advanced is unchanged: General → User Experience → Automation → Vault Search Index. Context Management is no longer a top-level section — it's now a sub-group inside Agent Config, since most users don't need to touch the compaction threshold. * chore(settings): reorder advanced sections — Tool Permissions, MCP, Agent Config, Debug Tool Permissions and MCP Servers are the most likely advanced sections a user opens (permissions for routine review, MCP for adding new servers), so float them to the top. Agent Config (the densest section) sits under them, with Debug last. * fix(test): add setShowDeveloperSettings to settings-agent-config test mock The SettingsSectionContext gained a setShowDeveloperSettings setter when the Show Advanced toggle moved into the General section. The agent-config test fixture wasn't updated, breaking typecheck:test. * perf(settings): persist section toggle state via saveData, not saveSettings The collapsible-section toggle handler is UI-only state that shouldn't trigger plugin.saveSettings()'s lifecycle reconciliation (api-key / provider / Ollama-URL / RAG-enabled checks + lifecycle.setup re-run). Switch to plugin.saveData(plugin.settings) directly so expand/collapse clicks just touch data.json. Addresses CodeRabbit review feedback. * fix(settings): use spans inside <summary> instead of <div> for HTML spec compliance The HTML spec only permits phrasing/heading content inside <summary>; <div> is flow content and not valid as a child. Browsers parse it but it can cause inconsistent rendering and accessibility issues. Switch the section header / title-row / description elements from <div> to <span>. Existing CSS already declared display: flex on the header and title-row, so they keep their layout once the elements are spans; add display: block to the description rule so its inline-by-default span behaves the same as the previous div. Done in both createCollapsibleSection and createAlwaysOpenSection for parity (the latter doesn't sit in a <summary> but matches for consistency). * docs(settings): annotate TOC entries with their UI section mapping The intro paragraph describes the new UI layout (General → User Experience → Automation → Vault Search Index → advanced sections), but the per-setting reference below still groups by topic, so anchors like #user-experience don't exist. CodeRabbit's complaint was that the new layout summary is hard to navigate. Rather than restructure the entire reference (large rewrite, breaks existing #basic-settings, #ui-settings, etc. inbound links), annotate each TOC entry with its UI section in parentheses, plus a note pointing readers to the guides for the two UI sections (Automation, Vault Search Index) that don't have dedicated reference entries. Anchors all resolve; readers can navigate from a UI section name to the relevant reference topic. * fix(settings): use adjacent-sibling selector for section separators The `:first-of-type` rule was buggy: it matched both the General `<div>` and the first `<details>` of its type, so the first collapsible (User Experience) lost its top border separator from General above. CodeRabbit caught this. Switch to the adjacent-sibling selector (`section + section`) so the separator is element-agnostic and only the very first section in the tab goes without a border.
1 parent 1c546dd commit a4509b3

18 files changed

Lines changed: 686 additions & 363 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Gemini Scribe is an Obsidian plugin that integrates Google's Gemini AI models, p
3030
## Features
3131

3232
- **Agent Mode with Tool Calling:** An AI agent that can actively work with your vault! It can search for files, read content, create new notes, edit existing ones, move and rename files, create folders, and even conduct deep research with proper citations. Features persistent sessions, granular permission controls, session-specific model configuration, and a diff review view that lets you inspect and edit proposed file changes before they're written.
33-
- **Semantic Vault Search:** [Experimental] Search your vault by meaning, not just keywords. Uses Google's File Search API to index your notes in the background. The AI can find relevant content even when you don't remember exact words. Supports PDFs and attachments, with pause/resume controls and detailed status tracking.
33+
- **Semantic Vault Search:** Search your vault by meaning, not just keywords. Uses Google's File Search API to index your notes in the background. The AI can find relevant content even when you don't remember exact words. Supports PDFs and attachments, with pause/resume controls and detailed status tracking.
3434
- **Context-Aware Agent:** Add specific notes as persistent context for your agent sessions. The agent can access and reference these context files throughout your conversation, providing highly relevant and personalized responses.
3535
- **Smart Summarization:** Quickly generate concise, one-sentence summaries of your notes and automatically store them in the document's frontmatter, using a dedicated Gemini model optimized for summarization.
3636
- **Selection-Based AI Features:** Work with selected text in powerful ways:

docs/guide/faq.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,11 +171,11 @@ Make sure you're on v4.0 or later — earlier versions had a bug where context f
171171

172172
This command requires: (1) a markdown file actively open in the editor, and (2) context sending to be enabled. If no file is open, you'll see "Failed to get file content for summary." ([#134](https://github.com/allenhutchison/obsidian-gemini/issues/134))
173173

174-
## Semantic Vault Search (Experimental)
174+
## Semantic Vault Search
175175

176176
### What does the Vault Index feature do? Is my data private?
177177

178-
The vault index uses Google's File Search API to enable semantic (meaning-based) search of your vault. Files are stored in an index private to your GCP project, tied to your API key. Your data is not shared or used for model training. The feature is experimental and located under Advanced Settings. ([#297](https://github.com/allenhutchison/obsidian-gemini/discussions/297))
178+
The vault index uses Google's File Search API to enable semantic (meaning-based) search of your vault. Files are stored in an index private to your GCP project, tied to your API key. Your data is not shared or used for model training. The feature is configured under the **Vault Search Index** section in the plugin settings. ([#297](https://github.com/allenhutchison/obsidian-gemini/discussions/297))
179179

180180
## Plugin Conflicts
181181

docs/reference/settings.md

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,31 @@
22

33
This document provides a comprehensive reference for all Obsidian Gemini Scribe settings.
44

5+
The settings tab is organised into a permanently-open **General** section at the top — covering provider, API key, models, and the plugin state folder — followed by collapsible sections (▶ collapsed, ▼ expanded). Click any header to toggle it; expand/collapse state is remembered between sessions in the `expandedSettingsSections` setting. All collapsible sections start collapsed.
6+
7+
The order of sections is:
8+
9+
1. **General** (always open) — provider, API key, models, plugin state folder, Show Advanced Settings toggle.
10+
2. **User Experience** — your name, frontmatter key, streaming, diff view, session history toggle, tool execution logging.
11+
3. **Automation** — scheduled tasks, scheduler catch-up, and lifecycle hooks combined.
12+
4. **Vault Search Index** — semantic search over your vault using Google File Search.
13+
14+
Advanced sections — Tool Permissions, MCP Servers, Agent Config, Debug — are tagged with an **ADVANCED** pill and only appear after toggling **Show Advanced Settings** at the bottom of General. **Agent Config** bundles four related sub-areas (Custom Prompts, API Configuration, Context Management, Tool Loop Detection) under one collapsible since they all tune how the agent talks to the model.
15+
516
## Table of Contents
617

7-
- [Basic Settings](#basic-settings)
8-
- [Model Configuration](#model-configuration)
9-
- [Custom Prompts](#custom-prompts)
10-
- [UI Settings](#ui-settings)
11-
- [Context Management](#context-management)
12-
- [Developer Settings](#developer-settings)
18+
The reference below groups settings by topic for lookup, which doesn't always map 1:1 to the UI section names. The annotation in parentheses tells you which UI section a topic appears under.
19+
20+
- [Basic Settings](#basic-settings) (UI: _General_ — provider, API key, models, plugin state folder)
21+
- [Model Configuration](#model-configuration) (UI: _General_ — chat/summary/completion/image model selection)
22+
- [Custom Prompts](#custom-prompts) (UI: _Agent Config_ — advanced)
23+
- [UI Settings](#ui-settings) (UI: _User Experience_ — streaming, diff view, identity, frontmatter key, session history)
24+
- [Context Management](#context-management) (UI: _Agent Config_ — advanced)
25+
- [Developer Settings](#developer-settings) (UI: split across _Agent Config_, _Tool Permissions_, _MCP Servers_, _Debug_)
1326
- [Session-Level Settings](#session-level-settings)
1427

28+
UI sections without a dedicated topic in this reference: _Automation_ (covered in the [Scheduled Tasks](/guide/scheduled-tasks) and [Lifecycle Hooks](/guide/lifecycle-hooks) guides) and _Vault Search Index_ (covered in [Semantic Search](/guide/semantic-search)).
29+
1530
## Basic Settings
1631

1732
### Provider
@@ -173,6 +188,14 @@ See the [Custom Prompts Guide](/guide/custom-prompts) for detailed instructions.
173188
- **Why opt-in**: Vault events fire continuously; an unintentionally-broad hook can drain API quota quickly. The default is off so users opt in deliberately.
174189
- **See also**: [Lifecycle Hooks](/guide/lifecycle-hooks)
175190

191+
### Expanded Settings Sections
192+
193+
- **Setting**: `expandedSettingsSections`
194+
- **Type**: `string[]`
195+
- **Default**: `[]`
196+
- **Description**: Internal list of section ids that are currently expanded in the settings tab. Updated automatically when you toggle a section. Known ids: `ui`, `automation`, `rag`, `tool-permissions`, `mcp-servers`, `agent-config`, `debug`. (General is always open; it has no id and ignores this setting.)
197+
- **Note**: Edit `data.json` directly to pre-expand sections (for example, on a new install) or restore a custom layout after migrating vaults.
198+
176199
## Context Management
177200

178201
Context management automatically monitors and controls conversation size to prevent exceeding model token limits.

src/main.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ export interface ObsidianGeminiSettings {
9999
autoRunCatchUp: boolean;
100100
// Lifecycle hooks (opt-in: AI runs triggered by vault events)
101101
hooksEnabled: boolean;
102+
// IDs of collapsible settings sections currently expanded; persists across reloads.
103+
expandedSettingsSections: string[];
102104
// Cached remote model list (managed by ModelListProvider)
103105
remoteModelCache?: { models: GeminiModel[]; timestamp: number };
104106
}
@@ -156,6 +158,8 @@ const DEFAULT_SETTINGS: ObsidianGeminiSettings = {
156158
autoRunCatchUp: false,
157159
// Lifecycle hooks default off (opt-in)
158160
hooksEnabled: false,
161+
// All settings sections start collapsed
162+
expandedSettingsSections: [],
159163
};
160164

161165
const MIGRATION_SECRET_NAME = 'gemini-scribe-api-key';
Lines changed: 101 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type ObsidianGemini from '../main';
22
import { Setting, Notice, debounce } from 'obsidian';
3+
import { createCollapsibleSection } from './settings-helpers';
34
import { getErrorMessage } from '../utils/error-utils';
45
import type { SettingsSectionContext } from './settings';
56

@@ -8,15 +9,24 @@ let temperatureRunId = 0;
89
let topPDebounceTimer: NodeJS.Timeout | null = null;
910
let topPRunId = 0;
1011

11-
export async function renderApiSettings(
12+
/**
13+
* "Agent Config" advanced section — combines Custom Prompts, API Configuration,
14+
* Context Management, and Tool Loop Detection into a single collapsible with
15+
* labeled sub-groups, since they all tune how the agent talks to the model.
16+
*/
17+
export async function renderAgentConfigSettings(
1218
containerEl: HTMLElement,
1319
plugin: ObsidianGemini,
14-
_context: SettingsSectionContext
20+
context: SettingsSectionContext
1521
): Promise<void> {
22+
const sectionEl = createCollapsibleSection(plugin, containerEl, 'Agent Config', 'agent-config', {
23+
description:
24+
'Tune how the agent talks to the model: custom prompts, retry/generation parameters, conversation summarization, and loop guards.',
25+
advanced: true,
26+
});
27+
1628
// Debounce saveSettings() for text inputs so typing doesn't trigger the plugin
1729
// lifecycle on every keystroke. Settings are mutated immediately; only the save is delayed.
18-
// The callback is async + wrapped in try/catch so rejections from saveSettings() don't
19-
// become unhandled promise rejections.
2030
const debouncedSave = debounce(
2131
async () => {
2232
try {
@@ -30,40 +40,39 @@ export async function renderApiSettings(
3040
true
3141
);
3242

33-
// File Logging
34-
new Setting(containerEl)
35-
.setName('Log to file')
43+
// --- Custom Prompts ---
44+
new Setting(sectionEl).setName('Custom Prompts').setHeading();
45+
46+
new Setting(sectionEl)
47+
.setName('Allow system prompt override')
3648
.setDesc(
37-
'Write log entries to a file in the plugin state folder. ' +
38-
'Errors and warnings are always logged; debug entries require Debug Mode. ' +
39-
'Log files are automatically rotated at 1 MB.'
49+
'WARNING: Allows custom prompts to completely replace the system prompt. This may break expected functionality.'
4050
)
4151
.addToggle((toggle) =>
42-
toggle.setValue(plugin.settings.fileLogging).onChange(async (value) => {
43-
plugin.settings.fileLogging = value;
52+
toggle.setValue(plugin.settings.allowSystemPromptOverride ?? false).onChange(async (value) => {
53+
plugin.settings.allowSystemPromptOverride = value;
4454
await plugin.saveSettings();
4555
})
4656
);
4757

48-
// Custom Prompts Advanced Settings
49-
new Setting(containerEl).setName('Custom Prompts').setHeading();
58+
// --- API Configuration ---
59+
new Setting(sectionEl).setName('API Configuration').setHeading();
5060

51-
new Setting(containerEl)
52-
.setName('Allow system prompt override')
61+
new Setting(sectionEl)
62+
.setName('Log to file')
5363
.setDesc(
54-
'WARNING: Allows custom prompts to completely replace the system prompt. This may break expected functionality.'
64+
'Write log entries to a file in the plugin state folder. ' +
65+
'Errors and warnings are always logged; debug entries require Debug Mode. ' +
66+
'Log files are automatically rotated at 1 MB.'
5567
)
5668
.addToggle((toggle) =>
57-
toggle.setValue(plugin.settings.allowSystemPromptOverride ?? false).onChange(async (value) => {
58-
plugin.settings.allowSystemPromptOverride = value;
69+
toggle.setValue(plugin.settings.fileLogging).onChange(async (value) => {
70+
plugin.settings.fileLogging = value;
5971
await plugin.saveSettings();
6072
})
6173
);
6274

63-
// API Configuration
64-
new Setting(containerEl).setName('API Configuration').setHeading();
65-
66-
new Setting(containerEl)
75+
new Setting(sectionEl)
6776
.setName('Maximum Retries')
6877
.setDesc('Maximum number of retries when a model request fails.')
6978
.addText((text) =>
@@ -79,7 +88,7 @@ export async function renderApiSettings(
7988
})
8089
);
8190

82-
new Setting(containerEl)
91+
new Setting(sectionEl)
8392
.setName('Initial Backoff Delay (ms)')
8493
.setDesc('Initial delay in milliseconds before the first retry. Subsequent retries will use exponential backoff.')
8594
.addText((text) =>
@@ -95,11 +104,75 @@ export async function renderApiSettings(
95104
})
96105
);
97106

98-
// Create temperature setting with dynamic ranges
99-
await createTemperatureSetting(containerEl, plugin);
107+
await createTemperatureSetting(sectionEl, plugin);
108+
await createTopPSetting(sectionEl, plugin);
109+
110+
// --- Context Management ---
111+
new Setting(sectionEl).setName('Context Management').setHeading();
112+
113+
const thresholdSetting = new Setting(sectionEl)
114+
.setName('Context compaction threshold')
115+
.setDesc(
116+
`Automatically summarize older conversation turns when token usage exceeds this percentage of the model context window. Current: ${plugin.settings.contextCompactionThreshold}%`
117+
);
118+
119+
thresholdSetting.addSlider((slider) =>
120+
slider
121+
.setLimits(5, 50, 5)
122+
.setValue(plugin.settings.contextCompactionThreshold)
123+
.setDynamicTooltip()
124+
.onChange(async (value) => {
125+
plugin.settings.contextCompactionThreshold = value;
126+
thresholdSetting.setDesc(
127+
`Automatically summarize older conversation turns when token usage exceeds this percentage of the model context window. Current: ${value}%`
128+
);
129+
await plugin.saveSettings();
130+
})
131+
);
132+
133+
// --- Tool Loop Detection ---
134+
new Setting(sectionEl).setName('Tool Loop Detection').setHeading();
135+
136+
new Setting(sectionEl)
137+
.setName('Enable loop detection')
138+
.setDesc('Prevent the AI from repeatedly calling the same tool with identical parameters.')
139+
.addToggle((toggle) =>
140+
toggle.setValue(plugin.settings.loopDetectionEnabled).onChange(async (value) => {
141+
plugin.settings.loopDetectionEnabled = value;
142+
await plugin.saveSettings();
143+
context.redisplay();
144+
})
145+
);
146+
147+
if (plugin.settings.loopDetectionEnabled) {
148+
new Setting(sectionEl)
149+
.setName('Loop threshold')
150+
.setDesc('Number of identical tool calls before considering it a loop (default: 3).')
151+
.addSlider((slider) =>
152+
slider
153+
.setLimits(2, 10, 1)
154+
.setValue(plugin.settings.loopDetectionThreshold)
155+
.setDynamicTooltip()
156+
.onChange(async (value) => {
157+
plugin.settings.loopDetectionThreshold = value;
158+
await plugin.saveSettings();
159+
})
160+
);
100161

101-
// Create topP setting with dynamic ranges
102-
await createTopPSetting(containerEl, plugin);
162+
new Setting(sectionEl)
163+
.setName('Time window (seconds)')
164+
.setDesc('Time window to check for repeated calls (default: 30 seconds).')
165+
.addSlider((slider) =>
166+
slider
167+
.setLimits(10, 120, 5)
168+
.setValue(plugin.settings.loopDetectionTimeWindowSeconds)
169+
.setDynamicTooltip()
170+
.onChange(async (value) => {
171+
plugin.settings.loopDetectionTimeWindowSeconds = value;
172+
await plugin.saveSettings();
173+
})
174+
);
175+
}
103176
}
104177

105178
async function createTemperatureSetting(containerEl: HTMLElement, plugin: ObsidianGemini): Promise<void> {
@@ -120,18 +193,14 @@ async function createTemperatureSetting(containerEl: HTMLElement, plugin: Obsidi
120193
.setValue(plugin.settings.temperature)
121194
.setDynamicTooltip()
122195
.onChange(async (value) => {
123-
// Clear previous timeout
124196
if (temperatureDebounceTimer) {
125197
clearTimeout(temperatureDebounceTimer);
126198
}
127199

128-
// Set immediate value for responsive UI
129200
plugin.settings.temperature = value;
130201

131-
// Capture the run ID upfront so we can discard stale async results.
132202
const runId = ++temperatureRunId;
133203

134-
// Debounce validation and saving
135204
temperatureDebounceTimer = setTimeout(async () => {
136205
try {
137206
// Validate the current value against model capabilities. Read from
@@ -189,30 +258,21 @@ async function createTopPSetting(containerEl: HTMLElement, plugin: ObsidianGemin
189258
.setValue(plugin.settings.topP)
190259
.setDynamicTooltip()
191260
.onChange(async (value) => {
192-
// Clear previous timeout
193261
if (topPDebounceTimer) {
194262
clearTimeout(topPDebounceTimer);
195263
}
196264

197-
// Set immediate value for responsive UI
198265
plugin.settings.topP = value;
199266

200-
// Capture the run ID upfront so we can discard stale async results.
201267
const runId = ++topPRunId;
202268

203-
// Debounce validation and saving
204269
topPDebounceTimer = setTimeout(async () => {
205270
try {
206-
// Validate the current value against model capabilities. Read from
207-
// settings rather than the captured `value` so the validation always
208-
// matches the most recent user input.
209271
const validation = await modelManager.validateParameters(
210272
plugin.settings.temperature,
211273
plugin.settings.topP
212274
);
213275

214-
// A newer slider change has superseded this run — discard the
215-
// stale result instead of clobbering the current slider/value.
216276
if (runId !== topPRunId) {
217277
return;
218278
}
@@ -227,8 +287,6 @@ async function createTopPSetting(containerEl: HTMLElement, plugin: ObsidianGemin
227287

228288
await plugin.saveSettings();
229289
} catch (error) {
230-
// If a newer run has superseded us, drop this stale failure silently —
231-
// surfacing it would contradict whatever the current run is doing.
232290
if (runId !== topPRunId) {
233291
return;
234292
}

0 commit comments

Comments
 (0)