Repository navigation
Redesign Defaults and Chat settings; fix semantic search, sync churn and workspace duplication - #416
Merged
Merged
Conversation
Blessed mockup: docs/mockups/defaults-settings-essentials.html (2026-10-14, 5.20.4). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
StatusBadge had no importer in src/ or tests/, so the component and its .nexus-status-badge rules go. The settings header also loses a bare ';' and the dead "Title and description" comment above it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Chat settings modal no longer merges its context window into the global contextWindowOverrides defaults; the value applies to the open conversation only, through the existing requestContextChange handoff. This reverses the save-to-defaults half of PR #410. Defaults no longer push context window changes into the active chat (ChatView.applyContextWindowDefault is removed). Defaults apply to new chats only, per the owner decision of 2026-10-14. Tests: the modal handoff suite now asserts global defaults are never written; the Defaults suite asserts the open chat is not touched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saving Chat settings while no conversation is open kept the values in ModelAgentManager memory, but selecting the first conversation created afterwards re-read the plugin defaults and dropped them. The modal now marks those selections to carry; the next conversation that has no saved selections yet adopts them (including the committed context window) and stores them in its chatSettings. Reloading the welcome defaults clears the carry. ChatSettingsWithoutConversation.test.ts drives the real modal save, ModelAgentManager and chat ConversationManager.createConversation; it failed on the baseline with temperature reset to the default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add resolveEmbeddingService(plugin), which reads plugin.embeddingManager and returns its service only while the manager is enabled and initialized. ServiceAccessors used to ask the registry for 'embeddingService', a name nothing registers, so semantic search of past conversations never ran. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SearchContentTool cached the first service it saw and SearchManagerAgent pre-seeded that cache from a dead registry lookup. Drop both and the setEmbeddingService seam; the tool now asks resolveEmbeddingService on each semantic search, so a stopped or rebuilt service is never used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
initializeSuggesters captured getServiceIfReady('embeddingService') once,
which was always null, so composer suggestions were always fuzzy.
searchNotes now accepts a resolver as well as an instance, and the note
suggester resolves the service per query.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The task board and the Workspaces tab task detail looked up 'embeddingService' in the registry and always got null. Both now go through resolveEmbeddingService. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Statically collects string names passed to getServiceIfReady/getService under src/ and checks them against CORE_SERVICE_DEFINITIONS, ADDITIONAL_SERVICE_FACTORIES and the src/core/index.ts registrations. The dead 'stateManager' shutdown lookup is listed as a known exception. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With no provider configured, the chat welcome's "Open settings" button opened the per-chat Chat settings modal, which has nothing to offer until a provider exists. It now opens Settings -> Nexus on the Providers tab. - UIStateController: the welcome callback is now setOpenProviderSettingsCallback. - SettingsTabManager.openNexusSettings(route) hands the route to the settings view, then opens Obsidian settings on the Nexus tab; exposed through PluginLifecycleManager and NexusPlugin.openNexusSettings. - SettingsView.openTo(route) navigates at once when settings are on screen, otherwise on the next display(), before the tab strip is built. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WorkspaceFormRenderer saves the workspace prompt binding as the
top-level dedicatedAgentId, but ChatSettingsRenderer.syncWorkspacePrompt
read only the deprecated context.dedicatedAgent.agentId, and
DefaultsTab.loadWorkspaces passed only { id, name }. Choosing a workspace
on Defaults therefore never selected its prompt.
The renderer now uses WorkspacePromptResolver's precedence (top-level
first, then the deprecated path), its workspace option type carries
dedicatedAgentId, and Defaults passes dedicatedAgentId and context.
The context-note Add and remove buttons now register clicks through the
renderer's Component (registerDomEvent) instead of onclick assignments;
the Component is unloaded on every redraw and on destroy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ingestion and Embeddings were appended after ChatSettingsRenderer.render(),
which empties its container on every redraw (provider or model change, LLM
settings change), so both sections vanished after the first redraw. They
now render inside the renderer's renderAfterImageSection hook, next to
Video and Voice, as an interim fix until the redesigned layout replaces
the hook.
Within Ingestion the toggle is created first and every dependent row,
including auto-convert, lives in the container that is hidden while the
toggle is off. Rows render synchronously; only the vision model dropdowns
wait for the capability lookup, and a lookup failure no longer escapes
as an unhandled rejection.
Copy: the toggle is "File import" ("Drop PDFs or audio into chat to turn
them into notes.") per the owner decision; auto-convert is "Convert new
files automatically" and no longer refers to "the defaults below".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add resolveThinkingForModel, which keeps the stored thinking preference but returns enabled:false for registry models without supportsThinking, and use it in ModelAgentPromptContextAssembler so a hidden preference is no longer sent to non-reasoning models such as Mistral Large. Add getSupportedThinkingEfforts / normalizeThinkingEffortForModel, a pure helper for the Thinking dropdown (owner decision 8): it derives the levels a provider/model distinguishes from the adapters' own effort mappings and returns all five when there is no capability data. The Anthropic adaptive model predicate moves into ThinkingEffortMapper so the adapter and the helper share it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…w settings Add SubagentModelResolver, the single precedence for agent runs (subagents and executePrompt): explicit tool parameter, the chat's subagent model, the default subagent model, a cloud chat model, then the first cloud provider. resolveAgentRunSettings combines thinking (gated by model capability), Creativity (dropped for models with supportsSamplingParams false) and the context budget (clamped to the model window). LLMService.getAgentModel now honours a configured agentModel whatever the chat model is, exposes getAgentDefaults(), and executePrompt forwards enableThinking/thinkingEffort to the adapter. New optional settings, no migration: llmProviders.agentTemperature and llmProviders.agentContextWindowOverrides, and per-chat chatSettings.agentTemperature / agentContextWindow carried through the defaults resolver, ModelAgentManager restore/save and the settings store. The conversation_updated event stores the whole chatSettings object and the applier replays it verbatim, so the new fields survive a cache rebuild. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…mpt runs Subagents no longer inherit the chat model and thinking unconditionally. SubagentController.buildSubagentContext resolves provider, model, thinking, Creativity and context window with the shared agent precedence, using this chat's subagent settings (new SubagentContextProvider.getAgentSettings) and the defaults. SubagentExecutor forwards thinking and temperature to the streaming generator, which passes them to generateResponseStream, and uses the context window as a budget for the inherited workspace data and context files: whole items beyond it are dropped and named in one prompt line. executePrompt (PromptExecutor) now sends the agent temperature (omitted for models that reject sampling) and gated agent thinking, and picks its model with the same precedence. A chat's own subagent settings apply when that chat calls the tool: the tool batch executor passes its runtime identity to tools that opt in via acceptsToolRuntimeContext, and ExecutePromptsTool matches it against the chat view's conversation, as the subagent tool does. MCP calls and other conversations use the defaults. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drops and recreates the three vec0 tables with the schema DDL and empties the embedding metadata tables and the conversation backfill cursor, in one transaction, then saves. Conversations, messages, traces, the notes index and the learned retrieval adapter file are untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EmbeddingManager.setEnabled() starts and stops the engine, watchers, queue, status bar and dream cycle without a restart (the dream interval and the dream-now command are now undone on stop). A shared teardown backs both setEnabled(false) and shutdown(), and a run generation stops a background chain between phases once it is torn down. New public surface for the settings fold: getStatus(), getProgress(), onStateChange() (start, stop, delete and every queue progress event), pauseIndexing()/resumeIndexing(), and getStats() that reads stored counts from the database even while off, plus a distinct conversation count. deleteIndex() clears the vectors only while off and keeps the AdapterStore. IndexingQueue gains pruneMissingNotes() and pruneMissingConversations(), run before the first phase on every pass, so vectors of notes deleted or renamed and chats deleted while search was off are removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A synced enableEmbeddings change no longer counts as needs-reload; it is adopted and applied through PluginLifecycleManager.setEmbeddingsEnabled(). ChatTraceService follows the manager's state, so traces stop going to a stopped embedding service and pick up the new one after a restart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
search content keeps its semantic property (ToolCliNormalizer rejects unknown flags, so removing it would turn --semantic into an error) but re-describes it, drops the semantic pitch from the tool description, and answers semantic=true with keyword results and a note saying semantic search is turned off in Nexus settings. The description follows the setting live. With search on, the committed description is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Their deferred 3s indexing timer stayed open after the test ended. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The obsidian mock's Setting appended nothing and dropped its name, so no test could observe row order, descriptions, disabled state or whether a redraw replaced a node. FakeElement keeps an ordered tree, tracks isConnected, dispatches registered events and answers simple selectors. Given a FakeElement container, Setting renders Obsidian's markup and the Dropdown, Toggle, Slider, Button, ExtraButton and ProgressBar mocks render real-looking controls that react to change/click events. The Slider mock reproduces the native span.slider-value that Obsidian 1.13+ draws, with setDisplayFormat. SettingGroup and Modal.setTitle are added. Every other container keeps the old call-recording mock unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The redesigned Defaults and Chat settings use SettingGroup and the core .setting-group styles, both added in Obsidian 1.11.0; the obsidianmd/no-unsupported-api lint rejects them under 1.10.0. Raise minAppVersion in manifest.json; version-bump.mjs records the new mapping in versions.json at the next release, so users on 1.10 stay on 5.20.x. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Accordion takes an options object (id, title, icon, summary, open, onToggle). Its toggle is a native button with aria-expanded and aria-controls; the content is a setting-group with one stable setting-items list (itemsEl), replacing getContentEl(), which created a new div on every call. The summary shows only while closed. FoldStateStore remembers open folds per device and per surface with app.saveLocalStorage, so a fold toggle never becomes a synced settings change that other devices must reload for. createSettingGroup wraps SettingGroup and returns its row list. bindSliderReadout writes the formatted value into the span.slider-value Obsidian 1.13+ draws itself (setDisplayFormat) and adds one span only when none exists. The extra plugin span was the cause of the duplicate "High 2" and "1,000,000 tokens 1000000" readouts, confirmed by reading the 1.14.4 app bundle; recorded in the testing skill's refinement log. Get started moves to the new Accordion API (rocket icon). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both surfaces keep one shared renderer and now draw the blessed layout:
no heading or intro, an always-open SettingGroup with Provider, Model
and Thinking, then icon folds that start collapsed and summarise
themselves in one line: Chat options, Context, Subagents, Images,
Video, Voice, File conversion and Semantic search. Video, File
conversion and Semantic search (desktop only) are Defaults-only folds;
Web search and Workflow appear only in Chat settings.
The shell is built once. Each group empties and refills only its own
rows (chat model -> top block, Chat options, Subagents; subagent model
-> Subagents; image provider -> Images; workspace -> Context; synced
settings -> every group), so open folds and the scroll position
survive redraws. Fold state is remembered per device and per surface.
- Thinking is one dropdown (Off, Low ... Max) over the existing
{ enabled, effort } storage; Off keeps the effort. getThinkingLevels
is the hook for capability filtering.
- Creativity and Context window use the single slider readout. The
context window keeps its per-model 1-token slider; a discovered window
redraws Chat options instead of appending rows out of order.
- Context notes are Setting rows with an Add note button and a remove
extra button per note. A text-only provider is explained in the Model
row description instead of a warning box.
- VoiceSettings is the only voice implementation. It edits the renderer
settings; Defaults persists read-aloud and live-voice picks only once
the user makes them (source: 'user'), and opening the tab saves
nothing. Warnings become the Provider row description.
- VideoSettings, FileConversionSettings and SemanticSearchSettings are
the Defaults folds. File conversion renders File import first and its
dependent rows only while it is on, Vision rows only for Vision.
SemanticSearchSettings hosts the enableEmbeddings switch behind a
render(container) entry point for the full semantic UI.
- DefaultsTab loses its header, intro and duplicate voice, video,
ingestion and embeddings code; every write goes through its save queue.
- Chat settings uses the modal title and a bottom modal-button-container.
CSS: the shared accordion section replaces the old mcp-accordion rules;
dead csr-* (except csr-section, still used by DataTab), nexus-voice-*,
thinking-settings and chat-settings header/button rules are removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds the Semantic search fold copy (index status, indexing, delete index) and the Turn off semantic search confirmation from the blessed mockup, plus the Delete the search index confirmation copy (owner decision 6). The fold summary reads "Off · no index" once the index has been deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e index
The Semantic search fold on Defaults now reads the embedding runtime:
- On: "Index" with note and chat counts from getStats().
- Indexing: a ProgressBarComponent, done / total, an estimate and a
pause / resume ExtraButton, updated in place on each progress event.
- Turning off asks first (SemanticSearchOffModal); Cancel or Escape keeps
it on. The switch is applied at runtime through the lifecycle manager,
with no restart notice.
- Off: "Index" with what is kept ("Paused · N notes") and a Delete index
button (mod-warning) behind a "Delete the search index?" confirmation,
which calls EmbeddingManager.deleteIndex().
The fold subscribes to onStateChange while mounted and unsubscribes on
destroy. The runtime is resolved on every use, since it is created only
once storage is ready.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…king rows The selected model now decides its rows in Defaults and Chat settings: - Creativity is disabled with "<Model> sets its own temperature." and a "—" readout for models whose registry entry rejects sampling parameters (StaticModelsService now carries supportsSamplingParams). Unknown models keep it enabled. - Context window is a SliderComponent over preset stops (32K … 1M) strictly below the model maximum, plus the maximum. A single stop disables it, a saved size between stops shows as saved until moved, and picking a new provider or model starts that model at its maximum. - Thinking offers only the levels the model distinguishes (owner decision 8), using the shared thinkingLevels helper, and shows a folded level as the level really sent without rewriting the stored preference. - The Subagents fold gains Creativity and Context window, backed by agentTemperature and agentContextWindowOverrides. Without a subagent model its rows and summary describe the cloud chat model subagents fall back to. - Fold summaries follow: "Fixed temperature", and the Subagents summary adds the subagent tuning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…omplete EmbeddingService.initialize() swallows a load failure and turns the service off, after which embedNote() is a silent no-op. The note pass walked the queue anyway and ended with phase 'complete', N of N, over an empty index, next to a "Failed to load embedding model" notice. It now ends with phase 'error', which the status bar reports once, so the service no longer raises its own notice as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The store is built at plugin load and lives as long as the plugin, but took its factory from window.activeWindow. When Nexus loads while a popout or the Settings window (its own window since Obsidian 1.14) has focus, as when the plugin is enabled from Settings, the factory belongs to that window. Verified in Obsidian 1.14.4: once that window closes, its indexedDB.open() returns null, so every later cache save would throw. Same origin, same database, so the main window's factory is the correct one in every case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Get started Replace the stale Gemini CLI section with Antigravity's real config path (~/.gemini/config/mcp_config.json, legacy location noted) and send readers to Get started -> Connect an external agent instead of the old MCP integration page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nothing has registered 'stateManager' since ProcessedFilesStateManager was removed, so the shutdown save was a no-op. Remove it and empty the contract test's KNOWN_UNREGISTERED list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esters The task board edit modal and the task detail view built their note-link suggesters with the embedding service captured when they opened, so turning semantic search on (or a rebuild) while they were open was never picked up. NoteInputSuggester now takes an EmbeddingServiceSource resolver, like the chat [[ suggester, and both callers pass one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… Try again Since the note pass ends with phase 'error' when the embedding model does not load, the Semantic search fold fell through to the "on" view and showed the index counts as if nothing were wrong. It now shows "Couldn't load the search model" with the queue's detail and a Try again button, and the fold summary reads "On · couldn't load". Try again calls a new EmbeddingManager.retry(): after a load failure the manager still counts as running, so setEnabled(true) is a no-op and the service has turned itself off. retry() tears the components down and starts them over, serialized with setEnabled and deleteIndex. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…main window Both are hidden elements that outlive the click that started them, and both were appended to window.activeDocument, which since Obsidian 1.14 includes the Settings window as well as any popout. - The live-voice <audio> element is attached after an awaited client-secret request and stays for the whole call. If focus moved to a popout or Settings meanwhile, closing that window removed the element and the assistant went silent. - base analyze renders a scratch base into an off-screen host for the length of the query. It is started by a tool call, so the focused window is arbitrary; if that window closed mid-query the host was detached, the controller's isShown() gate never opened, and the call timed out with no rows. Neither is visible, so the main window is always the right host, as for the embedding iframe in 2b15f95. The Jest window mock now exposes its own document so tests can tell it apart from activeDocument. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The click-outside listener was added to window.activeDocument when the suggester was built, with the chat view. A chat view restored into a popout is built while the main window has focus, so the listener heard the wrong window for the life of the view: clicking away inside the popout never closed the dropdown. destroy() removed the listener from whichever window had focus at teardown, so the original one leaked. The listener, the dropdown and its positioning now use the input element's ownerDocument and that document's window (the bottom offset used the main window's innerHeight, misplacing the dropdown in a popout of a different height). destroy() removes the listener from the document it was added to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
13633b3 replaced globalThis with window.activeWindow in desktopRequire and the eight private loadDesktopModule copies. activeWindow is whichever window has focus: a popout, or the Settings window (its own window since Obsidian 1.14), which is where OAuth and CLI-provider setup is clicked. The plugin runs in the main window, the only one guaranteed to have Node's require; looked up elsewhere it may be missing, or hand back modules whose objects (the OAuth callback server, child processes) belong to a window that can close under them. All nine now use window, which is what globalThis meant before. A source guard covers the private copies, which cannot be reached without standing each service up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two open handles kept a Jest worker alive after the full run: - BaseImageAdapter raced generation against a 2-minute timeout and never cleared it, so the timer outlived every finished generation (in the plugin too, not just tests). It is now cleared once the race settles. - MessageBubble tests never loaded their bubbles, so cleanup()'s unload was a no-op and the reasoning ThinkingLoader interval ran forever. The tests now load each bubble the way MessageDisplay does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Covers the Obsidian 1.11.0 requirement, the Defaults and Chat settings redesign, subagent model and settings, Get started and agent connect, runtime semantic search controls, and the embedding fixes. No version bump. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
DefaultWorkspaceManager initializes in the background while the hybrid adapter is still starting. Its existence check (getWorkspace -> withReadableBackend) fell back to the legacy .workspaces index because the adapter was not ready, and that index never holds hybrid workspaces, so it answered "missing". The create then waited for the adapter (resolveWritableAdapter) and wrote through it, where the workspace existed: one more workspace_created event per load, then SQLITE_CONSTRAINT_UNIQUE on workspaces.name logged by SQLiteCacheManager and workspaceRepository. Add WorkspaceService.ensureWorkspace, which checks the store it will write to after that store has finished replaying, and adopts a legacy default by name. DefaultWorkspaceManager and ChatTraceService use it instead of the getWorkspace + createWorkspace pair. The default-workspace unique-conflict recovery in createWorkspace now also recognizes the repository's typed WorkspaceConflictError. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
WorkspaceRepository.create appended workspace_created to JSONL and only then let the SQLite INSERT discover that the id or name was taken. The append is outside what the transaction rolls back, so every rejected create left an event for a workspace that was never created. Replay applies it with INSERT OR REPLACE: a same-id duplicate reset the workspace to its creation values, and a same-name phantom deleted the real workspace on the next cache rebuild. Measured in a test vault: an edit to the default workspace was gone after "Nexus: Rebuild cache". - create checks for an id/name conflict inside the transaction, before the append, and throws WorkspaceConflictError (not logged as a failure). - WorkspaceEventApplier treats a workspace_created for an id that already has a row as a no-op, so streams that 5.17.0-5.20.4 already filled with duplicate default-workspace creates replay with their edits intact. Tests run on real in-memory SQLite (node:sqlite) with the production DDL and transaction coordinator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A fresh install stores the shipped default chat model (an OpenAI model) and default image model before any provider is set up. The Defaults tab described them anyway: Chat options read "Fixed temperature · 1.05M context", Subagents named the OpenAI default with "Thinking off", the top block showed a Thinking "Off" dropdown, and Images showed the raw id "gemini-3.1-flash-image". - The chat model counts only when its provider is usable (the runtime skips such a model too). Without one, Thinking is hidden, Creativity and Context window are disabled rows reading "—", and the Chat options summary says "Pick a model first". The saved model is left alone. - Subagents with no model of their own and no usable cloud chat model say "No model" (no cloud provider) or "Not set", never a model name. Their Provider dropdown gains an explicit "Same as chat" option, selected while no subagent model is set, and the Model row reads "Same as chat (<model>)". - Images and Voice summaries use catalog display names before the model lists load: the image adapters' model tables are now module exports read by imageModelNames.ts, and Voice falls back to the transcription and speech catalogs. The raw id is shown only for an unknown model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A worktree's node_modules symlink was committed in e4951b2 because the ignore pattern node_modules/ only matches directories. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…letes A backfill that ran while the conversations table was still empty (a cache that had not replayed its conversation events yet) recorded status 'completed' with 0 conversations, and every later start returned at that row before looking at a single conversation. Measured in a real vault: 53 conversations, 1 indexed, backfill row completed/0/0. - Never record completion on an empty conversations table. - When the row says completed, index the non-branch conversations that have messages and no conversation_embedding_metadata rows (one indexed anti-join; a start with nothing missing costs one query and writes nothing). Vaults already stuck heal on their next start. Resume checkpoint and per-pair idempotency are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The status bar read "Indexing: 0/6644" for about six minutes on every start in a real vault. That was the trace backfill, not notes: 6,660 of the 6,681 un-embedded traces were tool-call traces whose content is "Used tool", which embedTrace() skips as too short, so they never got a row and were re-counted and re-walked (50 ms yield each) on every start, with progress reported only at the start and the end. - TraceIndexer counts only traces that can be embedded and reports progress after each one. - The note pass's content-hash comparison reports a 'checking' phase with notes-compared counts, every 100 notes, and is a running pass that pause and cancel reach. 'indexing' counts only notes being embedded. - Progress events carry a scope (notes, conversations, traces). The status bar says "Checking notes", "Indexing chats", "Indexing memory", and the completion notice no longer calls traces or chats notes. - The semantic search fold shows a "Checking for changes" row and names what chat and memory passes count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reported as semantic search returning the same file several times
('Application_Tracking.md' x3). Read live, those were different files
sharing a basename (one per job-application folder); embedding_metadata
holds one row per notePath, so the index did not produce duplicates.
Guard the result shaping anyway: keep the best-ranked hit per path and keep
filling up to limit with distinct files. Tests cover both the collapse and
that same-basename files stay separate results.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n data.json The scheduler ticks every minute on every device and saved workflowScheduler.lastCheckAt into data.json each time, so Obsidian Sync uploaded data.json about once a minute per device and every other device reloaded and reconciled it. The watermark is per-device bookkeeping (runKey prevents duplicate runs across devices), so it now lives in vault-scoped localStorage. A value older versions left in data.json is honoured as a floor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Redesigns Settings → Nexus → Defaults and the in-chat Chat settings modal, rebuilds Get started as in-tab pages, makes semantic search controllable at runtime, and fixes a set of search, storage and sync bugs found along the way (several also affect 5.20.x).
Visual contract:
docs/mockups/defaults-settings-essentials.html(accepted 2026-10-14). Plan:docs/plans/defaults-settings-redesign-plan.md(see "Owner decisions").Settings redesign
SettingGroup/Settingcomponents; folds use the shared, restyledAccordion. Fold state is device-local.supportsSamplingParams: false), context window is a slider that snaps to 32K–1M presets up to the model's max.executePromptrequests (previously mostly ignored). Thinking is gated at the request boundary for non-reasoning models.minAppVersion→ 1.11.0 (SettingGroup).Get started
nodefallback), OS-correct absolute paths, one-click connect for Claude Desktop, Claude Code (claude mcp add-json --scope user), Codex and Antigravity (~/.gemini/config/mcp_config.json, replaces "Gemini CLI"), and a four-step "Other agents" flow with a copyable config and setup prompt. Reconnect preserves other config keys; unreadable configs are backed up.Semantic search
Bug fixes (several affect released versions)
embeddingServicewas never registered aftere1eada3a; all five lookups replaced withresolveEmbeddingService().workspace_createdevents that reset edits on cache rebuild. Creation is now idempotent; replay ignores duplicates. (5.17.0–5.20.x)requireloaders no longer bind to whichever window has focus.stateManagerlookup; semantic results one per file.Test plan
npm run build(schemas check, lint incl. mobile import check, tsc, esbuild, connector)🤖 Generated with Claude Code