Repository: logancyang/obsidian-copilot

Issue:
the model editing menu is positioned incorrectly
- [x] Disable all other plugins besides Copilot **(required)**
- [x] Screenshot of note + Copilot chat pane + dev console added **(required)**

Copilot version:

(Bug report without the above will be closed)

**Describe how to reproduce**
A clear and concise description of what the bug is. Clear steps to reproduce the behavior

**Expected behavior**
A clear and concise description of what you expected to happen.

**Screenshots**
Add screenshots to help explain your problem. Please turn on debug mode in Copilot settings, turn off other plugins to leave only Copilot dev messages as necessary.

**Additional context**
Add any other context about the problem here.


Discussion:
@vervolk: <img width="1910" height="1038" alt="Image" src="https://github.com/user-attachments/assets/a0c28d6c-fe45-44ed-a675-331b93f9b595" />
all other community plugins is disabled
opbsidian-options-copilot settings- model - click three point  and editing menu positioned wrong place, some times out the visible screen area@logancyang: cc @Emt-lin @zeroliu 

Solution patch:
diff --git a/AGENTS.md b/AGENTS.md
index 2783994..04103e5 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -109,7 +109,7 @@ Copilot for Obsidian is an AI-powered assistant plugin that integrates various L
 
 ## Message Management Architecture
 
-For detailed architecture diagrams and documentation, see [`MESSAGE_ARCHITECTURE.md`](./MESSAGE_ARCHITECTURE.md).
+For detailed architecture diagrams and documentation, see [`MESSAGE_ARCHITECTURE.md`](./docs/MESSAGE_ARCHITECTURE.md).
 
 ### Core Classes and Flow
 
@@ -258,7 +258,7 @@ The TODO.md should be:
 - Settings are versioned - migrations may be needed
 - Local model support available via Ollama/LM Studio
 - Rate limiting is implemented for all API calls
-- For technical debt and known issues, see [`TECHDEBT.md`](./TECHDEBT.md)
+- For technical debt and known issues, see [`TECHDEBT.md`](./docs/TECHDEBT.md)
 - For current development session planning, see [`TODO.md`](./TODO.md)
 
 ### Obsidian Plugin Environment
diff --git a/CLAUDE.md b/CLAUDE.md
index dc041dd..e354805 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -109,7 +109,7 @@ Copilot for Obsidian is an AI-powered assistant plugin that integrates various L
 
 ## Message Management Architecture
 
-For detailed architecture diagrams and documentation, see [`MESSAGE_ARCHITECTURE.md`](./MESSAGE_ARCHITECTURE.md).
+For detailed architecture diagrams and documentation, see [`MESSAGE_ARCHITECTURE.md`](./docs/MESSAGE_ARCHITECTURE.md).
 
 ### Core Classes and Flow
 
@@ -258,7 +258,7 @@ The TODO.md should be:
 - Settings are versioned - migrations may be needed
 - Local model support available via Ollama/LM Studio
 - Rate limiting is implemented for all API calls
-- For technical debt and known issues, see [`TECHDEBT.md`](./TECHDEBT.md)
+- For technical debt and known issues, see [`TECHDEBT.md`](./docs/TECHDEBT.md)
 - For current development session planning, see [`TODO.md`](./TODO.md)
 
 ### Obsidian Plugin Environment
diff --git a/docs/CITATION_IMPLEMENTATION.md b/docs/CITATION_IMPLEMENTATION.md
new file mode 100644
index 0000000..1c4b6d5
--- /dev/null
+++ b/docs/CITATION_IMPLEMENTATION.md
@@ -0,0 +1,48 @@
+# Inline Citation System
+
+This guide explains how inline citations are produced across Copilot Plus, Vault QA, and web search, and how the feature is exercised by automated tests.
+
+## Feature Toggle & Surface Area
+
+- `enableInlineCitations` (default `true`) lives in `src/settings/model.ts` and is exposed in the QA settings UI (`src/settings/v2/components/QASettings.tsx`).
+- The toggle gates prompt instructions, fallback post-processing, and chat rendering. When disabled the system falls back to a collapsible sources list without inline markers.
+
+## Pipeline Overview
+
+1. **Retrieval Conditioning**
+   - Both `CopilotPlusChainRunner.prepareLocalSearchResult` and `VaultQAChainRunner` sanitize note content with `sanitizeContentForCitations` to strip stray `[^n]`/`[n]` markers before prompting.
+   - Retrieved notes receive stable `__sourceId` values and are serialized with `formatSearchResultsForLLM`; `deduplicateSources` keeps the highest-scoring entry per path/title.
+   - A compact source catalog is built via `formatSourceCatalog`, and Copilot Plus caches the first 20 entries in `lastCitationSources` for fallback footnotes.
+2. **Prompt Assembly**
+   - `CITATION_RULES` and `WEB_CITATION_RULES` live in `src/LLMProviders/chainRunner/utils/citationUtils.ts`.
+   - `getCitationInstructions` (Copilot Plus) and `getQACitationInstructionsConditional` (Vault QA) append guidance and a source catalog only when inline citations are enabled.
+   - Web search calls `getWebSearchCitationInstructions` so external sources emit `[title](url)` definitions while vault answers stay on `[[Note]]` links.
+3. **Response Safeguards**
+   - `addFallbackSources` appends a `#### Sources` block when the model produced inline markers but no definitions. Detection relies on `hasExistingCitations`, which now accepts alternate headings (e.g., `## Sources`, `Sources -`) and `<summary>Sources</summary>` blocks.
+   - Copilot Plus passes structured `lastCitationSources` into the fallback helper; Vault QA derives titles from the retriever output.
+4. **Chat Rendering**
+   - `src/components/chat-components/ChatSingleMessage.tsx` always pipes assistant messages through `processInlineCitations`.
+   - The helper extracts the trailing sources section, builds a first-mention map with `buildCitationMap`, normalizes references (`normalizeCitations`) so constructs like `[^7][^8]` become `[1][2]`, and converts definitions (`convertFootnoteDefinitions`) into clickable wiki links or Markdown anchors.
+   - Duplicate definitions collapse via `consolidateDuplicateSources` + `updateCitationsForConsolidation`, keeping numbering stable. When the sources block is not footnote formatted or citations are disabled, the renderer falls back to a simple `<details>` list.
+
+## Testing
+
+- `src/LLMProviders/chainRunner/utils/citationUtils.test.ts`
+  - Sanitization, catalog formatting, and fallback insertion.
+  - `hasExistingCitations` coverage for markdown headings, plain `Sources` labels, and `<summary>` wrappers.
+  - Regression suites for non-sequential citations, duplicate source consolidation, and consecutive markers (`[^7][^8]`).
+- `src/LLMProviders/chainRunner/utils/searchResultUtils.test.ts`
+  - Ensures retrieved documents are serialized with stable IDs and filtered for `includeInContext` before prompting.
+- `src/tools/ToolResultFormatter.test.ts`
+  - Verifies the local search tool emits JSON with the `{ type: "local_search", documents: [...] }` shape expected by the chain runners.
+
+## Manual QA Checklist
+
+- Vault QA turn using only local search: confirm inline `[1]` markers and numbered sources render without duplication.
+- Mixed Copilot Plus turn (local search + another tool): ensure fallback still works if the model omits the sources block.
+- Web search answer: verify footnote definitions render as `[title](url)` links when citations are enabled.
+
+## Watchlist
+
+- `sanitizeContentForCitations` intentionally strips bracketed numbers; keep an eye on domains (math, law) where literal `[1990]` values might be desirable.
+- Inline citations remain model-dependent. `addFallbackSources` guarantees a sources list, but the UI still reflects whatever inline markers the provider returns.
diff --git a/MESSAGE_ARCHITECTURE.md b/docs/MESSAGE_ARCHITECTURE.md
similarity index 100%
rename from MESSAGE_ARCHITECTURE.md
rename to docs/MESSAGE_ARCHITECTURE.md
diff --git a/TECHDEBT.md b/docs/TECHDEBT.md
similarity index 100%
rename from TECHDEBT.md
rename to docs/TECHDEBT.md
diff --git a/package-lock.json b/package-lock.json
index 0559c25..50aedef 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5506,18 +5506,18 @@
       }
     },
     "node_modules/@radix-ui/react-dropdown-menu": {
-      "version": "2.1.4",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.4.tgz",
-      "integrity": "sha512-iXU1Ab5ecM+yEepGAWK8ZhMyKX4ubFdCNtol4sT9D0OVErG9PNElfx3TQhjw7n7BC5nFVz68/5//clWy+8TXzA==",
+      "version": "2.1.16",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz",
+      "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==",
       "license": "MIT",
       "dependencies": {
-        "@radix-ui/primitive": "1.1.1",
-        "@radix-ui/react-compose-refs": "1.1.1",
-        "@radix-ui/react-context": "1.1.1",
-        "@radix-ui/react-id": "1.1.0",
-        "@radix-ui/react-menu": "2.1.4",
-        "@radix-ui/react-primitive": "2.0.1",
-        "@radix-ui/react-use-controllable-state": "1.1.0"
+        "@radix-ui/primitive": "1.1.3",
+        "@radix-ui/react-compose-refs": "1.1.2",
+        "@radix-ui/react-context": "1.1.2",
+        "@radix-ui/react-id": "1.1.1",
+        "@radix-ui/react-menu": "2.1.16",
+        "@radix-ui/react-primitive": "2.1.3",
+        "@radix-ui/react-use-controllable-state": "1.2.2"
       },
       "peerDependencies": {
         "@types/react": "*",
@@ -5535,16 +5535,49 @@
       }
     },
     "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/primitive": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
-      "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==",
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+      "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
       "license": "MIT"
     },
     "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-compose-refs": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+      "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-context": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+      "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-id": {
       "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
-      "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
+      "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
       "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-use-layout-effect": "1.1.1"
+      },
       "peerDependencies": {
         "@types/react": "*",
         "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
@@ -5556,12 +5589,12 @@
       }
     },
     "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-primitive": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
-      "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+      "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
       "license": "MIT",
       "dependencies": {
-        "@radix-ui/react-slot": "1.1.1"
+        "@radix-ui/react-slot": "1.2.3"
       },
       "peerDependencies": {
         "@types/react": "*",
@@ -5578,13 +5611,14 @@
         }
       }
     },
-    "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-slot": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
-      "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
+    "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-controllable-state": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+      "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
       "license": "MIT",
       "dependencies": {
-        "@radix-ui/react-compose-refs": "1.1.1"
+        "@radix-ui/react-use-effect-event": "0.0.2",
+        "@radix-ui/react-use-layout-effect": "1.1.1"
       },
       "peerDependencies": {
         "@types/react": "*",
@@ -5596,6 +5630,21 @@
         }
       }
     },
+    "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-use-layout-effect": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+      "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@radix-ui/react-focus-guards": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz",
@@ -5675,29 +5724,29 @@
       }
     },
     "node_modules/@radix-ui/react-menu": {
-      "version": "2.1.4",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.4.tgz",
-      "integrity": "sha512-BnOgVoL6YYdHAG6DtXONaR29Eq4nvbi8rutrV/xlr3RQCMMb3yqP85Qiw/3NReozrSW+4dfLkK+rc1hb4wPU/A==",
+      "version": "2.1.16",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz",
+      "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==",
       "license": "MIT",
       "dependencies": {
-        "@radix-ui/primitive": "1.1.1",
-        "@radix-ui/react-collection": "1.1.1",
-        "@radix-ui/react-compose-refs": "1.1.1",
-        "@radix-ui/react-context": "1.1.1",
-        "@radix-ui/react-direction": "1.1.0",
-        "@radix-ui/react-dismissable-layer": "1.1.3",
-        "@radix-ui/react-focus-guards": "1.1.1",
-        "@radix-ui/react-focus-scope": "1.1.1",
-        "@radix-ui/react-id": "1.1.0",
-        "@radix-ui/react-popper": "1.2.1",
-        "@radix-ui/react-portal": "1.1.3",
-        "@radix-ui/react-presence": "1.1.2",
-        "@radix-ui/react-primitive": "2.0.1",
-        "@radix-ui/react-roving-focus": "1.1.1",
-        "@radix-ui/react-slot": "1.1.1",
-        "@radix-ui/react-use-callback-ref": "1.1.0",
-        "aria-hidden": "^1.1.1",
-        "react-remove-scroll": "^2.6.1"
+        "@radix-ui/primitive": "1.1.3",
+        "@radix-ui/react-collection": "1.1.7",
+        "@radix-ui/react-compose-refs": "1.1.2",
+        "@radix-ui/react-context": "1.1.2",
+        "@radix-ui/react-direction": "1.1.1",
+        "@radix-ui/react-dismissable-layer": "1.1.11",
+        "@radix-ui/react-focus-guards": "1.1.3",
+        "@radix-ui/react-focus-scope": "1.1.7",
+        "@radix-ui/react-id": "1.1.1",
+        "@radix-ui/react-popper": "1.2.8",
+        "@radix-ui/react-portal": "1.1.9",
+        "@radix-ui/react-presence": "1.1.5",
+        "@radix-ui/react-primitive": "2.1.3",
+        "@radix-ui/react-roving-focus": "1.1.11",
+        "@radix-ui/react-slot": "1.2.3",
+        "@radix-ui/react-use-callback-ref": "1.1.1",
+        "aria-hidden": "^1.2.4",
+        "react-remove-scroll": "^2.6.3"
       },
       "peerDependencies": {
         "@types/react": "*",
@@ -5715,18 +5764,18 @@
       }
     },
     "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/primitive": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
-      "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==",
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+      "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
       "license": "MIT"
     },
     "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-arrow": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.1.tgz",
-      "integrity": "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==",
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
+      "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
       "license": "MIT",
       "dependencies": {
-        "@radix-ui/react-primitive": "2.0.1"
+        "@radix-ui/react-primitive": "2.1.3"
       },
       "peerDependencies": {
         "@types/react": "*",
@@ -5744,15 +5793,15 @@
       }
     },
     "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-collection": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz",
-      "integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==",
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
+      "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
       "license": "MIT",
       "dependencies": {
-        "@radix-ui/react-compose-refs": "1.1.1",
-        "@radix-ui/react-context": "1.1.1",
-        "@radix-ui/react-primitive": "2.0.1",
-        "@radix-ui/react-slot": "1.1.1"
+        "@radix-ui/react-compose-refs": "1.1.2",
+        "@radix-ui/react-context": "1.1.2",
+        "@radix-ui/react-primitive": "2.1.3",
+        "@radix-ui/react-slot": "1.2.3"
       },
       "peerDependencies": {
         "@types/react": "*",
@@ -5770,9 +5819,39 @@
       }
     },
     "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-compose-refs": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+      "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-context": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+      "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-direction": {
       "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
-      "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
+      "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
       "license": "MIT",
       "peerDependencies": {
         "@types/react": "*",
@@ -5785,16 +5864,16 @@
       }
     },
     "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-dismissable-layer": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.3.tgz",
-      "integrity": "sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==",
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
+      "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
       "license": "MIT",
       "dependencies": {
-        "@radix-ui/primitive": "1.1.1",
-        "@radix-ui/react-compose-refs": "1.1.1",
-        "@radix-ui/react-primitive": "2.0.1",
-        "@radix-ui/react-use-callback-ref": "1.1.0",
-        "@radix-ui/react-use-escape-keydown": "1.1.0"
+        "@radix-ui/primitive": "1.1.3",
+        "@radix-ui/react-compose-refs": "1.1.2",
+        "@radix-ui/react-primitive": "2.1.3",
+        "@radix-ui/react-use-callback-ref": "1.1.1",
+        "@radix-ui/react-use-escape-keydown": "1.1.1"
       },
       "peerDependencies": {
         "@types/react": "*",
@@ -5811,15 +5890,30 @@
         }
       }
     },
+    "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-guards": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz",
+      "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-focus-scope": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz",
-      "integrity": "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==",
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
+      "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
       "license": "MIT",
       "dependencies": {
-        "@radix-ui/react-compose-refs": "1.1.1",
-        "@radix-ui/react-primitive": "2.0.1",
-        "@radix-ui/react-use-callback-ref": "1.1.0"
+        "@radix-ui/react-compose-refs": "1.1.2",
+        "@radix-ui/react-primitive": "2.1.3",
+        "@radix-ui/react-use-callback-ref": "1.1.1"
       },
       "peerDependencies": {
         "@types/react": "*",
@@ -5836,22 +5930,40 @@
         }
       }
     },
+    "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-id": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
+      "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-use-layout-effect": "1.1.1"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-popper": {
-      "version": "1.2.1",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.1.tgz",
-      "integrity": "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==",
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
+      "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==",
       "license": "MIT",
       "dependencies": {
         "@floating-ui/react-dom": "^2.0.0",
-        "@radix-ui/react-arrow": "1.1.1",
-        "@radix-ui/react-compose-refs": "1.1.1",
-        "@radix-ui/react-context": "1.1.1",
-        "@radix-ui/react-primitive": "2.0.1",
-        "@radix-ui/react-use-callback-ref": "1.1.0",
-        "@radix-ui/react-use-layout-effect": "1.1.0",
-        "@radix-ui/react-use-rect": "1.1.0",
-        "@radix-ui/react-use-size": "1.1.0",
-        "@radix-ui/rect": "1.1.0"
+        "@radix-ui/react-arrow": "1.1.7",
+        "@radix-ui/react-compose-refs": "1.1.2",
+        "@radix-ui/react-context": "1.1.2",
+        "@radix-ui/react-primitive": "2.1.3",
+        "@radix-ui/react-use-callback-ref": "1.1.1",
+        "@radix-ui/react-use-layout-effect": "1.1.1",
+        "@radix-ui/react-use-rect": "1.1.1",
+        "@radix-ui/react-use-size": "1.1.1",
+        "@radix-ui/rect": "1.1.1"
       },
       "peerDependencies": {
         "@types/react": "*",
@@ -5869,13 +5981,37 @@
       }
     },
     "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-portal": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz",
-      "integrity": "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==",
+      "version": "1.1.9",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
+      "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
       "license": "MIT",
       "dependencies": {
-        "@radix-ui/react-primitive": "2.0.1",
-        "@radix-ui/react-use-layout-effect": "1.1.0"
+        "@radix-ui/react-primitive": "2.1.3",
+        "@radix-ui/react-use-layout-effect": "1.1.1"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "@types/react-dom": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+        "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "@types/react-dom": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-presence": {
+      "version": "1.1.5",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
+      "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-compose-refs": "1.1.2",
+        "@radix-ui/react-use-layout-effect": "1.1.1"
       },
       "peerDependencies": {
         "@types/react": "*",
@@ -5893,12 +6029,12 @@
       }
     },
     "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-primitive": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
-      "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+      "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
       "license": "MIT",
       "dependencies": {
-        "@radix-ui/react-slot": "1.1.1"
+        "@radix-ui/react-slot": "1.2.3"
       },
       "peerDependencies": {
         "@types/react": "*",
@@ -5915,13 +6051,28 @@
         }
       }
     },
-    "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
+    "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-callback-ref": {
       "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
-      "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
+      "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-escape-keydown": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
+      "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
       "license": "MIT",
       "dependencies": {
-        "@radix-ui/react-compose-refs": "1.1.1"
+        "@radix-ui/react-use-callback-ref": "1.1.1"
       },
       "peerDependencies": {
         "@types/react": "*",
@@ -5933,17 +6084,74 @@
         }
       }
     },
+    "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-layout-effect": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+      "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-rect": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
+      "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/rect": "1.1.1"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-use-size": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
+      "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-use-layout-effect": "1.1.1"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/rect": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
+      "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
+      "license": "MIT"
+    },
     "node_modules/@radix-ui/react-menu/node_modules/react-remove-scroll": {
-      "version": "2.6.2",
-      "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz",
-      "integrity": "sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==",
+      "version": "2.7.1",
+      "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz",
+      "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==",
       "license": "MIT",
       "dependencies": {
         "react-remove-scroll-bar": "^2.3.7",
-        "react-style-singleton": "^2.2.1",
+        "react-style-singleton": "^2.2.3",
         "tslib": "^2.1.0",
         "use-callback-ref": "^1.3.3",
-        "use-sidecar": "^1.1.2"
+        "use-sidecar": "^1.1.3"
       },
       "engines": {
         "node": ">=10"
@@ -6423,20 +6631,20 @@
       }
     },
     "node_modules/@radix-ui/react-roving-focus": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz",
-      "integrity": "sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==",
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
+      "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
       "license": "MIT",
       "dependencies": {
-        "@radix-ui/primitive": "1.1.1",
-        "@radix-ui/react-collection": "1.1.1",
-        "@radix-ui/react-compose-refs": "1.1.1",
-        "@radix-ui/react-context": "1.1.1",
-        "@radix-ui/react-direction": "1.1.0",
-        "@radix-ui/react-id": "1.1.0",
-        "@radix-ui/react-primitive": "2.0.1",
-        "@radix-ui/react-use-callback-ref": "1.1.0",
-        "@radix-ui/react-use-controllable-state": "1.1.0"
+        "@radix-ui/primitive": "1.1.3",
+        "@radix-ui/react-collection": "1.1.7",
+        "@radix-ui/react-compose-refs": "1.1.2",
+        "@radix-ui/react-context": "1.1.2",
+        "@radix-ui/react-direction": "1.1.1",
+        "@radix-ui/react-id": "1.1.1",
+        "@radix-ui/react-primitive": "2.1.3",
+        "@radix-ui/react-use-callback-ref": "1.1.1",
+        "@radix-ui/react-use-controllable-state": "1.2.2"
       },
       "peerDependencies": {
         "@types/react": "*",
@@ -6454,21 +6662,21 @@
       }
     },
     "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/primitive": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
-      "integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==",
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+      "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
       "license": "MIT"
     },
     "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-collection": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz",
-      "integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==",
+      "version": "1.1.7",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
+      "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
       "license": "MIT",
       "dependencies": {
-        "@radix-ui/react-compose-refs": "1.1.1",
-        "@radix-ui/react-context": "1.1.1",
-        "@radix-ui/react-primitive": "2.0.1",
-        "@radix-ui/react-slot": "1.1.1"
+        "@radix-ui/react-compose-refs": "1.1.2",
+        "@radix-ui/react-context": "1.1.2",
+        "@radix-ui/react-primitive": "2.1.3",
+        "@radix-ui/react-slot": "1.2.3"
       },
       "peerDependencies": {
         "@types/react": "*",
@@ -6486,10 +6694,58 @@
       }
     },
     "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-compose-refs": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+      "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-context": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+      "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-direction": {
       "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
-      "integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
+      "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-id": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
+      "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
       "license": "MIT",
+      "dependencies": {
+        "@radix-ui/react-use-layout-effect": "1.1.1"
+      },
       "peerDependencies": {
         "@types/react": "*",
         "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
@@ -6501,12 +6757,12 @@
       }
     },
     "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-primitive": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
-      "integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+      "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
       "license": "MIT",
       "dependencies": {
-        "@radix-ui/react-slot": "1.1.1"
+        "@radix-ui/react-slot": "1.2.3"
       },
       "peerDependencies": {
         "@types/react": "*",
@@ -6523,13 +6779,29 @@
         }
       }
     },
-    "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-slot": {
+    "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-use-callback-ref": {
       "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
-      "integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
+      "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-use-controllable-state": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+      "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
       "license": "MIT",
       "dependencies": {
-        "@radix-ui/react-compose-refs": "1.1.1"
+        "@radix-ui/react-use-effect-event": "0.0.2",
+        "@radix-ui/react-use-layout-effect": "1.1.1"
       },
       "peerDependencies": {
         "@types/react": "*",
@@ -6541,6 +6813,21 @@
         }
       }
     },
+    "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-use-layout-effect": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+      "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@radix-ui/react-scroll-area": {
       "version": "1.2.9",
       "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.9.tgz",
@@ -21540,9 +21827,10 @@
       }
     },
     "node_modules/use-sidecar": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
-      "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==",
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
+      "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+      "license": "MIT",
       "dependencies": {
         "detect-node-es": "^1.1.0",
         "tslib": "^2.0.0"
@@ -21551,8 +21839,8 @@
         "node": ">=10"
       },
       "peerDependencies": {
-        "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0",
-        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+        "@types/react": "*",
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
       },
       "peerDependenciesMeta": {
         "@types/react": {
diff --git a/src/LLMProviders/chainRunner/BaseChainRunner.ts b/src/LLMProviders/chainRunner/BaseChainRunner.ts
index 10b0beb..2b410a6 100644
--- a/src/LLMProviders/chainRunner/BaseChainRunner.ts
+++ b/src/LLMProviders/chainRunner/BaseChainRunner.ts
@@ -83,8 +83,8 @@ export abstract class BaseChainRunner implements ChainRunner {
       turns: Array.isArray(historyMessages) ? historyMessages.length : 0,
     });
 
+    const MAX_LOG_LENGTH = 2000;
     try {
-      const MAX = 300;
       const { parseToolCallMarkers } = await import("./utils/toolCallParser");
       const parsed = parseToolCallMarkers(fullAIResponse);
       let textOnly = parsed.segments
@@ -92,13 +92,16 @@ export abstract class BaseChainRunner implements ChainRunner {
         .join("")
         .trim();
       if (!textOnly) textOnly = fullAIResponse || "";
-      const snippet = textOnly.length > MAX ? textOnly.slice(0, MAX) + "... (truncated)" : textOnly;
+      const snippet =
+        textOnly.length > MAX_LOG_LENGTH
+          ? textOnly.slice(0, MAX_LOG_LENGTH) + "... (truncated)"
+          : textOnly;
       logInfo("Final AI response (truncated):\n", snippet);
     } catch {
       // Fallback: truncate raw response without parsing
-      const MAX = 300;
       const s = typeof fullAIResponse === "string" ? fullAIResponse : String(fullAIResponse ?? "");
-      const clipped = s.length > MAX ? s.slice(0, MAX) + "... (truncated)" : s;
+      const clipped =
+        s.length > MAX_LOG_LENGTH ? s.slice(0, MAX_LOG_LENGTH) + "... (truncated)" : s;
       logInfo("Final AI response (truncated):\n", clipped);
     }
     return fullAIResponse;
diff --git a/src/LLMProviders/chainRunner/CopilotPlusChainRunner.ts b/src/LLMProviders/chainRunner/CopilotPlusChainRunner.ts
index ce59997..3337665 100644
--- a/src/LLMProviders/chainRunner/CopilotPlusChainRunner.ts
+++ b/src/LLMProviders/chainRunner/CopilotPlusChainRunner.ts
@@ -28,6 +28,13 @@ import {
 import { BaseChatModel } from "@langchain/core/language_models/chat_models";
 import { COPILOT_TOOL_NAMES, IntentAnalyzer } from "../intentAnalyzer";
 import { BaseChainRunner } from "./BaseChainRunner";
+import {
+  formatSourceCatalog,
+  getCitationInstructions,
+  sanitizeContentForCitations,
+  addFallbackSources,
+  type SourceCatalogEntry,
+} from "./utils/citationUtils";
 import { ActionBlockStreamer } from "./utils/ActionBlockStreamer";
 import {
   addChatHistoryToMessages,
@@ -488,8 +495,7 @@ export class CopilotPlusChainRunner extends BaseChainRunner {
       // Enhance with ALL tool outputs including localSearch
       let enhancedUserMessage = this.prepareEnhancedUserMessage(
         questionForEnhancement,
-        toolOutputs,
-        toolCalls
+        toolOutputs
       );
 
       // If localSearch has actual results and no other tools, add QA-style instruction to maintain same behavior
@@ -538,6 +544,19 @@ export class CopilotPlusChainRunner extends BaseChainRunner {
       fullAIResponse = currentPartialResponse;
     }
 
+    // Add fallback sources if citations are enabled and missing
+    const settings = getSettings();
+    const fallbackSources =
+      this.lastCitationSources && this.lastCitationSources.length > 0
+        ? this.lastCitationSources
+        : ((sources as any[]) || []).map((source) => ({ title: source.title, path: source.path }));
+
+    fullAIResponse = addFallbackSources(
+      fullAIResponse,
+      fallbackSources,
+      settings.enableInlineCitations
+    );
+
     return this.handleResponse(
       fullAIResponse,
       userMessage,
@@ -632,7 +651,10 @@ export class CopilotPlusChainRunner extends BaseChainRunner {
     return { toolOutputs, sources: deduplicateSources(allSources) };
   }
 
-  private prepareEnhancedUserMessage(userMessage: string, toolOutputs: any[], toolCalls?: any[]) {
+  // Persist citation lines built for this turn to reuse in fallback
+  private lastCitationSources: { title?: string; path?: string }[] | null = null;
+
+  private prepareEnhancedUserMessage(userMessage: string, toolOutputs: any[]) {
     let context = "";
     let hasLocalSearchWithResults = false;
 
@@ -642,13 +664,9 @@ export class CopilotPlusChainRunner extends BaseChainRunner {
     );
 
     if (localSearchOutput && typeof localSearchOutput.output === "string") {
-      try {
-        const documents = JSON.parse(localSearchOutput.output);
-        if (Array.isArray(documents) && documents.length > 0) {
-          hasLocalSearchWithResults = true;
-        }
-      } catch {
-        // Invalid JSON or parsing error
+      // Detect presence via XML content
+      if (/<document>/.test(localSearchOutput.output)) {
+        hasLocalSearchWithResults = true;
       }
     }
 
@@ -725,13 +743,45 @@ export class CopilotPlusChainRunner extends BaseChainRunner {
       }));
     }
 
-    // Format documents with essential metadata
-    const formattedContent = formatSearchResultsForLLM(processedDocs);
+    // Sanitize content to remove any pre-existing citation markers to prevent number leakage
+
+    // Assign stable source ids and sanitize content
+    const withIds = processedDocs.map((doc, idx) => ({
+      ...doc,
+      __sourceId: idx + 1,
+      content: sanitizeContentForCitations(doc.content || ""),
+    }));
+
+    // Format documents with essential metadata including source id
+    const formattedContent = formatSearchResultsForLLM(withIds);
+
+    // Build a compact, unnumbered source catalog to avoid bias
+    const sourceEntries: SourceCatalogEntry[] = withIds
+      .slice(0, Math.min(20, withIds.length))
+      .map((d: any) => ({
+        title: d.title || d.path || "Untitled",
+        path: d.path || d.title || "",
+      }));
+    const catalogLines = formatSourceCatalog(sourceEntries);
+
+    // Also keep a numbered mapping for fallback use only (if model emits footnotes but forgets Sources)
+    this.lastCitationSources = withIds
+      .slice(0, Math.min(20, withIds.length))
+      .map((d: any, i: number) => {
+        const title = d.title || d.path || "Untitled";
+        return {
+          title,
+          path: d.path || undefined,
+        };
+      });
+
+    const settings = getSettings();
+    const guidance = getCitationInstructions(settings.enableInlineCitations, catalogLines);
 
     // Wrap in XML-like tags for better LLM understanding
     return timeExpression
-      ? `<localSearch timeRange="${timeExpression}">\n${formattedContent}\n</localSearch>`
-      : `<localSearch>\n${formattedContent}\n</localSearch>`;
+      ? `<localSearch timeRange="${timeExpression}">\n${formattedContent}${guidance}\n</localSearch>`
+      : `<localSearch>\n${formattedContent}${guidance}\n</localSearch>`;
   }
 
   /**
@@ -756,7 +806,14 @@ export class CopilotPlusChainRunner extends BaseChainRunner {
     }
 
     try {
-      const searchResults = JSON.parse(toolResult.result);
+      const parsed = JSON.parse(toolResult.result);
+      const searchResults =
+        parsed &&
+        typeof parsed === "object" &&
+        parsed.type === "local_search" &&
+        Array.isArray(parsed.documents)
+          ? parsed.documents
+          : null;
       if (!Array.isArray(searchResults)) {
         formattedForLLM = "<localSearch>\nInvalid search results format.\n</localSearch>";
         return { formattedForLLM, sources };
@@ -768,7 +825,7 @@ export class CopilotPlusChainRunner extends BaseChainRunner {
       // Extract sources with explanation for UI display
       sources = extractSourcesFromSearchResults(searchResults);
 
-      // Prepare and format results for LLM
+      // Prepare and format results for LLM (include stable ids)
       formattedForLLM = this.prepareLocalSearchResult(searchResults, timeExpression || "");
     } catch (error) {
       logWarn("Failed to parse localSearch results:", error);
diff --git a/src/LLMProviders/chainRunner/VaultQAChainRunner.ts b/src/LLMProviders/chainRunner/VaultQAChainRunner.ts
index 5b98956..7050758 100644
--- a/src/LLMProviders/chainRunner/VaultQAChainRunner.ts
+++ b/src/LLMProviders/chainRunner/VaultQAChainRunner.ts
@@ -11,6 +11,13 @@ import {
   withSuppressedTokenWarnings,
 } from "@/utils";
 import { BaseChainRunner } from "./BaseChainRunner";
+import {
+  formatSourceCatalog,
+  getQACitationInstructionsConditional,
+  sanitizeContentForCitations,
+  addFallbackSources,
+  type SourceCatalogEntry,
+} from "./utils/citationUtils";
 import { ThinkBlockStreamer } from "./utils/ThinkBlockStreamer";
 
 export class VaultQAChainRunner extends BaseChainRunner {
@@ -69,11 +76,14 @@ export class VaultQAChainRunner extends BaseChainRunner {
       this.chainManager.storeRetrieverDocuments(retrievedDocs);
 
       // Format documents as context with XML tags
+      // Sanitize content to remove pre-existing citation markers
+
       const context = retrievedDocs
-        .map(
-          (doc: any) =>
-            `<${RETRIEVED_DOCUMENT_TAG}>\n${doc.pageContent}\n</${RETRIEVED_DOCUMENT_TAG}>`
-        )
+        .map((doc: any) => {
+          const title = doc.metadata?.title || "Untitled";
+          const path = doc.metadata?.path || title;
+          return `<${RETRIEVED_DOCUMENT_TAG}>\n<title>${title}</title>\n<path>${path}</path>\n<content>\n${sanitizeContentForCitations(doc.pageContent)}\n</content>\n</${RETRIEVED_DOCUMENT_TAG}>`;
+        })
         .join("\n\n");
 
       // Create messages array
@@ -81,9 +91,21 @@ export class VaultQAChainRunner extends BaseChainRunner {
 
       // Add system message with QA instruction
       const systemPrompt = getSystemPrompt();
+      // Prepare citation mapping so the model can produce inline [#] and a Sources footer
+      // Build an unnumbered source catalog to avoid biasing the model with arbitrary numbers
+      // The model is instructed to assign [1..N] as it cites sources in the answer.
+      const sourceEntries: SourceCatalogEntry[] = retrievedDocs
+        .slice(0, Math.max(5, Math.min(20, retrievedDocs.length)))
+        .map((d: any) => ({
+          title: d.metadata?.title || d.metadata?.path || "Untitled",
+          path: d.metadata?.path || d.metadata?.title || "",
+        }));
+      const sourceCatalog = formatSourceCatalog(sourceEntries).join("\n");
+
       const qaInstructions =
-        "\n\nAnswer the question with as detailed as possible based only on the following context:\n" +
-        context;
+        "\n\nAnswer the question based only on the following context:\n" +
+        context +
+        getQACitationInstructionsConditional(settings.enableInlineCitations, sourceCatalog);
       const fullSystemMessage = systemPrompt + qaInstructions;
 
       const chatModel = this.chainManager.chatModelManager.getChatModel();
@@ -168,11 +190,10 @@ export class VaultQAChainRunner extends BaseChainRunner {
   }
 
   private addSourcestoResponse(response: string): string {
-    const docTitles = extractUniqueTitlesFromDocs(this.chainManager.getRetrievedDocuments());
-    if (docTitles.length > 0) {
-      const links = docTitles.map((title) => `- [[${title}]]`).join("\n");
-      response += "\n\n#### Sources:\n\n" + links;
-    }
-    return response;
+    const settings = getSettings();
+    const retrievedDocs = this.chainManager.getRetrievedDocuments();
+    const sources = extractUniqueTitlesFromDocs(retrievedDocs).map((title) => ({ title }));
+
+    return addFallbackSources(response, sources, settings.enableInlineCitations);
   }
 }
diff --git a/src/LLMProviders/chainRunner/utils/citationUtils.test.ts b/src/LLMProviders/chainRunner/utils/citationUtils.test.ts
new file mode 100644
index 0000000..56f1e6c
--- /dev/null
+++ b/src/LLMProviders/chainRunner/utils/citationUtils.test.ts
@@ -0,0 +1,484 @@
+import {
+  addFallbackSources,
+  formatSourceCatalog,
+  getCitationInstructions,
+  getQACitationInstructions,
+  getVaultCitationGuidance,
+  hasExistingCitations,
+  normalizeCitations,
+  processInlineCitations,
+  sanitizeContentForCitations,
+  type SourceCatalogEntry,
+} from "./citationUtils";
+
+describe("citationUtils", () => {
+  describe("sanitizeContentForCitations", () => {
+    it("should remove footnote references", () => {
+      const input = "This is text with [^1] and [^2] footnotes.";
+      const result = sanitizeContentForCitations(input);
+      expect(result).not.toContain("[^1]");
+      expect(result).not.toContain("[^2]");
+      expect(result).toContain("This is text with");
+      expect(result).toContain("footnotes.");
+    });
+
+    it("should remove numeric citations but preserve markdown links", () => {
+      const input = "Study shows [1] and see [this link](https://example.com) and [2, 3] results.";
+      const result = sanitizeContentForCitations(input);
+      expect(result).not.toContain("[1]");
+      expect(result).not.toContain("[2, 3]");
+      expect(result).toContain("[this link](https://example.com)");
+    });
+
+    it("should remove footnote definition lines", () => {
+      const input = `Content here
+[^1]: Source definition
+More content
+[^2]: Another source`;
+      const result = sanitizeContentForCitations(input);
+      expect(result).not.toContain("[^1]: Source definition");
+      expect(result).not.toContain("[^2]: Another source");
+      expect(result).toContain("Content here");
+      expect(result).toContain("More content");
+    });
+
+    it("should handle edge cases", () => {
+      expect(sanitizeContentForCitations("")).toBe("");
+      expect(sanitizeContentForCitations(null as any)).toBe("");
+      expect(sanitizeContentForCitations(undefined as any)).toBe("");
+    });
+  });
+
+  describe("formatSourceCatalog", () => {
+    it("should format sources with proper wikilink syntax", () => {
+      const sources: SourceCatalogEntry[] = [
+        { title: "Document 1", path: "path/to/doc1.md" },
+        { title: "Document 2", path: "path/to/doc2.md" },
+      ];
+      const result = formatSourceCatalog(sources);
+
+      expect(result).toHaveLength(2);
+      expect(result[0]).toMatch(/^- \[\[.*\]\] \(.*\)$/);
+      expect(result[0]).toContain("Document 1");
+      expect(result[0]).toContain("path/to/doc1.md");
+    });
+
+    it("should handle fallbacks for missing data", () => {
+      const sources: SourceCatalogEntry[] = [
+        { title: "", path: "path/to/doc.md" },
+        { title: "Document", path: "" },
+      ];
+      const result = formatSourceCatalog(sources);
+
+      expect(result[0]).toContain("path/to/doc.md");
+      expect(result[1]).toContain("Document");
+      expect(result).toHaveLength(2);
+    });
+
+    it("should handle empty input", () => {
+      expect(formatSourceCatalog([])).toEqual([]);
+    });
+  });
+
+  describe("hasExistingCitations", () => {
+    it("should detect sources section", () => {
+      const responseWithSources = "Some content\n#### Sources:\n[^1]: [[Doc]]";
+      expect(hasExistingCitations(responseWithSources)).toBe(true);
+
+      const responseWithSourcesColon = "Some content\nSources:\n[^1]: [[Doc]]";
+      expect(hasExistingCitations(responseWithSourcesColon)).toBe(true);
+    });
+
+    it("should detect alternate headings and HTML summaries", () => {
+      const heading = "Content\n## Sources\n[^1]: [[Doc]]";
+      expect(hasExistingCitations(heading)).toBe(true);
+
+      const dashHeading = "Content\nSources -\n[^1]: [[Doc]]";
+      expect(hasExistingCitations(dashHeading)).toBe(true);
+
+      const summary =
+        '<details><summary class="copilot-sources__summary">Sources</summary>List</details>';
+      expect(hasExistingCitations(summary)).toBe(true);
+    });
+
+    it("should detect footnote definitions with wikilinks", () => {
+      const responseWithFootnotes = "Some content\n[^1]: [[Document Name]]";
+      expect(hasExistingCitations(responseWithFootnotes)).toBe(true);
+    });
+
+    it("should detect bare footnote definitions (regression test for duplicate sources)", () => {
+      // This is the format that was causing duplicate sources sections
+      const responseWithBareFootnotes =
+        "Content here\n[^1]: [[How to Make Wealth]]\n[^2]: [[Superlinear Returns]]";
+      expect(hasExistingCitations(responseWithBareFootnotes)).toBe(true);
+
+      const responseWithFootnotesNoWikilinks =
+        "Content here\n[^1]: How to Make Wealth\n[^2]: Superlinear Returns";
+      expect(hasExistingCitations(responseWithFootnotesNoWikilinks)).toBe(true);
+    });
+
+    it("should detect footnotes at start of line with whitespace", () => {
+      const responseWithIndentedFootnotes =
+        "Content here\n   [^1]: [[Document]]\n  [^2]: [[Another]]";
+      expect(hasExistingCitations(responseWithIndentedFootnotes)).toBe(true);
+    });
+
+    it("should NOT detect inline citations", () => {
+      const responseWithInlineCitations = "This is a claim [^1] and another claim [^2].";
+      expect(hasExistingCitations(responseWithInlineCitations)).toBe(false);
+    });
+
+    it("should return false for responses without citations", () => {
+      const responseWithoutCitations = "Just regular content here";
+      expect(hasExistingCitations(responseWithoutCitations)).toBe(false);
+    });
+
+    it("should handle edge cases", () => {
+      expect(hasExistingCitations("")).toBe(false);
+      expect(hasExistingCitations(null as any)).toBe(false);
+      expect(hasExistingCitations(undefined as any)).toBe(false);
+    });
+
+    it("should handle the exact format from user's example", () => {
+      const userExample = `Content here
+
+[^1]: [[How to Make Wealth]]
+[^2]: [[Superlinear Returns]]
+
+#### Sources
+[^1]: [[How to Make Wealth]]
+[^2]: [[Superlinear Returns]]`;
+      expect(hasExistingCitations(userExample)).toBe(true);
+    });
+  });
+
+  describe("getVaultCitationGuidance", () => {
+    it("should format vault citation guidance with source catalog", () => {
+      const sourceCatalog = ["- [[Doc 1]] (path1.md)", "- [[Doc 2]] (path2.md)"];
+      const result = getVaultCitationGuidance(sourceCatalog);
+
+      expect(result).toContain("<guidance>");
+      expect(result).toContain("</guidance>");
+      expect(result).toContain("CITATION RULES:");
+      expect(result).toContain("START with [^1]");
+      expect(result).toContain("Source Catalog (for reference only):");
+      expect(result).toContain("- [[Doc 1]] (path1.md)");
+      expect(result).toContain("- [[Doc 2]] (path2.md)");
+    });
+  });
+
+  describe("getQACitationInstructions", () => {
+    it("should format QA citation instructions with source catalog", () => {
+      const sourceCatalog = "- [[Doc 1]]\n- [[Doc 2]]";
+      const result = getQACitationInstructions(sourceCatalog);
+
+      expect(result).toContain("CITATION RULES:");
+      expect(result).toContain("START with [^1]");
+      expect(result).toContain("Source Catalog (for reference only):");
+      expect(result).toContain("- [[Doc 1]]");
+      expect(result).toContain("- [[Doc 2]]");
+    });
+  });
+
+  describe("processInlineCitations", () => {
+    it("should process inline citations when enabled", () => {
+      const content = `Some content here
+
+#### Sources:
+[^1]: [[Document 1]]
+[^2]: [[Document 2]]`;
+
+      const result = processInlineCitations(content, true);
+      expect(result).toContain("copilot-sources__summary");
+      expect(result).toContain('copilot-sources__index">[1]');
+      expect(result).toContain('copilot-sources__text">[[Document 1]]');
+      expect(result).toContain('copilot-sources__text">[[Document 2]]');
+    });
+
+    it("should use simple expandable list when disabled", () => {
+      const content = `Some content here
+
+#### Sources:
+- [[Document 1]]
+- [[Document 2]]`;
+
+      const result = processInlineCitations(content, false);
+      expect(result).toContain("copilot-sources__summary");
+      expect(result).toContain('copilot-sources__index">[1]');
+      expect(result).toContain('copilot-sources__text">[[Document 1]]');
+      expect(result).toContain('copilot-sources__text">[[Document 2]]');
+    });
+
+    it("should return unchanged content when no sources section", () => {
+      const content = "Just regular content without sources";
+      const result = processInlineCitations(content, true);
+      expect(result).toBe(content);
+    });
+  });
+
+  describe("getCitationInstructions", () => {
+    it("should return citation instructions when enabled", () => {
+      const sourceCatalog = ["- [[Doc 1]] (path1.md)"];
+      const result = getCitationInstructions(true, sourceCatalog);
+
+      expect(result).toContain("CITATION RULES:");
+      expect(result).toContain("<guidance>");
+      expect(result).toContain("Doc 1");
+    });
+
+    it("should return empty string when disabled", () => {
+      const sourceCatalog = ["- [[Doc 1]] (path1.md)"];
+      const result = getCitationInstructions(false, sourceCatalog);
+
+      expect(result).toBe("");
+    });
+  });
+
+  describe("addFallbackSources", () => {
+    it("should add sources when citations enabled and missing", () => {
+      const response = "Some content without sources";
+      const sources = [{ title: "Document 1" }, { title: "Document 2" }];
+
+      const result = addFallbackSources(response, sources, true);
+      expect(result).toContain("#### Sources:");
+      expect(result).toContain("[^1]: [[Document 1]]");
+      expect(result).toContain("[^2]: [[Document 2]]");
+    });
+
+    it("should not add sources when citations disabled", () => {
+      const response = "Some content without sources";
+      const sources = [{ title: "Document 1" }];
+
+      const result = addFallbackSources(response, sources, false);
+      expect(result).toBe(response);
+    });
+
+    it("should not add sources when already present", () => {
+      const response = "Some content\n#### Sources:\n[^1]: [[Existing]]";
+      const sources = [{ title: "Document 1" }];
+
+      const result = addFallbackSources(response, sources, true);
+      expect(result).toBe(response);
+    });
+
+    it("should handle empty sources array", () => {
+      const response = "Some content";
+      const sources: any[] = [];
+
+      const result = addFallbackSources(response, sources, true);
+      expect(result).toBe(response);
+    });
+
+    it("should handle invalid inputs gracefully", () => {
+      expect(addFallbackSources("", [{ title: "Doc" }], true)).toBe("");
+      expect(addFallbackSources(null as any, [{ title: "Doc" }], true)).toBe("");
+      expect(addFallbackSources(undefined as any, [{ title: "Doc" }], true)).toBe("");
+    });
+  });
+
+  describe("normalizeCitations", () => {
+    it("should remove periods after citations to prevent markdown list interpretation", () => {
+      const content = "This is a claim [1]. Another claim [2]. Final text.";
+      const map = new Map([
+        [1, 1],
+        [2, 2],
+      ]);
+
+      const result = normalizeCitations(content, map);
+      expect(result).toBe("This is a claim [1] Another claim [2] Final text.");
+      expect(result).not.toContain("[1].");
+      expect(result).not.toContain("[2].");
+    });
+
+    it("should handle footnote references with periods", () => {
+      const content = "Text with [^1]. More text [^2].";
+      const map = new Map([
+        [1, 1],
+        [2, 2],
+      ]);
+
+      const result = normalizeCitations(content, map);
+      expect(result).toBe("Text with [1] More text [2]");
+    });
+
+    it("should handle multiple citations in brackets", () => {
+      const content = "Text with single [^1] and multiple [^2, ^4] citations.";
+      const map = new Map([
+        [1, 1],
+        [2, 2],
+        [4, 3],
+      ]);
+
+      const result = normalizeCitations(content, map);
+      expect(result).toBe("Text with single [1] and multiple [2, 3] citations.");
+    });
+
+    it("should sort multiple citations in ascending order", () => {
+      const content = "Text with unordered [^6, ^1, ^4] citations.";
+      const map = new Map([
+        [1, 1],
+        [4, 2],
+        [6, 3],
+      ]);
+
+      const result = normalizeCitations(content, map);
+      expect(result).toBe("Text with unordered [1, 2, 3] citations.");
+    });
+  });
+
+  describe("processInlineCitations - citation mapping bugs", () => {
+    it("should handle non-sequential citations correctly (CRITICAL BUG)", () => {
+      // This test demonstrates the CRITICAL bug where citations don't map to correct sources
+      const content = `Here are some notes related to your piano practice:
+
+*   **Piano Lessons:** You've been taking piano lessons at Dolce Arts Studio [^9]. You decided on 45-minute sessions every Wednesday at 7 pm [^1].
+*   **Practice Routines:** You have scheduled piano practice as part of your routine [^2].
+*   **Specific Dates:**
+    *   March 13, 2024: Piano trial lesson at Dolce Arts Studio [^9].
+    *   March 18, 2024: You decided on lesson schedule [^1].
+    *   April 8, 2024: You skipped your piano lesson this week [^18].
+
+#### Sources:
+[^1]: [[2024-03-18]]
+[^2]: [[2024-03-26]]
+[^9]: [[2024-03-13]]
+[^18]: [[2024-04-08]]`;
+
+      const result = processInlineCitations(content, true);
+
+      // CRITICAL: Each citation must map to the correct source
+      // After processing, we expect:
+      // [^9] (first mention) -> [1] -> should map to [[2024-03-13]]
+      // [^1] (second mention) -> [2] -> should map to [[2024-03-18]]
+      // [^2] (third mention) -> [3] -> should map to [[2024-03-26]]
+      // [^18] (fourth mention) -> [4] -> should map to [[2024-04-08]]
+
+      expect(result).toContain(
+        '<span class="copilot-sources__index">[1]</span><span class="copilot-sources__text">[[2024-03-13]]</span>'
+      );
+      expect(result).toContain(
+        '<span class="copilot-sources__index">[2]</span><span class="copilot-sources__text">[[2024-03-18]]</span>'
+      );
+      expect(result).toContain(
+        '<span class="copilot-sources__index">[3]</span><span class="copilot-sources__text">[[2024-03-26]]</span>'
+      );
+      expect(result).toContain(
+        '<span class="copilot-sources__index">[4]</span><span class="copilot-sources__text">[[2024-04-08]]</span>'
+      );
+
+      // Verify the citations in text are renumbered correctly
+      expect(result).toContain("Dolce Arts Studio [1]"); // [^9] -> [1]
+      expect(result).toContain("7 pm [2]"); // [^1] -> [2]
+      expect(result).toContain("your routine [3]"); // [^2] -> [3]
+      expect(result).toContain("April 8, 2024: You skipped your piano lesson this week [4]"); // [^18] -> [4]
+    });
+
+    it("should consolidate duplicate sources from user's real example", () => {
+      // This replicates the exact user scenario with multiple citations to "How to Make Wealth"
+      const content = `Based on the search results from your vault, here's what Paul Graham has said about getting rich:
+
+*   **Creating Wealth:** Paul Graham believes in making money by creating wealth and getting paid for it, which he considers more legitimate and straightforward than other methods like chance, speculation, or inheritance [^1]. He emphasizes that you simply have to do something people want [^1].
+*   **Startups as Wealth Compression:** Graham suggests that a startup is a way to compress your whole working life into a few years [^2]. Instead of working at a low intensity for forty years, you work as hard as you possibly can for four, which pays well in technology where there's a premium for working fast [^2].
+*   **Millions vs. Billions:** He advises against using famous rich people like Bill Gates as examples because they tend to be outliers, and their success often involves a large random factor and luck [^3].
+*   **Superlinear Returns:** Graham highlights the concept of superlinear returns, where performance returns are not linear [^4]. In business, if your product is only half as good as your competitor's, you don't get half as many customers; you get none [^4].
+
+#### Sources:
+[^1]: [[How to Make Wealth]]
+[^2]: [[How to Make Wealth]]
+[^3]: [[How to Make Wealth]]
+[^4]: [[Superlinear Returns]]`;
+
+      const result = processInlineCitations(content, true);
+
+      // Should consolidate the 3 "How to Make Wealth" entries into 1
+      expect(result).toContain(
+        '<span class="copilot-sources__index">[1]</span><span class="copilot-sources__text">[[How to Make Wealth]]</span>'
+      );
+      expect(result).toContain(
+        '<span class="copilot-sources__index">[2]</span><span class="copilot-sources__text">[[Superlinear Returns]]</span>'
+      );
+      expect(result).not.toContain('copilot-sources__index">[3]');
+      expect(result).not.toContain('copilot-sources__index">[4]');
+
+      // Verify periods after citations are removed
+      expect(result).not.toContain("[1].");
+      expect(result).not.toContain("[2].");
+
+      // Verify all citations are properly renumbered
+      expect(result).toContain("inheritance [1] He emphasizes");
+      expect(result).toContain("few years [1] Instead of working");
+      expect(result).toContain("not linear [2] In business");
+    });
+
+    it("should handle complex non-sequential citations with duplicates", () => {
+      // Test with both non-sequential AND duplicate citations
+      const content = `Complex example:
+
+*   First point references [^14] and also [^17].
+*   Second point uses [^3] again.
+*   Third point cites [^14] and [^22].
+*   Final point mentions [^3] once more.
+
+#### Sources:
+[^3]: [[Document A]]
+[^14]: [[Document B]]
+[^17]: [[Document C]]
+[^22]: [[Document B]]`;
+
+      const result = processInlineCitations(content, true);
+
+      // Expected renumbering based on first mention:
+      // [^14] (first mention) -> [1] -> [[Document B]]
+      // [^17] (second mention) -> [2] -> [[Document C]]
+      // [^3] (third mention) -> [3] -> [[Document A]]
+      // [^22] (fourth mention) -> [4] -> [[Document B]] (should consolidate with [^14])
+
+      // After consolidation, should only have 3 unique sources:
+      expect(result).toContain(
+        '<span class="copilot-sources__index">[1]</span><span class="copilot-sources__text">[[Document B]]</span>'
+      );
+      expect(result).toContain(
+        '<span class="copilot-sources__index">[2]</span><span class="copilot-sources__text">[[Document C]]</span>'
+      );
+      expect(result).toContain(
+        '<span class="copilot-sources__index">[3]</span><span class="copilot-sources__text">[[Document A]]</span>'
+      );
+      expect(result).not.toContain('copilot-sources__index">[4]');
+
+      // Verify citations in text point to correct consolidated sources
+      expect(result).toContain("references [1] and also [2]"); // [^14]->[1], [^17]->[2]
+      expect(result).toContain("uses [3] again"); // [^3]->[3]
+      expect(result).toContain("cites [1] and [1]"); // [^14]->[1], [^22]->[1] (consolidated)
+      expect(result).toContain("mentions [3] once more"); // [^3]->[3]
+    });
+
+    it("should handle consecutive citations without spaces (CRITICAL BUG)", () => {
+      // This test replicates the exact user bug with [^7][^8]
+      const content = `Paul Graham's advice on wealth:
+
+- Mindset & long game: rewards in these domains are often superlinear — small advantages compound into huge returns — so follow curiosity, do great work, take multiple shots while you're young, and seek situations where effort compounds (learning, network effects, thresholds). [^7][^8]
+
+#### Sources:
+[^7]: [[Superlinear Returns]]
+[^8]: [[How to Do Great Work]]`;
+
+      const result = processInlineCitations(content, true);
+
+      // CRITICAL: Both consecutive citations must be processed correctly
+      // [^7] (first mention) -> [1] -> [[Superlinear Returns]]
+      // [^8] (second mention) -> [2] -> [[How to Do Great Work]]
+
+      expect(result).toContain(
+        '<span class="copilot-sources__index">[1]</span><span class="copilot-sources__text">[[Superlinear Returns]]</span>'
+      );
+      expect(result).toContain(
+        '<span class="copilot-sources__index">[2]</span><span class="copilot-sources__text">[[How to Do Great Work]]</span>'
+      );
+
+      // CRITICAL: Both citations in text must be converted (not partial like [4][^8])
+      expect(result).toContain("thresholds). [1][2]"); // [^7][^8] -> [1][2]
+      expect(result).not.toContain("[^8]"); // Should not contain any unconverted citations
+      expect(result).not.toContain("[^7]"); // Should not contain any unconverted citations
+    });
+  });
+});
diff --git a/src/LLMProviders/chainRunner/utils/citationUtils.ts b/src/LLMProviders/chainRunner/utils/citationUtils.ts
new file mode 100644
index 0000000..e7211fa
--- /dev/null
+++ b/src/LLMProviders/chainRunner/utils/citationUtils.ts
@@ -0,0 +1,558 @@
+/**
+ * Citation utilities for consistent citation behavior across the application.
+ * Handles citation rules, content sanitization, and source formatting.
+ */
+
+// ===== CITATION RULES =====
+
+export const CITATION_RULES = `CITATION RULES:
+1. START with [^1] and increment sequentially ([^1], [^2], [^3], etc.) with NO gaps
+2. BE SELECTIVE: ONLY cite when introducing NEW factual claims, specific data, or direct quotes from sources
+3. IMPORTANT: Do NOT cite every sentence or bullet point. This creates clutter and poor readability.
+4. DO NOT cite for:
+   - General knowledge or common facts
+   - Your own analysis or synthesis
+   - Transitional or concluding statements
+   - Every single sentence (AVOID CITATION CLUTTER - aim for 1-3 citations per paragraph maximum)
+5. Citations are for SOURCE ATTRIBUTION, not for proving every statement
+6. GOOD: One citation per key concept. BAD: Citation after every sentence.
+7. Place citations immediately after the specific claim: "The study found X [^1]" not "The study found X. [^1]"
+8. Do not reuse any bracketed numbers that appear inside the source content itself
+9. If multiple source chunks come from the same document, cite each relevant chunk separately (e.g., [^1] and [^2] can both be from the same document title)
+10. End with '#### Sources' section containing: [^n]: [[Title]] (one per line, matching citation order)`;
+
+export const WEB_CITATION_RULES = `WEB CITATION RULES:
+1. START with [^1] and increment sequentially ([^1], [^2], [^3], etc.) with NO gaps
+2. Cite ONLY when introducing new factual claims, statistics, or direct quotes from the search results
+3. After every cited claim, place the corresponding footnote immediately after the sentence ("The study found X [^1]")
+4. End with '#### Sources' and provide definitions EXACTLY in this format: [^n]: [Short Title](URL)
+
+IMPORTANT: Each source definition must follow this exact pattern:
+- Start with [^n]: (where n is the citation number)
+- Follow with [Title](URL) where Title is SHORT (2-5 words) and wrapped in square brackets
+- Example: [^1]: [Paul Graham Essay](https://paulgraham.com/wealth.html)
+- DO NOT write long descriptions - keep titles concise`;
+
+// ===== INSTRUCTION GENERATORS =====
+
+/**
+ * Generates citation instructions with source catalog for vault searches.
+ */
+export function getVaultCitationGuidance(sourceCatalog: string[]): string {
+  return `
+
+<guidance>
+${CITATION_RULES}
+
+Source Catalog (for reference only):
+${sourceCatalog.join("\n")}
+</guidance>`;
+}
+
+/**
+ * Generates citation instructions for QA contexts.
+ */
+export function getQACitationInstructions(sourceCatalog: string): string {
+  return `
+
+${CITATION_RULES}
+
+Source Catalog (for reference only):
+${sourceCatalog}`;
+}
+
+// ===== CONSTANTS =====
+
+const MAX_FALLBACK_SOURCES = 20;
+
+// ===== CENTRALIZED CITATION CONTROL =====
+
+/**
+ * Gets appropriate citation instructions based on settings.
+ * Returns empty string if citations are disabled.
+ */
+export function getCitationInstructions(
+  enableInlineCitations: boolean,
+  sourceCatalog: string[]
+): string {
+  if (!enableInlineCitations) {
+    return "";
+  }
+  return getVaultCitationGuidance(sourceCatalog);
+}
+
+/**
+ * Gets QA citation instructions based on settings.
+ * Returns empty string if citations are disabled.
+ */
+export function getQACitationInstructionsConditional(
+  enableInlineCitations: boolean,
+  sourceCatalog: string
+): string {
+  if (!enableInlineCitations) {
+    return "";
+  }
+  return getQACitationInstructions(sourceCatalog);
+}
+
+/**
+ * Adds fallback sources to response if citations are missing and citations are enabled.
+ */
+export function addFallbackSources(
+  response: string,
+  sources: { title?: string; path?: string }[],
+  enableInlineCitations: boolean
+): string {
+  // Input validation
+  if (!enableInlineCitations || !sources?.length || !response) {
+    return response || "";
+  }
+
+  if (hasExistingCitations(response)) {
+    return response;
+  }
+
+  // Add simple sources section as fallback
+  const sourcesList = sources
+    .slice(0, MAX_FALLBACK_SOURCES)
+    .map((s, i) => {
+      const title = (s.title || s.path || "Untitled").replace(/^\[\[|\]\]$/g, ""); // Strip existing wiki link brackets
+      return `[^${i + 1}]: [[${title}]]`;
+    })
+    .join("\n");
+
+  return `${response}\n\n#### Sources:\n\n${sourcesList}`;
+}
+
+// ===== CONTENT PROCESSING =====
+
+/**
+ * Sanitizes content to remove pre-existing citation markers to prevent number leakage.
+ */
+export function sanitizeContentForCitations(text: string): string {
+  if (!text) return "";
+
+  // Remove inline footnote refs like [^12]
+  let out = text.replace(/\[\^\d+\]/g, "");
+
+  // Remove numeric citations like [1] or [1, 2] that are not markdown links or wiki links
+  out = out.replace(/\[(\d+(?:\s*,\s*\d+)*)\](?!\()/g, "");
+
+  // Remove footnote definition lines like [^1]: something
+  out = out.replace(/^\s*\[\^\d+\]:.*$/gm, "");
+
+  return out;
+}
+
+/**
+ * Detects if response already has sources section or footnote definitions.
+ */
+export function hasExistingCitations(response: string): boolean {
+  const content = response || "";
+  const hasMarkdownHeading = /(^|\n)\s*#{1,6}\s*Sources\b/i.test(content);
+  const hasPlainLabel = /(^|\n)\s*Sources\s*(?:[:-]\s*)?(\n|$)/i.test(content);
+  const hasSummaryTag = /<summary[^>]*>\s*Sources\s*<\/summary>/i.test(content);
+  // More robust detection: look for ANY line starting with [^digits]:
+  const hasFootnoteDefinitions = /(^|\n)\s*\[\^\d+\]:\s*/.test(content);
+  return hasMarkdownHeading || hasPlainLabel || hasSummaryTag || hasFootnoteDefinitions;
+}
+
+/**
+ * Provides web-search-specific citation instructions using markdown links.
+ */
+export function getWebSearchCitationInstructions(enableInlineCitations: boolean): string {
+  if (!enableInlineCitations) {
+    return "";
+  }
+
+  return `\n\n${WEB_CITATION_RULES}`;
+}
+
+// ===== CITATION PROCESSING UTILITIES =====
+
+export interface SourcesSection {
+  mainContent: string;
+  sourcesBlock: string;
+}
+
+/**
+ * Extracts the sources section from content if present.
+ */
+export function extractSourcesSection(content: string): SourcesSection | null {
+  const sourcesRegex = /([\s\S]*?)\n+(?:####\s*)?Sources\s*:?\s*\n+([\s\S]*)$/i;
+  const match = content.match(sourcesRegex);
+  if (!match) return null;
+
+  return {
+    mainContent: match[1],
+    sourcesBlock: (match[2] || "").trim(),
+  };
+}
+
+/**
+ * Normalizes sources block by adding line breaks if everything is on one line.
+ */
+export function normalizeSourcesBlock(sourcesBlock: string): string {
+  if (!sourcesBlock.includes("\n")) {
+    // Ensure a break before every [n]
+    sourcesBlock = sourcesBlock.replace(/\s*\[(\d+)\]\s*/g, "\n[$1] ");
+    // And before every n. pattern if present
+    sourcesBlock = sourcesBlock.replace(/\s+(\d+)\.\s/g, "\n$1. ");
+    sourcesBlock = sourcesBlock.trim();
+  }
+  return sourcesBlock;
+}
+
+/**
+ * Parses footnote definitions from sources block.
+ */
+export function parseFootnoteDefinitions(sourcesBlock: string): string[] {
+  return sourcesBlock
+    .split("\n")
+    .map((l) => l.trim())
+    .filter((l) => /^\[\^\d+\]:/.test(l));
+}
+
+/**
+ * Builds a citation renumbering map based on first-mention order in content.
+ */
+export function buildCitationMap(
+  mainContent: string,
+  footnoteLines: string[]
+): Map<number, number> {
+  const map = new Map<number, number>();
+  const seen = new Set<number>();
+  const firstMention: number[] = [];
+
+  // Find first mention order in main content
+  const refRe = /\[\^(\d+)\]/g;
+  let mref: RegExpExecArray | null;
+  while ((mref = refRe.exec(mainContent)) !== null) {
+    const n = parseInt(mref[1], 10);
+    if (!seen.has(n)) {
+      seen.add(n);
+      firstMention.push(n);
+    }
+  }
+
+  if (firstMention.length > 0) {
+    firstMention.forEach((n, i) => map.set(n, i + 1));
+  } else {
+    // Fallback to definition order
+    let idx = 1;
+    for (const line of footnoteLines) {
+      const m = line.match(/^\[\^(\d+)\]:/);
+      if (m) {
+        const n = parseInt(m[1], 10);
+        if (!map.has(n)) map.set(n, idx++);
+      }
+    }
+  }
+
+  return map;
+}
+
+/**
+ * Normalizes citations in content using the provided mapping.
+ */
+export function normalizeCitations(content: string, map: Map<number, number>): string {
+  // Already-footnote refs: [^n] -> [n] (remapped contiguously)
+  // Use global flag with multiple passes to handle consecutive citations like [^7][^8]
+  let result = content;
+  let changed;
+  do {
+    changed = false;
+
+    // Handle single citations: [^n] -> [n]
+    result = result.replace(/\[\^(\d+)\]/g, (match, n) => {
+      const oldN = parseInt(n, 10);
+      const newN = map.get(oldN) ?? oldN;
+      const replacement = `[${newN}]`;
+      if (replacement !== match) {
+        changed = true;
+      }
+      return replacement;
+    });
+
+    // Handle multiple citations: [^n, ^m] -> [n, m]
+    result = result.replace(/\[\^(\d+(?:\s*,\s*\^?\d+)*)\]/g, (match, citationList) => {
+      // Split and process each number in the list
+      const processedNumbers = citationList
+        .split(",")
+        .map((part: string) => {
+          const cleanPart = part.trim().replace(/^\^/, ""); // Remove leading ^
+          const oldN = parseInt(cleanPart, 10);
+          const newN = map.get(oldN) ?? oldN;
+          return newN;
+        })
+        .sort((a: number, b: number) => a - b) // Sort numbers in ascending order
+        .map((n: number) => n.toString())
+        .join(", ");
+
+      const replacement = `[${processedNumbers}]`;
+      if (replacement !== match) {
+        changed = true;
+      }
+      return replacement;
+    });
+  } while (changed);
+
+  // Fix periods after citations that cause markdown list interpretation: [1]. -> [1]
+  result = result.replace(/\[(\d+)\]\./g, "[$1]");
+
+  return result;
+}
+
+/**
+ * Converts footnote definitions to simple display items.
+ */
+export function convertFootnoteDefinitions(
+  sourcesBlock: string,
+  map: Map<number, number>
+): string[] {
+  const items: string[] = [];
+  sourcesBlock.split("\n").forEach((line) => {
+    const m = line.match(/^\[\^(\d+)\]:\s*(.*)$/);
+    if (!m) return;
+    const oldN = parseInt(m[1], 10);
+    const newN = map.get(oldN) ?? oldN;
+    const markdownLink = m[2].match(/\[([^\]]+)\]\(([^)]+)\)/);
+    const wl = m[2].match(/\[\[(.*?)\]\]/);
+
+    let display: string;
+    if (markdownLink) {
+      // Proper markdown link format: [Title](URL)
+      display = `<a href="${markdownLink[2]}">${markdownLink[1]}</a>`;
+    } else if (wl) {
+      // Wiki link format: [[Title]]
+      display = `[[${wl[1]}]]`;
+    } else {
+      // Handle malformed web citations like "Description text](URL)"
+      const malformedLink = m[2].match(/^(.*?)\]\s*\(([^)]+)\)\s*$/);
+      if (malformedLink) {
+        // Extract text and URL from malformed pattern
+        const text = malformedLink[1].trim();
+        const url = malformedLink[2].trim();
+        display = `<a href="${url}">${text}</a>`;
+      } else {
+        // Fallback: remove any trailing parenthetical content
+        display = m[2].replace(/\s*\([^)]*\)\s*$/, "");
+      }
+    }
+    items[newN - 1] = display;
+  });
+  return items;
+}
+
+/**
+ * Consolidates duplicate sources and returns mapping for citation updates.
+ */
+export function consolidateDuplicateSources(items: string[]): {
+  uniqueItems: string[];
+  consolidationMap: Map<number, number>;
+} {
+  const uniqueItems: string[] = [];
+  const seenTitles = new Set<string>();
+  const consolidationMap = new Map<number, number>(); // oldIndex -> newIndex
+
+  items.forEach((item, originalIndex) => {
+    if (!item) return;
+
+    // Extract title from wikilink format [[title]] or use the item as-is
+    const titleMatch = item.match(/\[\[(.*?)\]\]/);
+    const title = titleMatch ? titleMatch[1].toLowerCase() : item.toLowerCase();
+
+    if (!seenTitles.has(title)) {
+      seenTitles.add(title);
+      uniqueItems.push(item);
+      consolidationMap.set(originalIndex + 1, uniqueItems.length); // 1-based indexing
+    } else {
+      // Find the index of the first occurrence
+      const firstOccurrenceIndex = uniqueItems.findIndex((existing) => {
+        const existingTitleMatch = existing.match(/\[\[(.*?)\]\]/);
+        const existingTitle = existingTitleMatch
+          ? existingTitleMatch[1].toLowerCase()
+          : existing.toLowerCase();
+        return existingTitle === title;
+      });
+      if (firstOccurrenceIndex >= 0) {
+        consolidationMap.set(originalIndex + 1, firstOccurrenceIndex + 1); // 1-based indexing
+      }
+    }
+  });
+
+  return { uniqueItems, consolidationMap };
+}
+
+/**
+ * Updates citations in content to reflect consolidated numbering.
+ */
+export function updateCitationsForConsolidation(
+  content: string,
+  consolidationMap: Map<number, number>
+): string {
+  if (consolidationMap.size === 0) return content;
+
+  return content.replace(/\[(\d+(?:\s*,\s*\d+)*)\]/g, (match, nums) => {
+    const parts = nums.split(/\s*,\s*/);
+    const remappedParts = parts.map((n: string) => {
+      const oldNum = parseInt(n, 10);
+      return String(consolidationMap.get(oldNum) || oldNum);
+    });
+    return `[${remappedParts.join(", ")}]`;
+  });
+}
+
+interface SourcesDisplayItem {
+  index: number;
+  html: string;
+}
+
+/**
+ * Escapes HTML-sensitive characters to avoid unintended markup injection.
+ */
+function escapeHtml(value: string): string {
+  return value
+    .replace(/&/g, "&amp;")
+    .replace(/</g, "&lt;")
+    .replace(/>/g, "&gt;")
+    .replace(/"/g, "&quot;")
+    .replace(/'/g, "&#39;");
+}
+
+/**
+ * Type guard that filters out nullish values when mapping optional entries.
+ */
+function isNonNull<T>(value: T | null | undefined): value is T {
+  return value !== null && value !== undefined;
+}
+
+/**
+ * Converts a normalized sources block into display-ready list items.
+ */
+function parseSimpleSources(sourcesBlock: string): SourcesDisplayItem[] {
+  const lines = sourcesBlock.split("\n");
+  const items: SourcesDisplayItem[] = [];
+
+  lines.forEach((rawLine) => {
+    const trimmed = rawLine.trim();
+    if (!trimmed) {
+      return;
+    }
+
+    const content = trimmed
+      .replace(/^<li>/i, "")
+      .replace(/<\/li>$/i, "")
+      .replace(/^[-*]\s*/, "")
+      .trim();
+
+    if (!content) {
+      return;
+    }
+
+    const markdownLink = content.match(/\[([^\]]+)\]\(([^)]+)\)/);
+    const wikiLink = content.match(/\[\[(.*?)\]\]/);
+
+    let html: string;
+    if (markdownLink) {
+      html = `<a href="${markdownLink[2]}">${markdownLink[1]}</a>`;
+    } else if (wikiLink) {
+      html = `[[${wikiLink[1]}]]`;
+    } else {
+      html = escapeHtml(content);
+    }
+
+    items.push({ index: items.length + 1, html });
+  });
+
+  return items;
+}
+
+/**
+ * Appends a styled, collapsible sources list to the main message content.
+ */
+function buildSourcesDetails(mainContent: string, items: SourcesDisplayItem[]): string {
+  if (items.length === 0) {
+    return mainContent;
+  }
+
+  const listItems = items
+    .map(
+      ({ index, html }) =>
+        `<li class="copilot-sources__item"><span class="copilot-sources__index">[${index}]</span><span class="copilot-sources__text">${html}</span></li>`
+    )
+    .join("\n");
+
+  return (
+    `${mainContent}\n\n<br/>\n` +
+    `<details class="copilot-sources"><summary class="copilot-sources__summary">Sources</summary>\n` +
+    `<ul class="copilot-sources__list">\n${listItems}\n</ul>\n</details>`
+  );
+}
+
+/**
+ * Main function to process inline citations in content.
+ * Uses settings to determine if inline citations should be processed.
+ */
+export function processInlineCitations(content: string, useInlineCitations: boolean): string {
+  const sourcesSection = extractSourcesSection(content);
+  if (!sourcesSection) return content;
+
+  let { mainContent, sourcesBlock } = sourcesSection;
+  sourcesBlock = normalizeSourcesBlock(sourcesBlock);
+
+  // If inline citations are disabled, use simple expandable sources list
+  if (!useInlineCitations) {
+    const simpleItems = parseSimpleSources(sourcesBlock);
+    return buildSourcesDetails(mainContent, simpleItems);
+  }
+
+  // Process inline citations
+  const footnoteLines = parseFootnoteDefinitions(sourcesBlock);
+  if (footnoteLines.length === 0) {
+    const simpleItems = parseSimpleSources(sourcesBlock);
+    return buildSourcesDetails(mainContent, simpleItems);
+  }
+
+  // Process footnote-style citations
+  const citationMap = buildCitationMap(mainContent, footnoteLines);
+  mainContent = normalizeCitations(mainContent, citationMap);
+
+  let items = convertFootnoteDefinitions(sourcesBlock, citationMap);
+  const { uniqueItems, consolidationMap } = consolidateDuplicateSources(items);
+
+  // Update citations to reflect consolidation
+  if (consolidationMap.size > 0) {
+    mainContent = updateCitationsForConsolidation(mainContent, consolidationMap);
+    items = uniqueItems;
+  }
+
+  const detailedItems = items
+    .map<SourcesDisplayItem | null>((item, index) => {
+      if (!item) {
+        return null;
+      }
+      return { index: index + 1, html: item };
+    })
+    .filter(isNonNull);
+
+  return buildSourcesDetails(mainContent, detailedItems);
+}
+
+// ===== SOURCE CATALOG UTILITIES =====
+
+export interface SourceCatalogEntry {
+  title: string;
+  path: string;
+}
+
+/**
+ * Formats source catalog entries for citation guidance.
+ */
+export function formatSourceCatalog(sources: SourceCatalogEntry[]): string[] {
+  return sources.map((source) => {
+    const title = source.title || source.path || "Untitled";
+    const path = source.path || title;
+    return `- [[${title}]] (${path})`;
+  });
+}
diff --git a/src/LLMProviders/chainRunner/utils/modelAdapter.ts b/src/LLMProviders/chainRunner/utils/modelAdapter.ts
index c1cc1b7..71ae83d 100644
--- a/src/LLMProviders/chainRunner/utils/modelAdapter.ts
+++ b/src/LLMProviders/chainRunner/utils/modelAdapter.ts
@@ -88,9 +88,45 @@ class BaseModelAdapter implements ModelAdapter {
       }
     }
 
+    const copilotCommandInstructions = this.buildCopilotCommandInstructions(toolMetadata);
+    if (copilotCommandInstructions) {
+      instructions.push(copilotCommandInstructions);
+    }
+
     return instructions.length > 0 ? instructions.join("\n\n") : "";
   }
 
+  /**
+   * Build instructional text that maps Copilot command aliases to tool names.
+   *
+   * @param toolMetadata - Metadata for all tools available to the agent.
+   * @returns Instructional string or null if there are no Copilot aliases.
+   */
+  private buildCopilotCommandInstructions(toolMetadata: ToolMetadata[]): string | null {
+    const aliasLines: string[] = [];
+
+    for (const meta of toolMetadata) {
+      if (!meta.copilotCommands || meta.copilotCommands.length === 0) {
+        continue;
+      }
+
+      for (const command of meta.copilotCommands) {
+        aliasLines.push(`- ${command}: call the tool named ${meta.id}`);
+      }
+    }
+
+    if (aliasLines.length === 0) {
+      return null;
+    }
+
+    return [
+      "When the user explicitly includes a Copilot command alias (e.g., @vault) in their message, treat it as a direct request to call the mapped tool before proceeding.",
+      "Honor these aliases exactly (case-insensitive):",
+      ...aliasLines,
+      "If the referenced tool is unavailable, explain that the command cannot be fulfilled instead of ignoring it.",
+    ].join("\n");
+  }
+
   enhanceSystemPrompt(
     basePrompt: string,
     toolDescriptions: string,
diff --git a/src/LLMProviders/chainRunner/utils/searchResultUtils.test.ts b/src/LLMProviders/chainRunner/utils/searchResultUtils.test.ts
index 0484889..b20ca02 100644
--- a/src/LLMProviders/chainRunner/utils/searchResultUtils.test.ts
+++ b/src/LLMProviders/chainRunner/utils/searchResultUtils.test.ts
@@ -41,14 +41,11 @@ describe("searchResultUtils", () => {
       ];
 
       const result = formatSearchResultsForLLM(documents);
-      expect(result).toBe(`<document>
-<title>Test Document</title>
-<path>path/to/document.md</path>
-<modified>2024-01-15T10:30:00.000Z</modified>
-<content>
-This is the content
-</content>
-</document>`);
+      expect(result).toContain(
+        `<document>\n<id>1</id>\n<title>Test Document</title>\n<path>path/to/document.md</path>`
+      );
+      expect(result).toContain(`<modified>2024-01-15T10:30:00.000Z</modified>`);
+      expect(result).toContain(`<content>\nThis is the content\n</content>\n</document>`);
     });
 
     it("should handle missing optional fields gracefully", () => {
@@ -61,12 +58,8 @@ This is the content
       ];
 
       const result = formatSearchResultsForLLM(documents);
-      expect(result).toBe(`<document>
-<title>Test Document</title>
-<content>
-This is the content
-</content>
-</document>`);
+      expect(result).toContain(`<document>\n<id>1</id>\n<title>Test Document</title>`);
+      expect(result).toContain(`<content>\nThis is the content\n</content>\n</document>`);
     });
 
     it("should not include path if it equals title", () => {
diff --git a/src/LLMProviders/chainRunner/utils/searchResultUtils.ts b/src/LLMProviders/chainRunner/utils/searchResultUtils.ts
index 6e7e057..a477d13 100644
--- a/src/LLMProviders/chainRunner/utils/searchResultUtils.ts
+++ b/src/LLMProviders/chainRunner/utils/searchResultUtils.ts
@@ -20,9 +20,11 @@ export function formatSearchResultsForLLM(searchResults: any[]): string {
 
   // Format each document with essential metadata
   const formattedDocs = includedDocs
-    .map((doc: any) => {
+    .map((doc: any, idx: number) => {
       const title = doc.title || "Untitled";
       const path = doc.path || "";
+      // Optional stable source id if provided by caller; fallback to order
+      const sourceId = (doc as any).__sourceId || (doc as any).source_id || idx + 1;
 
       // Safely handle mtime - check validity before converting
       let modified: string | null = null;
@@ -35,6 +37,7 @@ export function formatSearchResultsForLLM(searchResults: any[]): string {
 
       // Use template literal for cleaner XML generation
       return `<document>
+<id>${sourceId}</id>
 <title>${title}</title>${
         path && path !== title
           ? `
diff --git a/src/LLMProviders/chainRunner/utils/toolCallParser.test.ts b/src/LLMProviders/chainRunner/utils/toolCallParser.test.ts
index 6654323..bee18cc 100644
--- a/src/LLMProviders/chainRunner/utils/toolCallParser.test.ts
+++ b/src/LLMProviders/chainRunner/utils/toolCallParser.test.ts
@@ -42,17 +42,20 @@ describe("toolCallParser encoding/decoding", () => {
 
   it("integration: ToolResultFormatter.format should handle encoded JSON localSearch results", () => {
     const id = "localSearch-789";
-    const localSearchArrayJson = JSON.stringify([
-      {
-        title: "Lesson 1",
-        content:
-          "Date: 2025/5/13\nProgress: 0/10. <!--TOOL_CALL_START:x:y:z:a:b:c--> should not break JSON --> tail",
-        path: "Piano Lessons/Lesson 1.md",
-        score: 0.59,
-        rerank_score: null,
-        includeInContext: true,
-      },
-    ]);
+    const localSearchArrayJson = JSON.stringify({
+      type: "local_search",
+      documents: [
+        {
+          title: "Lesson 1",
+          content:
+            "Date: 2025/5/13\nProgress: 0/10. <!--TOOL_CALL_START:x:y:z:a:b:c--> should not break JSON --> tail",
+          path: "Piano Lessons/Lesson 1.md",
+          score: 0.59,
+          rerank_score: null,
+          includeInContext: true,
+        },
+      ],
+    });
 
     const marker = createToolCallMarker(
       id,
diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx
index f256ee0..a043557 100644
--- a/src/components/Chat.tsx
+++ b/src/components/Chat.tsx
@@ -550,9 +550,8 @@ const Chat: React.FC<ChatProps> = ({
     safeSet.setCurrentAiMessage("");
     setContextNotes([]);
     clearSelectedTextContexts();
-    // Only modify includeActiveNote if in a non-COPILOT_PLUS_CHAIN mode
-    // In COPILOT_PLUS_CHAIN mode, respect the settings.includeActiveNoteAsContext value
-    if (selectedChain !== ChainType.COPILOT_PLUS_CHAIN) {
+    // Respect the includeActiveNote setting for all non-project chains
+    if (selectedChain === ChainType.PROJECT_CHAIN) {
       setIncludeActiveNote(false);
     } else {
       setIncludeActiveNote(settings.includeActiveNoteAsContext);
@@ -590,11 +589,10 @@ const Chat: React.FC<ChatProps> = ({
   useEffect(() => {
     if (settings.includeActiveNoteAsContext !== undefined) {
       // Only apply the setting if not in Project mode
-      if (selectedChain === ChainType.COPILOT_PLUS_CHAIN) {
-        setIncludeActiveNote(settings.includeActiveNoteAsContext);
-      } else {
-        // In other modes, always disable including active note
+      if (selectedChain === ChainType.PROJECT_CHAIN) {
         setIncludeActiveNote(false);
+      } else {
+        setIncludeActiveNote(settings.includeActiveNoteAsContext);
       }
     }
   }, [settings.includeActiveNoteAsContext, selectedChain]);
diff --git a/src/components/chat-components/ChatSingleMessage.test.tsx b/src/components/chat-components/ChatSingleMessage.test.tsx
new file mode 100644
index 0000000..985bf94
--- /dev/null
+++ b/src/components/chat-components/ChatSingleMessage.test.tsx
@@ -0,0 +1,162 @@
+import React from "react";
+import { render, waitFor } from "@testing-library/react";
+import ChatSingleMessage, {
+  normalizeFootnoteRendering,
+} from "@/components/chat-components/ChatSingleMessage";
+import { ChatMessage } from "@/types/message";
+import type { App } from "obsidian";
+import { TooltipProvider } from "@/components/ui/tooltip";
+
+jest.mock("@/settings/model", () => ({
+  useSettingsValue: jest.fn(() => ({ enableInlineCitations: true })),
+}));
+
+jest.mock("@/LLMProviders/chainRunner/utils/toolCallParser", () => ({
+  parseToolCallMarkers: jest.fn((message: string) => ({
+    segments: [{ type: "text", content: message }],
+  })),
+}));
+
+jest.mock("@/LLMProviders/chainRunner/utils/citationUtils", () => ({
+  processInlineCitations: jest.fn((content: string) => content),
+}));
+
+jest.mock("obsidian", () => {
+  const renderMarkdown = jest.fn();
+  return {
+    MarkdownRenderer: {
+      renderMarkdown,
+    },
+    Component: class {},
+    MarkdownView: class {},
+    TFile: class {},
+    App: class {},
+    Platform: {
+      isMobile: false,
+    },
+    Modal: class {
+      open() {
+        /* noop */
+      }
+      close() {
+        /* noop */
+      }
+    },
+    __renderMarkdownMock: renderMarkdown,
+  };
+});
+
+const { __renderMarkdownMock: renderMarkdownMock } = jest.requireMock("obsidian") as {
+  __renderMarkdownMock: jest.Mock;
+};
+
+describe("normalizeFootnoteRendering", () => {
+  beforeEach(() => {
+    renderMarkdownMock.mockReset();
+  });
+
+  it("removes separator and backref while preserving non-footnote elements", () => {
+    const container = document.createElement("div");
+    container.innerHTML = `
+      <div>
+        <p>Body <sup><a href="#fn-1">1-1</a></sup></p>
+        <hr class="content-separator" />
+        <div class="footnotes">
+          <hr class="footnotes-sep" />
+          <ol>
+            <li id="fn-1">
+              Entry <a class="footnote-backref" href="#ref">↩</a>
+            </li>
+          </ol>
+        </div>
+      </div>
+    `;
+
+    normalizeFootnoteRendering(container);
+
+    expect(container.querySelector(".footnotes hr")).toBeNull();
+    expect(container.querySelector(".footnote-backref")).toBeNull();
+    expect(container.querySelector(".content-separator")).not.toBeNull();
+    expect(container.querySelector('a[href="#fn-1"]')?.textContent).toBe("1");
+  });
+
+  it("leaves non-numeric footnote references untouched", () => {
+    const container = document.createElement("div");
+    container.innerHTML = `
+      <p>Body <sup><a href="#fn-note">Note-A</a></sup></p>
+      <a class="footnote-backref" href="#ref">↩</a>
+    `;
+
+    normalizeFootnoteRendering(container);
+
+    expect(container.querySelector('a[href="#fn-note"]')?.textContent).toBe("Note-A");
+    expect(container.querySelector(".footnote-backref")).toBeNull();
+  });
+});
+
+describe("ChatSingleMessage", () => {
+  const baseMessage: ChatMessage = {
+    id: "message-1",
+    message: "Test message",
+    sender: "assistant",
+    timestamp: { epoch: Date.now(), display: "now", fileName: "now" },
+    isVisible: true,
+  };
+
+  const createAppStub = (): App =>
+    ({
+      workspace: {
+        getActiveFile: jest.fn(() => null),
+        getMostRecentLeaf: jest.fn(() => null),
+        getLeaf: jest.fn(() => null),
+      },
+      metadataCache: {
+        getFirstLinkpathDest: jest.fn(() => null),
+      },
+    }) as unknown as App;
+
+  beforeEach(() => {
+    renderMarkdownMock.mockReset();
+  });
+
+  beforeAll(() => {
+    (globalThis as any).activeDocument = document;
+  });
+
+  it("normalizes rendered footnotes for assistant messages", async () => {
+    renderMarkdownMock.mockImplementation((_markdown: string, el: HTMLElement) => {
+      el.innerHTML = `
+        <p>Example <sup><a href="#fn-2">2-1</a></sup></p>
+        <hr class="content-hr" />
+        <div class="footnotes">
+          <hr class="footnotes-sep" />
+          <ol>
+            <li id="fn-2">
+              Source <a class="footnote-backref" href="#back">↩</a>
+            </li>
+          </ol>
+        </div>
+      `;
+    });
+
+    const { container } = render(
+      <TooltipProvider>
+        <ChatSingleMessage
+          message={baseMessage}
+          app={createAppStub()}
+          isStreaming={false}
+          onDelete={() => {}}
+        />
+      </TooltipProvider>
+    );
+
+    await waitFor(() => expect(renderMarkdownMock).toHaveBeenCalled());
+
+    const messageSegment = container.querySelector(".message-segment");
+    expect(messageSegment).toBeTruthy();
+    expect(messageSegment?.querySelector(".footnotes hr")).toBeNull();
+    expect(messageSegment?.querySelector(".footnote-backref")).toBeNull();
+    expect(messageSegment?.querySelector(".content-hr")).not.toBeNull();
+    expect(messageSegment?.querySelector('a[href="#fn-2"]')?.textContent).toBe("2");
+  });
+});
diff --git a/src/components/chat-components/ChatSingleMessage.tsx b/src/components/chat-components/ChatSingleMessage.tsx
index 781c2a3..59e7624 100644
--- a/src/components/chat-components/ChatSingleMessage.tsx
+++ b/src/components/chat-components/ChatSingleMessage.tsx
@@ -2,13 +2,14 @@ import { ChatButtons } from "@/components/chat-components/ChatButtons";
 import { ToolCallBanner } from "@/components/chat-components/ToolCallBanner";
 import { SourcesModal } from "@/components/modals/SourcesModal";
 import { Badge } from "@/components/ui/badge";
-import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
+import { HelpTooltip } from "@/components/ui/help-tooltip";
 import { USER_SENDER } from "@/constants";
 import { cn } from "@/lib/utils";
 import { parseToolCallMarkers } from "@/LLMProviders/chainRunner/utils/toolCallParser";
+import { processInlineCitations } from "@/LLMProviders/chainRunner/utils/citationUtils";
+import { useSettingsValue } from "@/settings/model";
 import { ChatMessage } from "@/types/message";
 import { cleanMessageForCopy, insertIntoEditor } from "@/utils";
-import { Bot, User } from "lucide-react";
 import { App, Component, MarkdownRenderer, MarkdownView, TFile } from "obsidian";
 import React, { useCallback, useEffect, useRef, useState } from "react";
 import ReactDOM, { Root } from "react-dom/client";
@@ -19,6 +20,43 @@ declare global {
   }
 }
 
+const FOOTNOTE_SUFFIX_PATTERN = /^\d+-\d+$/;
+
+/**
+ * Normalizes rendered markdown footnotes to align with inline citation UX.
+ * Removes separators/backrefs and fixes numbering artifacts (e.g., "2-1").
+ */
+export const normalizeFootnoteRendering = (root: HTMLElement): void => {
+  const footnoteSection = root.querySelector(".footnotes");
+
+  if (footnoteSection) {
+    footnoteSection.querySelectorAll("hr, hr.footnotes-sep").forEach((el) => el.remove());
+    footnoteSection
+      .querySelectorAll("a.footnote-backref, a.footnote-link.footnote-backref")
+      .forEach((el) => el.remove());
+  } else {
+    root
+      .querySelectorAll("a.footnote-backref, a.footnote-link.footnote-backref")
+      .forEach((el) => el.remove());
+  }
+
+  root
+    .querySelectorAll(
+      'a.footnote-ref, sup a[href^="#fn"], sup a[href^="#fn-"], a[href^="#fn"], a[href^="#fn-"]'
+    )
+    .forEach((anchor) => {
+      const text = anchor.textContent?.trim() ?? "";
+      if (!text || !FOOTNOTE_SUFFIX_PATTERN.test(text)) {
+        return;
+      }
+
+      const [primary] = text.split("-");
+      if (primary && primary !== text) {
+        anchor.textContent = primary;
+      }
+    });
+};
+
 function MessageContext({ context }: { context: ChatMessage["context"] }) {
   if (!context || (!context.notes?.length && !context.urls?.length)) {
     return null;
@@ -27,24 +65,18 @@ function MessageContext({ context }: { context: ChatMessage["context"] }) {
   return (
     <div className="tw-flex tw-flex-wrap tw-gap-2">
       {context.notes.map((note, index) => (
-        <Tooltip key={`${index}-${note.path}`}>
-          <TooltipTrigger asChild>
-            <Badge variant="secondary">
-              <span className="tw-max-w-40 tw-truncate">{note.basename}</span>
-            </Badge>
-          </TooltipTrigger>
-          <TooltipContent>{note.path}</TooltipContent>
-        </Tooltip>
+        <HelpTooltip key={`${index}-${note.path}`} content={note.path} side="top">
+          <Badge variant="secondary">
+            <span className="tw-max-w-40 tw-truncate">{note.basename}</span>
+          </Badge>
+        </HelpTooltip>
       ))}
       {context.urls.map((url, index) => (
-        <Tooltip key={`${index}-${url}`}>
-          <TooltipTrigger asChild>
-            <Badge variant="secondary">
-              <span className="tw-max-w-40 tw-truncate">{url}</span>
-            </Badge>
-          </TooltipTrigger>
-          <TooltipContent>{url}</TooltipContent>
-        </Tooltip>
+        <HelpTooltip key={`${index}-${url}`} content={url} side="top">
+          <Badge variant="secondary">
+            <span className="tw-max-w-40 tw-truncate">{url}</span>
+          </Badge>
+        </HelpTooltip>
       ))}
     </div>
   );
@@ -69,12 +101,14 @@ const ChatSingleMessage: React.FC<ChatSingleMessageProps> = ({
   onDelete,
   chatHistory = [],
 }) => {
+  const settings = useSettingsValue();
   const [isCopied, setIsCopied] = useState<boolean>(false);
   const [isEditing, setIsEditing] = useState<boolean>(false);
   const [editedMessage, setEditedMessage] = useState<string>(message.message);
   const contentRef = useRef<HTMLDivElement>(null);
   const componentRef = useRef<Component | null>(null);
   const textareaRef = useRef<HTMLTextAreaElement>(null);
+  const isUnmountingRef = useRef<boolean>(false);
   // Use a stable ID for the message to preserve tool call roots across re-renders
   const messageId = useRef(
     message.timestamp?.epoch
@@ -250,7 +284,10 @@ const ChatSingleMessage: React.FC<ChatSingleMessageProps> = ({
       const writeToFileSectionProcessed = processWriteToFileSection(thinkSectionProcessed);
 
       // Transform markdown sources section into HTML structure
-      const sourcesSectionProcessed = processSourcesSection(writeToFileSectionProcessed);
+      const sourcesSectionProcessed = processInlineCitations(
+        writeToFileSectionProcessed,
+        settings.enableInlineCitations
+      );
 
       // Transform [[link]] to clickable format but exclude ![[]] image links
       const noteLinksProcessed = replaceLinks(
@@ -262,35 +299,12 @@ const ChatSingleMessage: React.FC<ChatSingleMessageProps> = ({
 
       return noteLinksProcessed;
     },
-    [app, isStreaming]
+    [app, isStreaming, settings.enableInlineCitations]
   );
 
-  const processSourcesSection = (content: string): string => {
-    const sections = content.split("\n\n#### Sources:\n\n");
-    if (sections.length !== 2) return content;
-
-    const [mainContent, sources] = sections;
-    const sourceLinks = sources
-      .split("\n")
-      .map((line) => {
-        const match = line.match(/- \[\[(.*?)\]\]/);
-        if (match) {
-          return `<li>[[${match[1]}]]</li>`;
-        }
-        return line;
-      })
-      .join("\n");
-
-    return (
-      mainContent +
-      "\n\n<br/>\n<details><summary>Sources</summary>\n<ul>\n" +
-      sourceLinks +
-      "\n</ul>\n</details>"
-    );
-  };
-
   useEffect(() => {
-    let isUnmounting = false;
+    // Reset unmounting flag when effect runs
+    isUnmountingRef.current = false;
 
     if (contentRef.current && message.sender !== USER_SENDER) {
       // Create a new Component instance if it doesn't exist
@@ -301,7 +315,7 @@ const ChatSingleMessage: React.FC<ChatSingleMessageProps> = ({
       const processedMessage = preprocess(message.message);
       const parsedMessage = parseToolCallMarkers(processedMessage);
 
-      if (!isUnmounting) {
+      if (!isUnmountingRef.current) {
         // Track existing tool call IDs
         const existingToolCallIds = new Set<string>();
         const existingElements = contentRef.current.querySelectorAll('[id^="tool-call-"]');
@@ -331,6 +345,7 @@ const ChatSingleMessage: React.FC<ChatSingleMessageProps> = ({
             }
 
             MarkdownRenderer.renderMarkdown(segment.content, textDiv, "", componentRef.current!);
+            normalizeFootnoteRendering(textDiv);
             currentIndex++;
           } else if (segment.type === "toolCall" && segment.toolCall) {
             const toolCallId = segment.toolCall.id;
@@ -339,7 +354,7 @@ const ChatSingleMessage: React.FC<ChatSingleMessageProps> = ({
             if (existingDiv) {
               // Update existing tool call
               const root = rootsRef.current.get(toolCallId);
-              if (root) {
+              if (root && !isUnmountingRef.current) {
                 root.render(
                   <ToolCallBanner
                     toolName={segment.toolCall.toolName}
@@ -368,16 +383,18 @@ const ChatSingleMessage: React.FC<ChatSingleMessageProps> = ({
               const root = ReactDOM.createRoot(toolDiv);
               rootsRef.current.set(toolCallId, root);
 
-              root.render(
-                <ToolCallBanner
-                  toolName={segment.toolCall.toolName}
-                  displayName={segment.toolCall.displayName}
-                  emoji={segment.toolCall.emoji}
-                  isExecuting={segment.toolCall.isExecuting}
-                  result={segment.toolCall.result || null}
-                  confirmationMessage={segment.toolCall.confirmationMessage}
-                />
-              );
+              if (!isUnmountingRef.current) {
+                root.render(
+                  <ToolCallBanner
+                    toolName={segment.toolCall.toolName}
+                    displayName={segment.toolCall.displayName}
+                    emoji={segment.toolCall.emoji}
+                    isExecuting={segment.toolCall.isExecuting}
+                    result={segment.toolCall.result || null}
+                    confirmationMessage={segment.toolCall.confirmationMessage}
+                  />
+                );
+              }
               currentIndex++;
             }
           }
@@ -415,7 +432,7 @@ const ChatSingleMessage: React.FC<ChatSingleMessageProps> = ({
 
     // Cleanup function
     return () => {
-      isUnmounting = true;
+      isUnmountingRef.current = true;
     };
   }, [message, app, componentRef, isStreaming, preprocess]);
 
@@ -452,6 +469,9 @@ const ChatSingleMessage: React.FC<ChatSingleMessageProps> = ({
     cleanupOldRoots();
 
     return () => {
+      // Set unmounting flag immediately
+      isUnmountingRef.current = true;
+
       // Defer cleanup to avoid React rendering conflicts
       setTimeout(() => {
         // Clean up component
@@ -617,14 +637,16 @@ const ChatSingleMessage: React.FC<ChatSingleMessageProps> = ({
     <div className="tw-my-1 tw-flex tw-w-full tw-flex-col">
       <div
         className={cn(
-          "tw-group tw-mx-2 tw-flex tw-gap-2 tw-rounded-md tw-p-2",
+          "tw-group tw-mx-2 tw-rounded-md tw-p-2",
           message.sender === USER_SENDER && "tw-border tw-border-solid tw-border-border"
         )}
+        style={
+          message.sender === USER_SENDER
+            ? { backgroundColor: "var(--background-modifier-hover)" }
+            : undefined
+        }
       >
-        <div className="tw-w-6 tw-shrink-0">
-          {message.sender === USER_SENDER ? <User /> : <Bot />}
-        </div>
-        <div className="tw-flex tw-max-w-full tw-grow tw-flex-col tw-gap-2 tw-overflow-hidden">
+        <div className="tw-flex tw-max-w-full tw-flex-col tw-gap-2 tw-overflow-hidden">
           {!isEditing && <MessageContext context={message.context} />}
           <div className="message-content">{renderMessageContent()}</div>
 
diff --git a/src/components/chat-components/ProjectList.tsx b/src/components/chat-components/ProjectList.tsx
index 6389c96..bed1512 100644
--- a/src/components/chat-components/ProjectList.tsx
+++ b/src/components/chat-components/ProjectList.tsx
@@ -10,7 +10,7 @@ import {
   SelectTrigger,
   SelectValue,
 } from "@/components/ui/select";
-import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
+import { HelpTooltip } from "@/components/ui/help-tooltip";
 import { SearchBar } from "@/components/ui/SearchBar";
 import { cn } from "@/lib/utils";
 import { logError } from "@/logger";
@@ -20,7 +20,6 @@ import {
   ChevronUp,
   Edit2,
   Folder,
-  Info,
   MessageSquare,
   Plus,
   Search,
@@ -30,6 +29,7 @@ import {
 import { App, Notice } from "obsidian";
 import React, { memo, useEffect, useMemo, useState } from "react";
 import { filterProjects } from "@/utils/projectUtils";
+import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
 
 function ProjectItem({
   project,
@@ -236,7 +236,7 @@ export const ProjectList = memo(
           <div className="tw-flex tw-flex-col">
             {showChatInput && selectedProject ? (
               <div className="tw-flex tw-items-center tw-justify-between tw-px-2 tw-py-3">
-                <div className="tw-flex tw-items-center tw-gap-2">
+                <div className="tw-flex tw-min-w-0 tw-items-center tw-gap-2">
                   <span className="tw-font-semibold tw-text-normal">Projects</span>
                   <Select
                     value={selectedProject.name}
@@ -247,31 +247,31 @@ export const ProjectList = memo(
                       }
                     }}
                   >
-                    <SelectTrigger className="tw-w-[200px]">
+                    <SelectTrigger className="tw-truncate">
                       <SelectValue>
-                        <div className="tw-flex tw-items-center tw-gap-2">
-                          <Folder className="tw-size-4 tw-text-accent/70" />
-                          <span>{selectedProject.name}</span>
+                        <div className="tw-flex tw-min-w-0 tw-items-center tw-gap-2">
+                          <Folder className="tw-size-4 tw-shrink-0 tw-text-accent/70" />
+                          <span className="tw-flex-1 tw-truncate">{selectedProject.name}</span>
                         </div>
                       </SelectValue>
                     </SelectTrigger>
-                    <SelectContent>
+                    <SelectContent className="tw-truncate">
                       {projects.map((project) => (
                         <SelectItem
                           key={project.name}
                           value={project.name}
                           className="tw-flex tw-items-center tw-gap-2"
                         >
-                          <div className="tw-flex tw-items-center tw-gap-2">
-                            <Folder className="tw-size-4" />
-                            {project.name}
+                          <div className="tw-flex tw-min-w-0 tw-items-center tw-gap-2">
+                            <Folder className="tw-size-4 tw-shrink-0" />
+                            <span className="tw-truncate">{project.name}</span>
                           </div>
                         </SelectItem>
                       ))}
                     </SelectContent>
                   </Select>
                 </div>
-                <div className="tw-flex tw-items-center tw-gap-2">
+                <div className="tw-ml-1 tw-flex tw-items-center tw-gap-2">
                   <Button
                     variant="secondary"
                     onClick={() => handleEditProject(selectedProject)}
@@ -307,14 +307,11 @@ export const ProjectList = memo(
                 <div className="tw-flex tw-items-center tw-justify-between tw-px-4 tw-py-3">
                   <div className="tw-flex tw-flex-1 tw-items-center tw-gap-2">
                     <span className="tw-font-semibold tw-text-normal">Projects</span>
-                    <Tooltip>
-                      <TooltipTrigger asChild>
-                        <Info className="tw-size-4 tw-text-muted" />
-                      </TooltipTrigger>
-                      <TooltipContent side="bottom" className="tw-w-64">
-                        Manage your projects with different contexts and configurations.
-                      </TooltipContent>
-                    </Tooltip>
+                    <HelpTooltip
+                      content="Manage your projects with different contexts and configurations."
+                      contentClassName="tw-w-64"
+                      buttonClassName="tw-size-4 tw-text-muted"
+                    />
                   </div>
                   <div className="tw-flex tw-items-center tw-gap-2">
                     <Button className="tw-px-2" variant="secondary" onClick={handleAddProject}>
diff --git a/src/components/chat-components/RelevantNotes.tsx b/src/components/chat-components/RelevantNotes.tsx
index f4e56b6..c6caf07 100644
--- a/src/components/chat-components/RelevantNotes.tsx
+++ b/src/components/chat-components/RelevantNotes.tsx
@@ -2,7 +2,7 @@ import { Badge } from "@/components/ui/badge";
 import { Button } from "@/components/ui/button";
 import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
 import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
-import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
+import { HelpTooltip } from "@/components/ui/help-tooltip";
 import { useActiveFile } from "@/hooks/useActiveFile";
 import { cn } from "@/lib/utils";
 import {
@@ -17,13 +17,13 @@ import {
   ChevronUp,
   FileInput,
   FileOutput,
-  Info,
   PlusCircle,
   RefreshCcw,
   TriangleAlert,
 } from "lucide-react";
 import { Notice, TFile } from "obsidian";
 import React, { memo, useCallback, useEffect, useState } from "react";
+import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
 
 function useRelevantNotes(refresher: number) {
   const [relevantNotes, setRelevantNotes] = useState<RelevantNoteEntry[]>([]);
@@ -47,6 +47,7 @@ function useRelevantNotes(refresher: number) {
         setRelevantNotes([]);
       }
     }
+
     fetchNotes();
   }, [activeFile?.path, refresher]);
 
@@ -57,6 +58,7 @@ function useHasIndex(notePath: string, refresher: number) {
   const [hasIndex, setHasIndex] = useState(true);
   useEffect(() => {
     if (!notePath) return;
+
     async function fetchHasIndex() {
       try {
         const VectorStoreManager = (await import("@/search/vectorStoreManager")).default;
@@ -66,6 +68,7 @@ function useHasIndex(notePath: string, refresher: number) {
         setHasIndex(false);
       }
     }
+
     fetchHasIndex();
   }, [notePath, refresher]);
   return hasIndex;
@@ -290,22 +293,16 @@ export const RelevantNotes = memo(
           <div className="tw-flex tw-items-center tw-justify-between tw-pb-2 tw-pl-1">
             <div className="tw-flex tw-flex-1 tw-items-center tw-gap-2">
               <span className="tw-font-semibold tw-text-normal">Relevant Notes</span>
-              <Tooltip>
-                <TooltipTrigger asChild>
-                  <Info className="tw-size-4 tw-text-muted" />
-                </TooltipTrigger>
-                <TooltipContent side="bottom" className="tw-w-64">
-                  Relevance is a combination of semantic similarity and links.
-                </TooltipContent>
-              </Tooltip>
+              <HelpTooltip
+                content="Relevance is a combination of semantic similarity and links."
+                contentClassName="tw-w-64"
+                buttonClassName="tw-size-4 tw-text-muted"
+              />
 
               {!hasIndex && (
-                <Tooltip>
-                  <TooltipTrigger asChild>
-                    <TriangleAlert className="tw-size-4 tw-text-warning" />
-                  </TooltipTrigger>
-                  <TooltipContent side="bottom">Note has not been indexed</TooltipContent>
-                </Tooltip>
+                <HelpTooltip content="Note has not been indexed" side="bottom">
+                  <TriangleAlert className="tw-size-4 tw-text-warning" />
+                </HelpTooltip>
               )}
             </div>
             <div className="tw-flex tw-items-center">
diff --git a/src/components/chat-components/SuggestedPrompts.tsx b/src/components/chat-components/SuggestedPrompts.tsx
index 61be9ab..2c07af0 100644
--- a/src/components/chat-components/SuggestedPrompts.tsx
+++ b/src/components/chat-components/SuggestedPrompts.tsx
@@ -1,17 +1,18 @@
 import { useChainType } from "@/aiParams";
 import { ChainType } from "@/chainFactory";
 import { Button } from "@/components/ui/button";
-import { Card, CardTitle, CardContent, CardHeader } from "@/components/ui/card";
-import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
 import { VAULT_VECTOR_STORE_STRATEGY } from "@/constants";
 import { useSettingsValue } from "@/settings/model";
 import { PlusCircle, TriangleAlert } from "lucide-react";
 import React, { useMemo } from "react";
+import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
 
 interface NotePrompt {
   title: string;
   prompts: string[];
 }
+
 const SUGGESTED_PROMPTS: Record<string, NotePrompt> = {
   activeNote: {
     title: "Active Note Insights",
diff --git a/src/components/modals/project/AddProjectModal.tsx b/src/components/modals/project/AddProjectModal.tsx
index 1e84da4..771331b 100644
--- a/src/components/modals/project/AddProjectModal.tsx
+++ b/src/components/modals/project/AddProjectModal.tsx
@@ -8,15 +8,14 @@ import { getModelDisplayWithIcons } from "@/components/ui/model-display";
 import { ObsidianNativeSelect } from "@/components/ui/obsidian-native-select";
 import { SettingSlider } from "@/components/ui/setting-slider";
 import { Textarea } from "@/components/ui/textarea";
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
 import { DEFAULT_MODEL_SETTING } from "@/constants";
 import { getDecodedPatterns } from "@/search/searchUtils";
 import { getModelKeyFromModel, useSettingsValue } from "@/settings/model";
 import { checkModelApiKey, err2String, randomUUID } from "@/utils";
-import { HelpCircle } from "lucide-react";
 import { App, Modal, Notice } from "obsidian";
 import React, { useState } from "react";
 import { createRoot, Root } from "react-dom/client";
+import { HelpTooltip } from "@/components/ui/help-tooltip";
 
 interface AddProjectModalContentProps {
   initialProject?: ProjectConfig;
@@ -259,31 +258,27 @@ function AddProjectModalContent({ initialProject, onSave, onCancel }: AddProject
             label={
               <div className="tw-flex tw-items-center tw-gap-2">
                 <span>File Context</span>
-                <TooltipProvider delayDuration={0}>
-                  <Tooltip>
-                    <TooltipTrigger asChild>
-                      <HelpCircle className="tw-size-4 tw-text-muted" />
-                    </TooltipTrigger>
-                    <TooltipContent>
-                      <div className="tw-max-w-80">
-                        <strong>Supported File Types:</strong>
-                        <br />
-                        <strong>• Documents:</strong> pdf, doc, docx, ppt, pptx, epub, txt, rtf and
-                        many more
-                        <br />
-                        <strong>• Images:</strong> jpg, png, svg, gif, bmp, webp, tiff
-                        <br />
-                        <strong>• Spreadsheets:</strong> xlsx, xls, csv, numbers
-                        <br />
-                        <br />
-                        Non-markdown files are converted to markdown in the background.
-                        <br />
-                        <strong>Rate limit:</strong> 50 files or 100MB per 3 hours, whichever is
-                        reached first.
-                      </div>
-                    </TooltipContent>
-                  </Tooltip>
-                </TooltipProvider>
+                <HelpTooltip
+                  buttonClassName="tw-size-4 tw-text-muted"
+                  content={
+                    <div className="tw-max-w-80">
+                      <strong>Supported File Types:</strong>
+                      <br />
+                      <strong>• Documents:</strong> pdf, doc, docx, ppt, pptx, epub, txt, rtf and
+                      many more
+                      <br />
+                      <strong>• Images:</strong> jpg, png, svg, gif, bmp, webp, tiff
+                      <br />
+                      <strong>• Spreadsheets:</strong> xlsx, xls, csv, numbers
+                      <br />
+                      <br />
+                      Non-markdown files are converted to markdown in the background.
+                      <br />
+                      <strong>Rate limit:</strong> 50 files or 100MB per 3 hours, whichever is
+                      reached first.
+                    </div>
+                  }
+                />
               </div>
             }
             description="Define patterns to include specific files, folders or tags (specified in the note property) in the project context."
diff --git a/src/components/modals/project/context-manage-modal.tsx b/src/components/modals/project/context-manage-modal.tsx
index 6e8250a..130c0f9 100644
--- a/src/components/modals/project/context-manage-modal.tsx
+++ b/src/components/modals/project/context-manage-modal.tsx
@@ -8,7 +8,6 @@ import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "@/componen
 import { ScrollArea } from "@/components/ui/scroll-area";
 import { SearchBar } from "@/components/ui/SearchBar";
 import { Separator } from "@/components/ui/separator";
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
 import { cn } from "@/lib/utils";
 import {
   createPatternSettingsValue,
@@ -25,15 +24,15 @@ import {
   FileText,
   FileVideo,
   FolderIcon,
-  HelpCircle,
   Plus,
   PlusCircle,
   TagIcon,
   XIcon,
 } from "lucide-react";
-import { App, Modal, Notice, TFile } from "obsidian";
+import { App, Modal, Notice, Platform, TFile } from "obsidian";
 import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
 import { createRoot, Root } from "react-dom/client";
+import { HelpTooltip } from "@/components/ui/help-tooltip";
 
 function FileIcon({ extension, size = "tw-size-4" }: { extension: string; size?: string }) {
   const ext = extension.toLowerCase().replace("*.", "");
@@ -78,18 +77,13 @@ const SectionHeader: React.FC<SectionHeaderProps> = ({
         <IconComponent className={`tw-mr-2 tw-size-4 ${iconColorClassName}`} />
         <h3 className={`tw-text-sm tw-font-semibold ${iconColorClassName}`}>{title}</h3>
         {tooltip && (
-          <TooltipProvider delayDuration={0}>
-            <Tooltip>
-              <TooltipTrigger asChild>
-                <HelpCircle className="tw-ml-2 tw-size-4 tw-text-muted" />
-              </TooltipTrigger>
-              <TooltipContent>
-                <div className="tw-max-w-80">{tooltip}</div>
-              </TooltipContent>
-            </Tooltip>
-          </TooltipProvider>
+          <HelpTooltip
+            buttonClassName="tw-ml-2 tw-size-4 tw-text-muted"
+            content={<div className="tw-max-w-80">{tooltip}</div>}
+          />
         )}
       </div>
+
       <Button
         variant="ghost"
         size="fit"
@@ -309,6 +303,7 @@ function isCategoryItem(item: DisplayItem): item is CategoryItem {
 }
 
 function ContextManage({ initialProject, onSave, onCancel, app }: ContextManageProps) {
+  const isMobile = Platform.isMobile;
   const { inclusions: inclusionPatterns, exclusions: exclusionPatterns } = useMemo(() => {
     return getMatchingPatterns({
       inclusions: initialProject?.contextSource.inclusions,
@@ -997,7 +992,7 @@ function ContextManage({ initialProject, onSave, onCancel, app }: ContextManageP
     <div className="tw-flex tw-h-full tw-flex-col">
       <ResizablePanelGroup direction="horizontal" className="tw-flex-1">
         {/* Left Sidebar - Navigation */}
-        <ResizablePanel defaultSize={30} minSize={20} maxSize={40}>
+        <ResizablePanel defaultSize={isMobile ? 35 : 30} minSize={20} maxSize={40}>
           <div className="tw-flex tw-h-full tw-flex-col">
             {/* Header */}
             <div className="tw-border-b tw-p-4">
@@ -1094,7 +1089,7 @@ function ContextManage({ initialProject, onSave, onCancel, app }: ContextManageP
                     )}
                     onClick={groupHandlers.click.ignoreFiles}
                   >
-                    Ignore Files ({ignoreItems.files.size})
+                    Files ({ignoreItems.files.size})
                   </div>
                 </div>
               </div>
@@ -1105,7 +1100,7 @@ function ContextManage({ initialProject, onSave, onCancel, app }: ContextManageP
         <ResizableHandle withHandle />
 
         {/* Right Content Area */}
-        <ResizablePanel defaultSize={70}>
+        <ResizablePanel defaultSize={isMobile ? 65 : 70}>
           <div className="tw-flex tw-h-full tw-flex-col">
             {/* Header */}
             <div className="tw-border-b tw-p-4">
diff --git a/src/components/ui/help-tooltip.tsx b/src/components/ui/help-tooltip.tsx
new file mode 100644
index 0000000..a57bfce
--- /dev/null
+++ b/src/components/ui/help-tooltip.tsx
@@ -0,0 +1,91 @@
+import { Button } from "@/components/ui/button";
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
+import { HelpCircle } from "lucide-react";
+import React, { useState } from "react";
+import { Platform } from "obsidian";
+
+interface TooltipProps {
+  content: React.ReactNode;
+  children?: React.ReactNode;
+  side?: "top" | "right" | "bottom" | "left";
+  delayDuration?: number;
+  contentClassName?: string;
+  buttonClassName?: string;
+}
+
+/**
+ * A versatile tooltip component that works on both desktop and mobile devices.
+ * On desktop, it shows on hover. On mobile, it shows on click.
+ *
+ * Can be used in two modes:
+ * 1. Help mode (default): Shows a help icon with tooltip
+ * 2. Wrapper mode (with children): Wraps any component with tooltip functionality
+ */
+export const HelpTooltip: React.FC<TooltipProps> = ({
+  content,
+  children,
+  side = "bottom",
+  delayDuration = 0,
+  contentClassName,
+  buttonClassName,
+}) => {
+  const isMobile = Platform.isMobile;
+  const [showTooltip, setShowTooltip] = useState(false);
+  const isClickingRef = React.useRef(false);
+
+  const handleTouchStart = () => {
+    if (isMobile) {
+      isClickingRef.current = true;
+    }
+  };
+
+  const handleClick = () => {
+    if (isMobile) {
+      setShowTooltip(!showTooltip);
+      // Reset the flag after a brief delay
+      setTimeout(() => {
+        isClickingRef.current = false;
+      }, 100);
+    }
+  };
+
+  return (
+    <TooltipProvider delayDuration={delayDuration}>
+      <Tooltip
+        open={showTooltip}
+        onOpenChange={(open) => {
+          // Ignore onOpenChange events on mobile when we're handling a click
+          if (isMobile && isClickingRef.current) {
+            return;
+          }
+          setShowTooltip(open);
+        }}
+      >
+        <TooltipTrigger asChild>
+          {children ? (
+            <div
+              onClick={handleClick}
+              onTouchStart={handleTouchStart}
+              className="tw-cursor-pointer"
+            >
+              {children}
+            </div>
+          ) : (
+            <Button
+              variant="ghost"
+              size="sm"
+              onClick={handleClick}
+              onTouchStart={handleTouchStart}
+              className={`tw-inline-flex tw-size-6 tw-items-center tw-justify-center tw-p-0 hover:tw-bg-transparent hover:tw-text-normal ${buttonClassName || ""}`}
+            >
+              <HelpCircle className="tw-size-4" />
+            </Button>
+          )}
+        </TooltipTrigger>
+        <TooltipContent side={side} className={contentClassName}>
+          {content}
+        </TooltipContent>
+      </Tooltip>
+    </TooltipProvider>
+  );
+};
diff --git a/src/components/ui/mobile-card.tsx b/src/components/ui/mobile-card.tsx
new file mode 100644
index 0000000..4c9f792
--- /dev/null
+++ b/src/components/ui/mobile-card.tsx
@@ -0,0 +1,211 @@
+import React, { useState } from "react";
+import { Card, CardContent, CardHeader } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
+import {
+  DropdownMenu,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+import { cn } from "@/lib/utils";
+import { useSortable } from "@dnd-kit/sortable";
+import { CSS } from "@dnd-kit/utilities";
+import { ChevronDown, ChevronRight, GripVertical, MoreVertical } from "lucide-react";
+
+export interface MobileCardDropdownAction<T = any> {
+  icon: React.ReactNode;
+  label: string;
+  onClick: (item: T) => void;
+  variant?: "default" | "destructive";
+}
+
+export interface MobileCardProps<T extends object> {
+  id: string;
+  item: T;
+  title: string;
+  subtitle?: string;
+  badge?: React.ReactNode;
+  icon?: React.ReactNode;
+  isDraggable?: boolean;
+  isExpandable?: boolean;
+  defaultExpanded?: boolean;
+  expandedContent?: React.ReactNode;
+  primaryAction?: {
+    icon: React.ReactNode;
+    onClick: (item: T) => void;
+    tooltip?: string;
+  };
+  dropdownActions?: Array<MobileCardDropdownAction<T>>;
+  containerRef?: React.RefObject<HTMLDivElement>;
+  className?: string;
+  onExpandToggle?: (expanded: boolean) => void;
+}
+
+export function MobileCard<T extends object>({
+  id,
+  item,
+  title,
+  subtitle,
+  badge,
+  icon,
+  isDraggable = false,
+  isExpandable = false,
+  defaultExpanded = false,
+  expandedContent,
+  primaryAction,
+  dropdownActions = [],
+  containerRef,
+  className,
+  onExpandToggle,
+}: MobileCardProps<T>) {
+  const [isExpanded, setIsExpanded] = useState(defaultExpanded);
+
+  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
+    id,
+    disabled: !isDraggable,
+  });
+
+  const style = {
+    transform: CSS.Transform.toString(transform),
+    transition,
+  };
+
+  const handleExpandToggle = () => {
+    if (isExpandable) {
+      const newExpanded = !isExpanded;
+      setIsExpanded(newExpanded);
+      onExpandToggle?.(newExpanded);
+    }
+  };
+
+  const showDropdownMenu = dropdownActions.length > 0;
+
+  return (
+    <Card
+      ref={setNodeRef}
+      style={style}
+      className={cn(
+        "tw-mb-2",
+        isDragging && [
+          "tw-opacity-90",
+          "tw-shadow-lg",
+          "tw-border-accent/50",
+          "tw-relative",
+          "tw-z-[9999]",
+          "tw-bg-primary",
+          "tw-rounded-lg",
+          "tw-transform-gpu",
+        ],
+        isDraggable && "tw-touch-manipulation",
+        className
+      )}
+    >
+      <CardHeader className="tw-p-3">
+        <div className="tw-flex tw-items-center tw-justify-between">
+          {/* Drag handle */}
+          {isDraggable && (
+            <div
+              className="tw-mr-2 tw-cursor-grab tw-touch-none active:tw-cursor-grabbing"
+              {...attributes}
+              {...listeners}
+            >
+              <GripVertical className="tw-size-4" />
+            </div>
+          )}
+
+          {/* Main content area */}
+          <div
+            className="tw-flex-1 tw-touch-auto"
+            onClick={isExpandable ? handleExpandToggle : undefined}
+            style={{ cursor: isExpandable ? "pointer" : "default" }}
+          >
+            <div className="tw-flex tw-items-center tw-gap-2">
+              {/* Expand/collapse icon */}
+              {isExpandable && (
+                <div className="tw-flex tw-size-3 tw-items-center tw-justify-center">
+                  {isExpanded ? (
+                    <ChevronDown className="tw-size-3 tw-stroke-[7]" />
+                  ) : (
+                    <ChevronRight className="tw-size-3 tw-stroke-[7]" />
+                  )}
+                </div>
+              )}
+
+              {/* Custom icon */}
+              {icon && <div className="tw-flex tw-items-center tw-justify-center">{icon}</div>}
+
+              {/* Title and subtitle area */}
+              <div className="tw-min-w-0 tw-flex-1">
+                <div className="tw-break-words tw-font-medium tw-leading-relaxed">
+                  {title}
+                  {badge && <span className="tw-ml-1 tw-inline-flex tw-items-center">{badge}</span>}
+                </div>
+                {subtitle && (
+                  <div className="tw-flex tw-items-center tw-gap-2">
+                    <span className="tw-bg-secondary tw-text-sm tw-text-muted">{subtitle}</span>
+                  </div>
+                )}
+              </div>
+            </div>
+          </div>
+
+          {/* Action buttons */}
+          <div className="tw-flex tw-items-center tw-gap-2">
+            {primaryAction && (
+              <Button
+                variant="ghost"
+                size="icon"
+                onClick={(e) => {
+                  e.stopPropagation();
+                  primaryAction.onClick(item);
+                }}
+                title={primaryAction.tooltip}
+              >
+                {primaryAction.icon}
+              </Button>
+            )}
+
+            {showDropdownMenu && (
+              <DropdownMenu>
+                <DropdownMenuTrigger asChild>
+                  <Button variant="ghost" size="icon">
+                    <MoreVertical className="tw-size-4" />
+                  </Button>
+                </DropdownMenuTrigger>
+                <DropdownMenuContent align="end" container={containerRef?.current}>
+                  {dropdownActions.map((action, index) => (
+                    <DropdownMenuItem
+                      key={index}
+                      onClick={(e) => {
+                        e.stopPropagation();
+                        action.onClick(item);
+                      }}
+                      className={cn(action.variant === "destructive" && "tw-text-error")}
+                    >
+                      <span className="tw-mr-2 tw-flex tw-size-4 tw-items-center tw-justify-center">
+                        {action.icon}
+                      </span>
+                      {action.label}
+                    </DropdownMenuItem>
+                  ))}
+                </DropdownMenuContent>
+              </DropdownMenu>
+            )}
+          </div>
+        </div>
+      </CardHeader>
+
+      {/* Expandable content */}
+      {isExpandable && (
+        <div
+          className={cn(
+            "tw-transition-all tw-duration-300 tw-ease-in-out",
+            isExpanded ? "tw-max-h-96 tw-opacity-100" : "tw-max-h-0 tw-overflow-hidden tw-opacity-0"
+          )}
+        >
+          <CardContent className="tw-p-3 tw-pt-0">{expandedContent}</CardContent>
+        </div>
+      )}
+    </Card>
+  );
+}
diff --git a/src/components/ui/parameter-controls.tsx b/src/components/ui/parameter-controls.tsx
index 2d5d69c..2a03953 100644
--- a/src/components/ui/parameter-controls.tsx
+++ b/src/components/ui/parameter-controls.tsx
@@ -9,7 +9,8 @@ import {
   SelectTrigger,
   SelectValue,
 } from "@/components/ui/select";
-import { HelpCircle, Zap, ZapOff } from "lucide-react";
+import { Zap, ZapOff } from "lucide-react";
+import { HelpTooltip } from "@/components/ui/help-tooltip";
 
 interface BaseParameterControlProps {
   label: string;
@@ -83,14 +84,10 @@ export function ParameterControl(props: ParameterControlProps) {
               {label}
             </span>
             {helpText && (
-              <Tooltip>
-                <TooltipTrigger asChild>
-                  <HelpCircle className="tw-size-4 tw-text-muted" />
-                </TooltipTrigger>
-                <TooltipContent side="bottom">
-                  <div className="tw-max-w-[300px]">{helpText}</div>
-                </TooltipContent>
-              </Tooltip>
+              <HelpTooltip
+                buttonClassName="tw-size-4 tw-text-muted"
+                content={<div className="tw-max-w-[300px]">{helpText}</div>}
+              />
             )}
           </div>
           <div className={`tw-flex tw-items-center ${optional ? "tw-gap-3" : "tw-gap-0"}`}>
diff --git a/src/components/ui/setting-tabs.tsx b/src/components/ui/setting-tabs.tsx
index ae4a6cd..e3740e0 100644
--- a/src/components/ui/setting-tabs.tsx
+++ b/src/components/ui/setting-tabs.tsx
@@ -33,17 +33,16 @@ export const TabItem: React.FC<TabItemProps> = ({ tab, isSelected, onClick, isFi
         "tw-whitespace-nowrap",
         "tw-text-sm",
         "tw-border tw-border-solid tw-border-border",
-        "tw-rounded-b-[2px] tw-rounded-t-md",
+        "tw-rounded-b-[2px] tw-rounded-t-sm",
         "tw-bg-primary",
         "tw-transition-all tw-duration-300 tw-ease-in-out",
         "hover:tw-border-interactive-accent",
         isSelected && [
           "!tw-bg-interactive-accent",
           "tw-text-on-accent",
-          "!tw-px-3",
           "!tw-max-w-full",
           "tw-transition-all tw-duration-300 tw-ease-in-out",
-          "tw-delay-200",
+          "tw-delay-100",
         ],
         "lg:tw-max-w-32",
         "md:tw-max-w-32"
diff --git a/src/constants.ts b/src/constants.ts
index b66be18..c56f8ae 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -11,8 +11,10 @@ export const USER_SENDER = "user";
 export const AI_SENDER = "ai";
 
 // Default folder names
-export const DEFAULT_CHAT_HISTORY_FOLDER = "copilot/copilot-conversations";
-export const DEFAULT_CUSTOM_PROMPTS_FOLDER = "copilot/copilot-custom-prompts";
+export const COPILOT_FOLDER_ROOT = "copilot";
+export const DEFAULT_CHAT_HISTORY_FOLDER = `${COPILOT_FOLDER_ROOT}/copilot-conversations`;
+export const DEFAULT_CUSTOM_PROMPTS_FOLDER = `${COPILOT_FOLDER_ROOT}/copilot-custom-prompts`;
+export const DEFAULT_QA_EXCLUSIONS_SETTING = COPILOT_FOLDER_ROOT;
 export const DEFAULT_SYSTEM_PROMPT = `You are Obsidian Copilot, a helpful assistant that integrates AI to Obsidian note-taking.
   1. Never mention that you do not have access to something. Always rely on the user provided context.
   2. Always answer to the best of your knowledge. If you are unsure about something, say so and ask the user to provide more context.
@@ -150,6 +152,8 @@ export enum ChatModels {
   OPENROUTER_GEMINI_2_5_FLASH = "google/gemini-2.5-flash",
   OPENROUTER_GEMINI_2_5_PRO = "google/gemini-2.5-pro",
   OPENROUTER_GEMINI_2_5_FLASH_LITE = "google/gemini-2.5-flash-lite",
+  OPENROUTER_GPT_41 = "openai/gpt-4.1",
+  OPENROUTER_GPT_41_MINI = "openai/gpt-4.1-mini",
 }
 
 // Model Providers
@@ -220,6 +224,24 @@ export const BUILTIN_CHAT_MODELS: CustomModel[] = [
     projectEnabled: true,
     capabilities: [ModelCapability.VISION],
   },
+  {
+    name: ChatModels.OPENROUTER_GPT_41,
+    provider: ChatModelProviders.OPENROUTERAI,
+    enabled: true,
+    isBuiltIn: true,
+    core: false,
+    projectEnabled: true,
+    capabilities: [ModelCapability.VISION],
+  },
+  {
+    name: ChatModels.OPENROUTER_GPT_41_MINI,
+    provider: ChatModelProviders.OPENROUTERAI,
+    enabled: true,
+    isBuiltIn: true,
+    core: false,
+    projectEnabled: true,
+    capabilities: [ModelCapability.VISION],
+  },
   {
     name: ChatModels.GPT_5,
     provider: ChatModelProviders.OPENAI,
@@ -708,7 +730,7 @@ export const DEFAULT_SETTINGS: CopilotSettings = {
   defaultOpenArea: DEFAULT_OPEN_AREA.VIEW,
   customPromptsFolder: DEFAULT_CUSTOM_PROMPTS_FOLDER,
   indexVaultToVectorStore: VAULT_VECTOR_STORE_STRATEGY.ON_MODE_SWITCH,
-  qaExclusions: `${DEFAULT_CHAT_HISTORY_FOLDER},${DEFAULT_CUSTOM_PROMPTS_FOLDER}`,
+  qaExclusions: DEFAULT_QA_EXCLUSIONS_SETTING,
   qaInclusions: "",
   chatNoteContextPath: "",
   chatNoteContextTags: [],
@@ -754,6 +776,7 @@ export const DEFAULT_SETTINGS: CopilotSettings = {
   ],
   reasoningEffort: DEFAULT_MODEL_SETTING.REASONING_EFFORT,
   verbosity: DEFAULT_MODEL_SETTING.VERBOSITY,
+  enableInlineCitations: true,
 };
 
 export const EVENT_NAMES = {
diff --git a/src/logFileManager.ts b/src/logFileManager.ts
index e8c40e2..d91739e 100644
--- a/src/logFileManager.ts
+++ b/src/logFileManager.ts
@@ -7,13 +7,13 @@ type LogLevel = "INFO" | "WARN" | "ERROR";
 /**
  * Manages a rolling log file that keeps the last N entries and works on desktop and mobile.
  * - Writes to <vault>/copilot/copilot-log.md
- * - Maintains an in-memory ring buffer of the last 1000 entries
+ * - Maintains an in-memory ring buffer of the last 500 entries
  * - Debounced flush to reduce I/O; single-line entries to preserve accurate line limits
  */
 class LogFileManager {
   private static instance: LogFileManager;
 
-  private readonly maxLines = 1000;
+  private readonly maxLines = 500;
   private readonly maxLineChars = 8000; // guard against extremely large entries
   private buffer: string[] = [];
   private initialized = false;
@@ -30,7 +30,7 @@ class LogFileManager {
     return "copilot/copilot-log.md"; // under copilot/
   }
 
-  /** Ensure buffer is loaded with up to last 1000 lines from existing file. */
+  /** Ensure buffer is loaded with up to last 500 lines from existing file. */
   private async ensureInitialized() {
     if (this.initialized) return;
     try {
diff --git a/src/search/indexEventHandler.ts b/src/search/indexEventHandler.ts
index 58173c2..d3e8f54 100644
--- a/src/search/indexEventHandler.ts
+++ b/src/search/indexEventHandler.ts
@@ -1,6 +1,7 @@
 import { getChainType } from "@/aiParams";
 import { ChainType } from "@/chainFactory";
-import { getSettings } from "@/settings/model";
+import { logInfo } from "@/logger";
+import { getSettings, subscribeToSettingsChange } from "@/settings/model";
 import { App, MarkdownView, Platform, TAbstractFile, TFile } from "obsidian";
 import { DBOperations } from "./dbOperations";
 import { IndexOperations } from "./indexOperations";
@@ -12,24 +13,65 @@ export class IndexEventHandler {
   private debounceTimer: number | null = null;
   private lastActiveFile: TFile | null = null;
   private lastActiveFileMtime: number | null = null;
+  private listenersActive = false;
 
   constructor(
     private app: App,
     private indexOps: IndexOperations,
     private dbOps: DBOperations
   ) {
-    this.initializeEventListeners();
+    this.syncEventListeners();
+    subscribeToSettingsChange(() => {
+      this.syncEventListeners();
+    });
   }
 
-  private initializeEventListeners() {
-    if (getSettings().debug) {
-      console.log("Copilot Plus: Initializing event listeners");
+  /**
+   * Determine whether indexing-related events should be processed based on current settings.
+   *
+   * @returns {boolean} True when semantic search indexing should run.
+   */
+  private shouldHandleEvents(): boolean {
+    return getSettings().enableSemanticSearchV3;
+  }
+
+  /**
+   * Ensure event listeners are registered only when semantic search indexing is enabled.
+   */
+  private syncEventListeners(): void {
+    const shouldListen = this.shouldHandleEvents();
+    if (shouldListen && !this.listenersActive) {
+      logInfo("Copilot Plus: Initializing semantic index event listeners");
+      this.app.workspace.on("active-leaf-change", this.handleActiveLeafChange);
+      this.app.vault.on("delete", this.handleFileDelete);
+      this.listenersActive = true;
+    } else if (!shouldListen && this.listenersActive) {
+      this.teardownEventListeners();
     }
-    this.app.workspace.on("active-leaf-change", this.handleActiveLeafChange);
-    this.app.vault.on("delete", this.handleFileDelete);
+  }
+
+  /**
+   * Remove indexing event listeners and reset any pending timers or cached state.
+   */
+  private teardownEventListeners(): void {
+    if (!this.listenersActive) {
+      return;
+    }
+    this.app.workspace.off("active-leaf-change", this.handleActiveLeafChange);
+    this.app.vault.off("delete", this.handleFileDelete);
+    if (this.debounceTimer !== null) {
+      window.clearTimeout(this.debounceTimer);
+      this.debounceTimer = null;
+    }
+    this.lastActiveFile = null;
+    this.lastActiveFileMtime = null;
+    this.listenersActive = false;
   }
 
   private handleActiveLeafChange = async (leaf: any) => {
+    if (!this.shouldHandleEvents()) {
+      return;
+    }
     if (Platform.isMobile && getSettings().disableIndexOnMobile) {
       return;
     }
@@ -73,6 +115,9 @@ export class IndexEventHandler {
   };
 
   private debouncedReindexFile = (file: TFile) => {
+    if (!this.shouldHandleEvents()) {
+      return;
+    }
     if (this.debounceTimer !== null) {
       window.clearTimeout(this.debounceTimer);
     }
@@ -87,28 +132,19 @@ export class IndexEventHandler {
   };
 
   private handleFileDelete = async (file: TAbstractFile) => {
+    if (!this.shouldHandleEvents()) {
+      return;
+    }
     if (file instanceof TFile) {
       await this.dbOps.removeDocs(file.path);
     }
   };
 
   public cleanup() {
-    if (this.debounceTimer !== null) {
-      window.clearTimeout(this.debounceTimer);
-    }
-    this.app.workspace.off("active-leaf-change", this.handleActiveLeafChange);
-    this.app.vault.off("delete", this.handleFileDelete);
+    this.teardownEventListeners();
   }
 
   public unload() {
-    if (this.debounceTimer !== null) {
-      window.clearTimeout(this.debounceTimer);
-    }
-    // Clean up file tracking
-    this.lastActiveFile = null;
-    this.lastActiveFileMtime = null;
-
-    this.app.workspace.off("active-leaf-change", this.handleActiveLeafChange);
-    this.app.vault.off("delete", this.handleFileDelete);
+    this.teardownEventListeners();
   }
 }
diff --git a/src/settings/model.test.ts b/src/settings/model.test.ts
new file mode 100644
index 0000000..16d4664
--- /dev/null
+++ b/src/settings/model.test.ts
@@ -0,0 +1,30 @@
+import { COPILOT_FOLDER_ROOT, DEFAULT_QA_EXCLUSIONS_SETTING } from "@/constants";
+import { sanitizeQaExclusions } from "@/settings/model";
+
+describe("sanitizeQaExclusions", () => {
+  it("defaults to copilot root when value is not a string", () => {
+    expect(sanitizeQaExclusions(undefined)).toBe(encodeURIComponent(DEFAULT_QA_EXCLUSIONS_SETTING));
+  });
+
+  it("keeps slash-only patterns distinct from canonical entries", () => {
+    const rawValue = `${encodeURIComponent("///")},${encodeURIComponent(COPILOT_FOLDER_ROOT)}`;
+
+    const sanitized = sanitizeQaExclusions(rawValue);
+
+    expect(sanitized.split(",")).toEqual([
+      encodeURIComponent("///"),
+      encodeURIComponent(COPILOT_FOLDER_ROOT),
+    ]);
+  });
+
+  it("normalizes trailing slashes to canonical path keys", () => {
+    const rawValue = `${encodeURIComponent("folder/")},${encodeURIComponent("folder//")}`;
+
+    const sanitized = sanitizeQaExclusions(rawValue);
+
+    expect(sanitized.split(",")).toEqual([
+      encodeURIComponent("folder/"),
+      encodeURIComponent(COPILOT_FOLDER_ROOT),
+    ]);
+  });
+});
diff --git a/src/settings/model.ts b/src/settings/model.ts
index afd2dee..f6384bf 100644
--- a/src/settings/model.ts
+++ b/src/settings/model.ts
@@ -7,7 +7,9 @@ import { type ChainType } from "@/chainFactory";
 import {
   BUILTIN_CHAT_MODELS,
   BUILTIN_EMBEDDING_MODELS,
+  COPILOT_FOLDER_ROOT,
   DEFAULT_OPEN_AREA,
+  DEFAULT_QA_EXCLUSIONS_SETTING,
   DEFAULT_SETTINGS,
   DEFAULT_SYSTEM_PROMPT,
   EmbeddingModelProviders,
@@ -134,6 +136,8 @@ export interface CopilotSettings {
   reasoningEffort: "minimal" | "low" | "medium" | "high";
   /** Default verbosity level for models that support it */
   verbosity: "low" | "medium" | "high";
+  /** Enable inline citations in AI responses with footnote-style references */
+  enableInlineCitations: boolean;
 }
 
 export const settingsStore = createStore();
@@ -147,6 +151,42 @@ export function setSettings(settings: Partial<CopilotSettings>) {
   settingsStore.set(settingsAtom, newSettings);
 }
 
+/**
+ * Normalize QA exclusion patterns and guarantee the Copilot folder root is excluded.
+ * @param rawValue - Persisted QA exclusion setting value.
+ * @returns Encoded QA exclusion patterns string.
+ */
+export function sanitizeQaExclusions(rawValue: unknown): string {
+  const rawValueString = typeof rawValue === "string" ? rawValue : DEFAULT_QA_EXCLUSIONS_SETTING;
+
+  const decodedPatterns: string[] = rawValueString
+    .split(",")
+    .map((pattern: string) => decodeURIComponent(pattern.trim()))
+    .filter((pattern: string) => pattern.length > 0);
+
+  const canonicalToOriginalPattern = new Map<string, string>();
+
+  decodedPatterns.forEach((pattern) => {
+    const canonical = pattern.replace(/\/+$/, "");
+    const canonicalKey = canonical.length > 0 ? canonical : pattern;
+    if (canonicalKey === COPILOT_FOLDER_ROOT) {
+      canonicalToOriginalPattern.set(COPILOT_FOLDER_ROOT, COPILOT_FOLDER_ROOT);
+      return;
+    }
+    if (!canonicalToOriginalPattern.has(canonicalKey)) {
+      const normalizedValue =
+        canonical.length > 0 && pattern.endsWith("/") ? `${canonical}/` : pattern;
+      canonicalToOriginalPattern.set(canonicalKey, normalizedValue);
+    }
+  });
+
+  canonicalToOriginalPattern.set(COPILOT_FOLDER_ROOT, COPILOT_FOLDER_ROOT);
+
+  return Array.from(canonicalToOriginalPattern.values())
+    .map((pattern) => encodeURIComponent(pattern))
+    .join(",");
+}
+
 /**
  * Sets a single setting in the atom.
  */
@@ -275,6 +315,11 @@ export function sanitizeSettings(settings: CopilotSettings): CopilotSettings {
     sanitizedSettings.passMarkdownImages = DEFAULT_SETTINGS.passMarkdownImages;
   }
 
+  // Ensure enableInlineCitations has a default value
+  if (typeof sanitizedSettings.enableInlineCitations !== "boolean") {
+    sanitizedSettings.enableInlineCitations = DEFAULT_SETTINGS.enableInlineCitations;
+  }
+
   // Ensure enableCustomPromptTemplating has a default value
   if (typeof sanitizedSettings.enableCustomPromptTemplating !== "boolean") {
     sanitizedSettings.enableCustomPromptTemplating = DEFAULT_SETTINGS.enableCustomPromptTemplating;
@@ -317,6 +362,8 @@ export function sanitizeSettings(settings: CopilotSettings): CopilotSettings {
   sanitizedSettings.customPromptsFolder =
     promptsFolder.length > 0 ? promptsFolder : DEFAULT_SETTINGS.customPromptsFolder;
 
+  sanitizedSettings.qaExclusions = sanitizeQaExclusions(settingsToSanitize.qaExclusions);
+
   return sanitizedSettings;
 }
 
diff --git a/src/settings/v2/SettingsMainV2.tsx b/src/settings/v2/SettingsMainV2.tsx
index 6a2d071..4da2208 100644
--- a/src/settings/v2/SettingsMainV2.tsx
+++ b/src/settings/v2/SettingsMainV2.tsx
@@ -49,7 +49,7 @@ const SettingsContent: React.FC = () => {
 
   return (
     <div className="tw-flex tw-flex-col">
-      <div className="tw-inline-flex tw-rounded-lg">
+      <div className="tw-flex tw-flex-wrap tw-rounded-lg">
         {tabs.map((tab, index) => (
           <TabItem
             key={tab.id}
diff --git a/src/settings/v2/components/BasicSettings.tsx b/src/settings/v2/components/BasicSettings.tsx
index 20a17a2..5be417f 100644
--- a/src/settings/v2/components/BasicSettings.tsx
+++ b/src/settings/v2/components/BasicSettings.tsx
@@ -3,14 +3,14 @@ import { Button } from "@/components/ui/button";
 import { Input } from "@/components/ui/input";
 import { getModelDisplayWithIcons } from "@/components/ui/model-display";
 import { SettingItem } from "@/components/ui/setting-item";
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
+import { HelpTooltip } from "@/components/ui/help-tooltip";
 import { DEFAULT_OPEN_AREA, PLUS_UTM_MEDIUMS } from "@/constants";
 import { cn } from "@/lib/utils";
 import { createPlusPageUrl } from "@/plusUtils";
 import { getModelKeyFromModel, updateSetting, useSettingsValue } from "@/settings/model";
 import { PlusSettings } from "@/settings/v2/components/PlusSettings";
 import { checkModelApiKey, formatDateTime } from "@/utils";
-import { HelpCircle, Key, Loader2 } from "lucide-react";
+import { Key, Loader2 } from "lucide-react";
 import { Notice } from "obsidian";
 import React, { useState } from "react";
 import { ApiKeyDialog } from "./ApiKeyDialog";
@@ -104,12 +104,9 @@ export const BasicSettings: React.FC = () => {
                   <span className="tw-leading-none">
                     Configure API keys for different AI providers
                   </span>
-                  <TooltipProvider delayDuration={0}>
-                    <Tooltip>
-                      <TooltipTrigger asChild>
-                        <HelpCircle className="tw-size-4" />
-                      </TooltipTrigger>
-                      <TooltipContent className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2 tw-py-4">
+                  <HelpTooltip
+                    content={
+                      <div className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2 tw-py-4">
                         <div className="tw-text-sm tw-font-medium tw-text-accent">
                           API key required for chat and QA features
                         </div>
@@ -117,9 +114,9 @@ export const BasicSettings: React.FC = () => {
                           To enable chat and QA functionality, please provide an API key from your
                           selected provider.
                         </div>
-                      </TooltipContent>
-                    </Tooltip>
-                  </TooltipProvider>
+                      </div>
+                    }
+                  />
                 </div>
               }
             >
@@ -141,12 +138,9 @@ export const BasicSettings: React.FC = () => {
             description={
               <div className="tw-flex tw-items-center tw-gap-1.5">
                 <span className="tw-leading-none">Select the Chat model to use</span>
-                <TooltipProvider delayDuration={0}>
-                  <Tooltip>
-                    <TooltipTrigger asChild>
-                      <HelpCircle className="tw-size-4" />
-                    </TooltipTrigger>
-                    <TooltipContent className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2 tw-py-4">
+                <HelpTooltip
+                  content={
+                    <div className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2 tw-py-4">
                       <div className="tw-text-sm tw-font-medium tw-text-accent">
                         Default model is OpenRouter Gemini 2.5 Flash
                       </div>
@@ -154,9 +148,9 @@ export const BasicSettings: React.FC = () => {
                         Set your OpenRouter API key in &apos;API keys&apos; to use this model, or
                         select a different model from another provider.
                       </div>
-                    </TooltipContent>
-                  </Tooltip>
-                </TooltipProvider>
+                    </div>
+                  }
+                />
               </div>
             }
             value={defaultModelActivated ? settings.defaultModelKey : "Select Model"}
@@ -188,12 +182,9 @@ export const BasicSettings: React.FC = () => {
             description={
               <div className="tw-flex tw-items-center tw-gap-1.5">
                 <span className="tw-leading-none">Select the default chat mode</span>
-                <TooltipProvider delayDuration={0}>
-                  <Tooltip>
-                    <TooltipTrigger asChild>
-                      <HelpCircle className="tw-size-4" />
-                    </TooltipTrigger>
-                    <TooltipContent className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2">
+                <HelpTooltip
+                  content={
+                    <div className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2">
                       <ul className="tw-pl-4 tw-text-sm tw-text-muted">
                         <li>
                           <strong>Chat:</strong> Regular chat mode for general conversations and
@@ -218,9 +209,9 @@ export const BasicSettings: React.FC = () => {
                           for more details.
                         </li>
                       </ul>
-                    </TooltipContent>
-                  </Tooltip>
-                </TooltipProvider>
+                    </div>
+                  }
+                />
               </div>
             }
             value={settings.defaultChainType}
@@ -269,12 +260,9 @@ export const BasicSettings: React.FC = () => {
                 <span className="tw-leading-none">
                   Customize the format of saved conversation note names.
                 </span>
-                <TooltipProvider delayDuration={0}>
-                  <Tooltip>
-                    <TooltipTrigger asChild>
-                      <HelpCircle className="tw-size-4" />
-                    </TooltipTrigger>
-                    <TooltipContent className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2 tw-py-4">
+                <HelpTooltip
+                  content={
+                    <div className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2 tw-py-4">
                       <div className="tw-text-sm tw-font-medium tw-text-accent">
                         Note: All the following variables must be included in the template.
                       </div>
@@ -298,9 +286,9 @@ export const BasicSettings: React.FC = () => {
                           20250114_153232__polish_this_article_[[Readme]]
                         </i>
                       </div>
-                    </TooltipContent>
-                  </Tooltip>
-                </TooltipProvider>
+                    </div>
+                  }
+                />
               </div>
             }
           >
diff --git a/src/settings/v2/components/CommandSettings.tsx b/src/settings/v2/components/CommandSettings.tsx
index 03b3616..ec83e42 100644
--- a/src/settings/v2/components/CommandSettings.tsx
+++ b/src/settings/v2/components/CommandSettings.tsx
@@ -1,7 +1,8 @@
-import { useCustomCommands } from "@/commands/state";
+import React, { useMemo, useRef } from "react";
 import { Button } from "@/components/ui/button";
-import { Copy, GripVertical, Info, Lightbulb, PenLine, Plus, Trash2 } from "lucide-react";
-import React, { useMemo } from "react";
+import { useCustomCommands } from "@/commands/state";
+import { MobileCard, MobileCardDropdownAction } from "@/components/ui/mobile-card";
+import { Copy, GripVertical, Lightbulb, PenLine, Plus, Trash2 } from "lucide-react";
 
 import { Checkbox } from "@/components/ui/checkbox";
 import {
@@ -12,9 +13,6 @@ import {
   TableHeader,
   TableRow,
 } from "@/components/ui/table";
-import { cn } from "@/lib/utils";
-import { updateSetting, useSettingsValue } from "@/settings/model";
-import { PromptSortStrategy } from "@/types";
 import {
   closestCenter,
   DndContext,
@@ -32,6 +30,10 @@ import {
 } from "@dnd-kit/sortable";
 import { CSS } from "@dnd-kit/utilities";
 
+import { cn } from "@/lib/utils";
+import { updateSetting, useSettingsValue } from "@/settings/model";
+import { PromptSortStrategy } from "@/types";
+import { HelpTooltip } from "@/components/ui/help-tooltip";
 import { EMPTY_COMMAND } from "@/commands/constants";
 import { CustomCommandManager } from "@/commands/customCommandManager";
 import { CustomCommandSettingsModal } from "@/commands/CustomCommandSettingsModal";
@@ -44,9 +46,124 @@ import { generateDefaultCommands } from "@/commands/migrator";
 import { CustomCommand } from "@/commands/type";
 import { ConfirmModal } from "@/components/modals/ConfirmModal";
 import { SettingItem } from "@/components/ui/setting-item";
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
 import { Notice } from "obsidian";
 
+const MobileCommandCard: React.FC<{
+  command: CustomCommand;
+  commands: CustomCommand[];
+  onUpdate: (newCommand: CustomCommand, prevCommandTitle: string) => void;
+  onRemove: (command: CustomCommand) => void;
+  onCopy: (command: CustomCommand) => void;
+  containerRef: React.RefObject<HTMLDivElement>;
+}> = ({ command, commands, onUpdate, onRemove, onCopy, containerRef }) => {
+  const handleEdit = (cmd: CustomCommand) => {
+    const modal = new CustomCommandSettingsModal(app, commands, cmd, async (updatedCommand) => {
+      await onUpdate(updatedCommand, cmd.title);
+    });
+    modal.open();
+  };
+
+  const dropdownActions: MobileCardDropdownAction<CustomCommand>[] = [
+    {
+      icon: <PenLine className="tw-size-4" />,
+      label: "Edit",
+      onClick: handleEdit,
+    },
+    {
+      icon: <Copy className="tw-size-4" />,
+      label: "Copy",
+      onClick: onCopy,
+    },
+    {
+      icon: <Trash2 className="tw-size-4" />,
+      label: "Delete",
+      onClick: (cmd) => {
+        new ConfirmModal(
+          app,
+          () => onRemove(cmd),
+          `Are you sure you want to delete the command "${cmd.title}"? This will permanently remove the command file and cannot be undone.`,
+          "Delete Command",
+          "Delete",
+          "Cancel"
+        ).open();
+      },
+      variant: "destructive",
+    },
+  ];
+
+  const expandedContent = (
+    <div className="tw-flex tw-flex-wrap tw-justify-around">
+      <div className="tw-flex tw-items-center tw-justify-between tw-gap-2">
+        <div className="tw-flex tw-items-center tw-gap-1">
+          <span className="tw-text-sm tw-font-medium">In Menu</span>
+          <HelpTooltip
+            content={
+              <div className="tw-max-w-xs tw-text-xs">
+                If enabled, the command will be available in the context menu when you right-click
+                in the editor.
+              </div>
+            }
+          />
+        </div>
+        <Checkbox
+          checked={command.showInContextMenu}
+          onCheckedChange={(checked) => {
+            onUpdate(
+              {
+                ...command,
+                showInContextMenu: checked === true,
+              },
+              command.title
+            );
+          }}
+        />
+      </div>
+      <div className="tw-flex tw-items-center tw-justify-between  tw-gap-2">
+        <div className="tw-flex tw-items-center tw-gap-1">
+          <span className="tw-text-sm tw-font-medium">In Slash</span>
+          <HelpTooltip
+            content={
+              <div className="tw-max-w-xs tw-text-xs">
+                If enabled, the command will be available as a slash command in the chat.
+              </div>
+            }
+          />
+        </div>
+        <Checkbox
+          checked={command.showInSlashMenu}
+          onCheckedChange={(checked) =>
+            onUpdate(
+              {
+                ...command,
+                showInSlashMenu: checked === true,
+              },
+              command.title
+            )
+          }
+        />
+      </div>
+    </div>
+  );
+
+  return (
+    <MobileCard
+      id={command.title}
+      item={command}
+      title={command.title}
+      isDraggable
+      isExpandable
+      expandedContent={expandedContent}
+      primaryAction={{
+        icon: <PenLine className="tw-size-4" />,
+        onClick: handleEdit,
+        tooltip: "Edit Command",
+      }}
+      dropdownActions={dropdownActions}
+      containerRef={containerRef}
+    />
+  );
+};
+
 const SortableTableRow: React.FC<{
   command: CustomCommand;
   commands: CustomCommand[];
@@ -168,6 +285,7 @@ export const CommandSettings: React.FC = () => {
   }, [rawCommands]);
 
   const settings = useSettingsValue();
+  const containerRef = useRef<HTMLDivElement>(null);
   const sensors = useSensors(
     useSensor(PointerSensor, {
       activationConstraint: {
@@ -240,8 +358,40 @@ export const CommandSettings: React.FC = () => {
     await CustomCommandManager.getInstance().reorderCommands(newCommands);
   };
 
+  // Mobile view rendering
+  const renderMobileView = () => (
+    <div className="tw-relative md:tw-hidden">
+      <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
+        <SortableContext
+          items={commands.map((command) => command.title)}
+          strategy={verticalListSortingStrategy}
+        >
+          <div className="tw-space-y-2">
+            {commands.length === 0 ? (
+              <div className="tw-rounded-lg tw-border tw-border-border tw-bg-primary tw-p-8 tw-text-center tw-text-muted">
+                No custom prompt files found.
+              </div>
+            ) : (
+              commands.map((command) => (
+                <MobileCommandCard
+                  key={command.title}
+                  command={command}
+                  commands={commands}
+                  onUpdate={handleUpdate}
+                  onRemove={handleRemove}
+                  onCopy={handleCopy}
+                  containerRef={containerRef}
+                />
+              ))
+            )}
+          </div>
+        </SortableContext>
+      </DndContext>
+    </div>
+  );
+
   return (
-    <div className="tw-space-y-4">
+    <div className="tw-space-y-4" ref={containerRef}>
       <section>
         <div className="tw-mb-4 tw-flex tw-flex-col tw-gap-2">
           <div className="tw-text-xl tw-font-bold">Custom Commands</div>
@@ -295,7 +445,7 @@ export const CommandSettings: React.FC = () => {
         </div>
 
         <div className="tw-flex tw-flex-col tw-gap-4">
-          <div className="tw-flex tw-w-full tw-justify-between">
+          <div className="tw-flex tw-w-full tw-justify-between tw-gap-2 md:tw-justify-end">
             <div>
               <Button
                 variant="secondary"
@@ -308,7 +458,7 @@ export const CommandSettings: React.FC = () => {
                   ).open()
                 }
               >
-                Generate Default Commands
+                Generate Default
               </Button>
             </div>
             <Button
@@ -329,82 +479,83 @@ export const CommandSettings: React.FC = () => {
                 modal.open();
               }}
             >
-              <Plus className="tw-size-4" />
-              Add Command
+              <Plus className="tw-size-2 md:tw-size-4" />
+              Add Cmd
             </Button>
           </div>
-          <DndContext
-            sensors={sensors}
-            collisionDetection={closestCenter}
-            onDragEnd={handleDragEnd}
-          >
-            <Table>
-              <TableHeader>
-                <TableRow>
-                  <TableHead className="tw-w-10"></TableHead>
-                  <TableHead>Name</TableHead>
-                  <TableHead className="tw-w-24 tw-text-center">
-                    <div className="tw-flex tw-items-center tw-justify-center tw-gap-1">
-                      In Menu
-                      <TooltipProvider>
-                        <Tooltip>
-                          <TooltipTrigger asChild>
-                            <Info className="tw-size-4" />
-                          </TooltipTrigger>
-                          <TooltipContent className="tw-max-w-xs tw-text-xs">
-                            If enabled, the command will be available in the context menu when you
-                            right-click in the editor.
-                          </TooltipContent>
-                        </Tooltip>
-                      </TooltipProvider>
-                    </div>
-                  </TableHead>
-                  <TableHead className="tw-w-28 tw-text-center">
-                    <div className="tw-flex tw-items-center tw-justify-center tw-gap-1">
-                      Slash Cmd
-                      <TooltipProvider>
-                        <Tooltip>
-                          <TooltipTrigger asChild>
-                            <Info className="tw-size-4" />
-                          </TooltipTrigger>
-                          <TooltipContent className="tw-max-w-xs tw-text-xs">
-                            If enabled, the command will be available as a slash command in the
-                            chat.
-                          </TooltipContent>
-                        </Tooltip>
-                      </TooltipProvider>
-                    </div>
-                  </TableHead>
-                  <TableHead className="tw-w-32 tw-text-center">Actions</TableHead>
-                </TableRow>
-              </TableHeader>
-              <SortableContext
-                items={commands.map((command) => command.title)}
-                strategy={verticalListSortingStrategy}
-              >
-                <TableBody>
-                  {commands.length === 0 ? (
-                    <TableRow>
-                      <TableCell colSpan={5} className="tw-py-8 tw-text-center tw-text-muted">
-                        No custom prompt files found.
-                      </TableCell>
-                    </TableRow>
-                  ) : (
-                    commands.map((command) => (
-                      <SortableTableRow
-                        key={command.title}
-                        command={command}
-                        commands={commands}
-                        onUpdate={handleUpdate}
-                        onRemove={handleRemove}
-                        onCopy={handleCopy}
-                      />
-                    ))
-                  )}
-                </TableBody>
-              </SortableContext>
-            </Table>
-          </DndContext>
+
+          {/* Desktop view */}
+          <div className="tw-hidden md:tw-block">
+            <DndContext
+              sensors={sensors}
+              collisionDetection={closestCenter}
+              onDragEnd={handleDragEnd}
+            >
+              <Table>
+                <TableHeader>
+                  <TableRow>
+                    <TableHead className="tw-w-10"></TableHead>
+                    <TableHead>Name</TableHead>
+                    <TableHead className="tw-w-24 tw-text-center">
+                      <div className="tw-flex tw-items-center tw-justify-center tw-gap-1">
+                        In Menu
+                        <HelpTooltip
+                          content={
+                            <div className="tw-max-w-xs tw-text-xs">
+                              If enabled, the command will be available in the context menu when you
+                              right-click in the editor.
+                            </div>
+                          }
+                        />
+                      </div>
+                    </TableHead>
+                    <TableHead className="tw-w-28 tw-text-center">
+                      <div className="tw-flex tw-items-center tw-justify-center tw-gap-1">
+                        Slash Cmd
+                        <HelpTooltip
+                          content={
+                            <div className="tw-max-w-xs tw-text-xs">
+                              If enabled, the command will be available as a slash command in the
+                              chat.
+                            </div>
+                          }
+                        />
+                      </div>
+                    </TableHead>
+                    <TableHead className="tw-w-32 tw-text-center">Actions</TableHead>
+                  </TableRow>
+                </TableHeader>
+                <SortableContext
+                  items={commands.map((command) => command.title)}
+                  strategy={verticalListSortingStrategy}
+                >
+                  <TableBody>
+                    {commands.length === 0 ? (
+                      <TableRow>
+                        <TableCell colSpan={5} className="tw-py-8 tw-text-center tw-text-muted">
+                          No custom prompt files found.
+                        </TableCell>
+                      </TableRow>
+                    ) : (
+                      commands.map((command) => (
+                        <SortableTableRow
+                          key={command.title}
+                          command={command}
+                          commands={commands}
+                          onUpdate={handleUpdate}
+                          onRemove={handleRemove}
+                          onCopy={handleCopy}
+                        />
+                      ))
+                    )}
+                  </TableBody>
+                </SortableContext>
+              </Table>
+            </DndContext>
+          </div>
+
+          {/* Mobile view */}
+          {renderMobileView()}
         </div>
       </section>
     </div>
diff --git a/src/settings/v2/components/CopilotPlusSettings.tsx b/src/settings/v2/components/CopilotPlusSettings.tsx
index 8bfbecc..283ca7f 100644
--- a/src/settings/v2/components/CopilotPlusSettings.tsx
+++ b/src/settings/v2/components/CopilotPlusSettings.tsx
@@ -10,11 +10,11 @@ import {
   SelectValue,
 } from "@/components/ui/select";
 import { SettingItem } from "@/components/ui/setting-item";
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
+import { HelpTooltip } from "@/components/ui/help-tooltip";
 import { AUTOCOMPLETE_CONFIG } from "@/constants";
 import { cn } from "@/lib/utils";
 import { updateSetting, useSettingsValue } from "@/settings/model";
-import { HelpCircle, RefreshCw } from "lucide-react";
+import { RefreshCw } from "lucide-react";
 import { Notice } from "obsidian";
 import React, { useState } from "react";
 import { ToolSettingsSection } from "./ToolSettingsSection";
@@ -145,19 +145,16 @@ export const CopilotPlusSettings: React.FC = () => {
                 <span className="tw-leading-none">
                   The key used to accept autocomplete suggestions
                 </span>
-                <TooltipProvider delayDuration={0}>
-                  <Tooltip>
-                    <TooltipTrigger asChild>
-                      <HelpCircle className="tw-size-4" />
-                    </TooltipTrigger>
-                    <TooltipContent className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2">
+                <HelpTooltip
+                  content={
+                    <div className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2">
                       <div className="tw-text-sm tw-text-muted">
                         Select the key you want to use for accepting suggestions. Default is
                         &quot;Tab&quot;.
                       </div>
-                    </TooltipContent>
-                  </Tooltip>
-                </TooltipProvider>
+                    </div>
+                  }
+                />
               </div>
             }
           >
diff --git a/src/settings/v2/components/ModelAddDialog.tsx b/src/settings/v2/components/ModelAddDialog.tsx
index 8e75ed8..748789a 100644
--- a/src/settings/v2/components/ModelAddDialog.tsx
+++ b/src/settings/v2/components/ModelAddDialog.tsx
@@ -18,7 +18,7 @@ import { Label } from "@/components/ui/label";
 import { Input } from "@/components/ui/input";
 import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
 import { Button } from "@/components/ui/button";
-import { ChevronDown, HelpCircle, Loader2 } from "lucide-react";
+import { ChevronDown, Loader2 } from "lucide-react";
 import {
   Dialog,
   DialogContent,
@@ -35,7 +35,7 @@ import {
 } from "@/components/ui/select";
 import { PasswordInput } from "@/components/ui/password-input";
 import { Checkbox } from "@/components/ui/checkbox";
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
+import { HelpTooltip } from "@/components/ui/help-tooltip";
 import { FormField } from "@/components/ui/form-field";
 
 interface FormErrors {
@@ -421,25 +421,21 @@ export const ModelAddDialog: React.FC<ModelAddDialogProps> = ({
             label={
               <div className="tw-flex tw-items-center tw-gap-1.5">
                 <span className="tw-leading-none">Display Name</span>
-                <TooltipProvider delayDuration={0}>
-                  <Tooltip>
-                    <TooltipTrigger asChild>
-                      <HelpCircle className="tw-size-4" />
-                    </TooltipTrigger>
-                    <TooltipContent align="start" className="tw-max-w-96" side="bottom">
-                      <div className="tw-flex tw-flex-col tw-gap-0.5 tw-text-sm tw-text-muted">
-                        <div className="tw-text-[12px] tw-font-bold">Suggested format:</div>
-                        <div className="tw-text-accent">[Source]-[Payment]:[Pretty Model Name]</div>
-                        <div className="tw-text-[12px]">
-                          Example:
-                          <li>Direct-Paid:Ds-r1</li>
-                          <li>OpenRouter-Paid:Ds-r1</li>
-                          <li>Perplexity-Paid:lg</li>
-                        </div>
+                <HelpTooltip
+                  content={
+                    <div className="tw-flex tw-flex-col tw-gap-0.5 tw-text-sm tw-text-muted">
+                      <div className="tw-text-[12px] tw-font-bold">Suggested format:</div>
+                      <div className="tw-text-accent">[Source]-[Payment]:[Pretty Model Name]</div>
+                      <div className="tw-text-[12px]">
+                        Example:
+                        <li>Direct-Paid:Ds-r1</li>
+                        <li>OpenRouter-Paid:Ds-r1</li>
+                        <li>Perplexity-Paid:lg</li>
                       </div>
-                    </TooltipContent>
-                  </Tooltip>
-                </TooltipProvider>
+                    </div>
+                  }
+                  contentClassName="tw-max-w-96"
+                />
               </div>
             }
           >
@@ -500,18 +496,14 @@ export const ModelAddDialog: React.FC<ModelAddDialogProps> = ({
             label={
               <div className="tw-flex tw-items-center tw-gap-1.5">
                 <span className="tw-leading-none">Model Capabilities</span>
-                <TooltipProvider delayDuration={0}>
-                  <Tooltip>
-                    <TooltipTrigger asChild>
-                      <HelpCircle className="tw-size-4" />
-                    </TooltipTrigger>
-                    <TooltipContent align="start" className="tw-max-w-96" side="bottom">
-                      <div className="tw-text-sm tw-text-muted">
-                        Only used to display model capabilities, does not affect model functionality
-                      </div>
-                    </TooltipContent>
-                  </Tooltip>
-                </TooltipProvider>
+                <HelpTooltip
+                  content={
+                    <div className="tw-text-sm tw-text-muted">
+                      Only used to display model capabilities, does not affect model functionality
+                    </div>
+                  }
+                  contentClassName="tw-max-w-96"
+                />
               </div>
             }
           >
@@ -531,16 +523,11 @@ export const ModelAddDialog: React.FC<ModelAddDialogProps> = ({
                       });
                     }}
                   />
-                  <Label htmlFor={id} className="tw-text-sm">
-                    <TooltipProvider delayDuration={0}>
-                      <Tooltip>
-                        <TooltipTrigger asChild>
-                          <span>{label}</span>
-                        </TooltipTrigger>
-                        <TooltipContent side="bottom">{description}</TooltipContent>
-                      </Tooltip>
-                    </TooltipProvider>
-                  </Label>
+                  <HelpTooltip content={description}>
+                    <Label htmlFor={id} className="tw-text-sm">
+                      {label}
+                    </Label>
+                  </HelpTooltip>
                 </div>
               ))}
             </div>
@@ -549,39 +536,35 @@ export const ModelAddDialog: React.FC<ModelAddDialogProps> = ({
           {renderProviderSpecificFields()}
         </div>
 
-        <div className="tw-flex tw-items-center tw-justify-end tw-gap-4">
+        <div className="tw-flex tw-items-center  tw-justify-between tw-gap-4">
           <div className="tw-flex tw-items-center tw-gap-2">
             <Checkbox
               id="enable-cors"
               checked={model.enableCors || false}
               onCheckedChange={(checked: boolean) => setModel({ ...model, enableCors: checked })}
             />
-            <Label htmlFor="enable-cors" className="tw-text-sm">
-              <div className="tw-flex tw-items-center tw-gap-1.5">
-                <span>Enable CORS</span>
-                <TooltipProvider delayDuration={0}>
-                  <Tooltip>
-                    <TooltipTrigger asChild>
-                      <HelpCircle className="tw-size-4" />
-                    </TooltipTrigger>
-                    <TooltipContent align="start" className="tw-max-w-96" side="bottom">
-                      <div className="tw-text-sm tw-text-muted">
-                        Only check this option when prompted that CORS is needed
-                      </div>
-                    </TooltipContent>
-                  </Tooltip>
-                </TooltipProvider>
+            <Label htmlFor="enable-cors">
+              <div className="tw-flex tw-items-center tw-gap-0.5">
+                <span className="tw-text-xs md:tw-text-sm">CORS</span>
+                <HelpTooltip
+                  content={
+                    <div className="tw-text-sm tw-text-muted">
+                      Only check this option when prompted that CORS is needed
+                    </div>
+                  }
+                  contentClassName="tw-max-w-96"
+                />
               </div>
             </Label>
           </div>
-          <div className="tw-flex tw-gap-2">
+          <div className="tw-flex tw-gap-2 tw-text-xs md:tw-text-sm">
             <Button variant="secondary" onClick={handleAdd} disabled={isButtonDisabled()}>
               Add Model
             </Button>
             <Button variant="secondary" onClick={handleVerify} disabled={isButtonDisabled()}>
               {isVerifying ? (
                 <>
-                  <Loader2 className="tw-mr-2 tw-size-4 tw-animate-spin" />
+                  <Loader2 className="tw-mr-2 tw-size-2 tw-animate-spin md:tw-size-4 " />
                   Verify
                 </>
               ) : (
diff --git a/src/settings/v2/components/ModelEditDialog.tsx b/src/settings/v2/components/ModelEditDialog.tsx
index 5de5a03..31cf470 100644
--- a/src/settings/v2/components/ModelEditDialog.tsx
+++ b/src/settings/v2/components/ModelEditDialog.tsx
@@ -5,7 +5,7 @@ import { Input } from "@/components/ui/input";
 import { Label } from "@/components/ui/label";
 import { PasswordInput } from "@/components/ui/password-input";
 
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
+import { HelpTooltip } from "@/components/ui/help-tooltip";
 import {
   DEFAULT_MODEL_SETTING,
   MODEL_CAPABILITIES,
@@ -17,7 +17,6 @@ import {
 } from "@/constants";
 import { getSettings } from "@/settings/model";
 import { debounce, getProviderInfo, getProviderLabel } from "@/utils";
-import { HelpCircle } from "lucide-react";
 import { App, Modal, Platform } from "obsidian";
 import React, { useCallback, useEffect, useMemo, useState } from "react";
 import { createRoot, Root } from "react-dom/client";
@@ -143,25 +142,21 @@ export const ModelEditModalContent: React.FC<ModelEditModalContentProps> = ({
           label={
             <div className="tw-flex tw-items-center tw-gap-1.5">
               <span className="tw-leading-none">Display Name</span>
-              <TooltipProvider delayDuration={0}>
-                <Tooltip>
-                  <TooltipTrigger asChild>
-                    <HelpCircle className="tw-size-4" />
-                  </TooltipTrigger>
-                  <TooltipContent align="start" className="tw-max-w-96" side="bottom">
-                    <div className="tw-flex tw-flex-col tw-gap-0.5 tw-text-sm tw-text-muted">
-                      <div className="tw-text-[12px] tw-font-bold">Suggested format:</div>
-                      <div className="tw-text-accent">[Source]-[Payment]:[Pretty Model Name]</div>
-                      <div className="tw-text-[12px]">
-                        Example:
-                        <li>Direct-Paid:Ds-r1</li>
-                        <li>OpenRouter-Paid:Ds-r1</li>
-                        <li>Perplexity-Paid:lg</li>
-                      </div>
+              <HelpTooltip
+                content={
+                  <div className="tw-flex tw-flex-col tw-gap-0.5 tw-text-sm tw-text-muted">
+                    <div className="tw-text-[12px] tw-font-bold">Suggested format:</div>
+                    <div className="tw-text-accent">[Source]-[Payment]:[Pretty Model Name]</div>
+                    <div className="tw-text-[12px]">
+                      Example:
+                      <li>Direct-Paid:Ds-r1</li>
+                      <li>OpenRouter-Paid:Ds-r1</li>
+                      <li>Perplexity-Paid:lg</li>
                     </div>
-                  </TooltipContent>
-                </Tooltip>
-              </TooltipProvider>
+                  </div>
+                }
+                contentClassName="tw-max-w-96"
+              />
             </div>
           }
         >
@@ -207,19 +202,14 @@ export const ModelEditModalContent: React.FC<ModelEditModalContentProps> = ({
               label={
                 <div className="tw-flex tw-items-center tw-gap-1.5">
                   <span className="tw-leading-none">Model Capabilities</span>
-                  <TooltipProvider delayDuration={0}>
-                    <Tooltip>
-                      <TooltipTrigger asChild>
-                        <HelpCircle className="tw-size-4" />
-                      </TooltipTrigger>
-                      <TooltipContent align="start" className="tw-max-w-96" side="bottom">
-                        <div className="tw-text-sm tw-text-muted">
-                          Only used to display model capabilities, does not affect model
-                          functionality
-                        </div>
-                      </TooltipContent>
-                    </Tooltip>
-                  </TooltipProvider>
+                  <HelpTooltip
+                    content={
+                      <div className="tw-text-sm tw-text-muted">
+                        Only used to display model capabilities, does not affect model functionality
+                      </div>
+                    }
+                    contentClassName="tw-max-w-96"
+                  />
                 </div>
               }
             >
@@ -237,16 +227,11 @@ export const ModelEditModalContent: React.FC<ModelEditModalContentProps> = ({
                         handleLocalUpdate("capabilities", value);
                       }}
                     />
-                    <Label htmlFor={id} className="tw-text-sm">
-                      <TooltipProvider delayDuration={0}>
-                        <Tooltip>
-                          <TooltipTrigger asChild>
-                            <span>{label}</span>
-                          </TooltipTrigger>
-                          <TooltipContent side="bottom">{description}</TooltipContent>
-                        </Tooltip>
-                      </TooltipProvider>
-                    </Label>
+                    <HelpTooltip content={description}>
+                      <Label htmlFor={id} className="tw-text-sm">
+                        {label}
+                      </Label>
+                    </HelpTooltip>
                   </div>
                 ))}
               </div>
diff --git a/src/settings/v2/components/ModelTable.tsx b/src/settings/v2/components/ModelTable.tsx
index d270137..31f47ba 100644
--- a/src/settings/v2/components/ModelTable.tsx
+++ b/src/settings/v2/components/ModelTable.tsx
@@ -1,7 +1,7 @@
 import { CustomModel } from "@/aiParams";
 import { Button } from "@/components/ui/button";
-import { Card, CardContent, CardHeader } from "@/components/ui/card";
 import { Checkbox } from "@/components/ui/checkbox";
+import { MobileCard, MobileCardDropdownAction } from "@/components/ui/mobile-card";
 import { ModelCapabilityIcons } from "@/components/ui/model-display";
 import {
   Table,
@@ -11,7 +11,7 @@ import {
   TableHeader,
   TableRow,
 } from "@/components/ui/table";
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
+import { HelpTooltip } from "@/components/ui/help-tooltip";
 import { MODEL_CAPABILITIES, ModelCapability } from "@/constants";
 import { cn } from "@/lib/utils";
 import { getModelKeyFromModel } from "@/settings/model";
@@ -34,8 +34,6 @@ import {
 } from "@dnd-kit/sortable";
 import { CSS } from "@dnd-kit/utilities";
 import {
-  ChevronDown,
-  ChevronRight,
   Copy,
   Eye,
   Globe,
@@ -49,14 +47,13 @@ import {
   RefreshCw,
   Trash2,
 } from "lucide-react";
-import React, { ForwardRefExoticComponent, RefAttributes } from "react";
+import React, { ForwardRefExoticComponent, RefAttributes, useRef } from "react";
 import {
   DropdownMenu,
   DropdownMenuContent,
   DropdownMenuItem,
   DropdownMenuTrigger,
 } from "@/components/ui/dropdown-menu";
-import { useContainerContext } from "@/settings/v2/components/ContainerContext";
 
 const CAPABILITY_ICONS: Record<
   ModelCapability,
@@ -91,32 +88,27 @@ const CAPABILITY_ORDER = [
 
 const renderCapabilities = (model: CustomModel) => {
   return (
-    <TooltipProvider delayDuration={0}>
-      <div className="tw-mx-auto tw-grid tw-w-16 tw-grid-cols-3 tw-gap-1">
-        {CAPABILITY_ORDER.map((capability) => {
-          const config = CAPABILITY_ICONS[capability];
-          if (!config) return <div key={capability} className="tw-w-4" />;
-
-          const Icon = config.icon;
-          const hasCapability = model.capabilities?.includes(capability);
-
-          return hasCapability ? (
-            <Tooltip key={capability}>
-              <TooltipTrigger asChild>
-                <div className="tw-flex tw-items-center tw-justify-center">
-                  <Icon className={cn("tw-size-4", config.color)} />
-                </div>
-              </TooltipTrigger>
-              <TooltipContent side="bottom">{config.tooltip}</TooltipContent>
-            </Tooltip>
-          ) : (
-            <div key={capability} className="tw-flex tw-items-center tw-justify-center">
-              <div className="tw-size-4" />
+    <div className="tw-mx-auto tw-grid tw-w-16 tw-grid-cols-3 tw-gap-1">
+      {CAPABILITY_ORDER.map((capability) => {
+        const config = CAPABILITY_ICONS[capability];
+        if (!config) return <div key={capability} className="tw-w-4" />;
+
+        const Icon = config.icon;
+        const hasCapability = model.capabilities?.includes(capability);
+
+        return hasCapability ? (
+          <HelpTooltip key={capability} content={config.tooltip} side="bottom">
+            <div className="tw-flex tw-items-center tw-justify-center">
+              <Icon className={cn("tw-size-4", config.color)} />
             </div>
-          );
-        })}
-      </div>
-    </TooltipProvider>
+          </HelpTooltip>
+        ) : (
+          <div key={capability} className="tw-flex tw-items-center tw-justify-center">
+            <div className="tw-size-4" />
+          </div>
+        );
+      })}
+    </div>
   );
 };
 
@@ -127,6 +119,7 @@ interface ModelCardProps {
   onDelete: (modelKey: string) => void;
   onUpdateModel: (model: CustomModel) => void;
   id: string;
+  containerRef: React.RefObject<HTMLDivElement>;
 }
 
 const ModelCard: React.FC<ModelCardProps> = ({
@@ -136,169 +129,82 @@ const ModelCard: React.FC<ModelCardProps> = ({
   onDelete,
   onUpdateModel,
   id,
+  containerRef,
 }) => {
-  const [isExpanded, setIsExpanded] = React.useState(false);
-  const container = useContainerContext();
-
-  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
-    id,
-    disabled: model.core,
-  });
-
-  const style = {
-    transform: CSS.Transform.toString(transform),
-    transition,
-  };
-
-  return (
-    <Card
-      ref={setNodeRef}
-      style={style}
-      className={cn(
-        "tw-mb-2",
-        isDragging && [
-          "tw-opacity-90",
-          "tw-shadow-lg",
-          "tw-border-accent/50",
-          "tw-relative",
-          "tw-z-[9999]",
-          "tw-bg-primary",
-          "tw-rounded-lg",
-          "tw-transform-gpu",
-        ],
-        !model.core && "tw-touch-none"
-      )}
-    >
-      <CardHeader className="tw-p-3">
-        <div className="tw-flex tw-items-center tw-justify-between">
-          {!model.core && (
-            <div
-              className="tw-mr-2 tw-cursor-grab tw-touch-none active:tw-cursor-grabbing"
-              {...attributes}
-              {...listeners}
-            >
-              <GripVertical className="tw-size-4" />
-            </div>
-          )}
-
-          <div className="tw-flex-1 tw-cursor-pointer" onClick={() => setIsExpanded(!isExpanded)}>
-            <div className="tw-flex tw-items-center tw-gap-2">
-              <div className="tw-flex tw-size-3 tw-items-center tw-justify-center">
-                {isExpanded ? (
-                  <ChevronDown className="tw-size-3 tw-stroke-[7]" />
-                ) : (
-                  <ChevronRight className="tw-size-3 tw-stroke-[7]" />
-                )}
-              </div>
-              <div>
-                <div className="tw-flex tw-items-center tw-gap-1">
-                  <span className="tw-font-medium">{model.displayName || model.name}</span>
-                  {model.capabilities && model.capabilities.length > 0 && (
-                    <ModelCapabilityIcons capabilities={model.capabilities} iconSize={14} />
-                  )}
-                </div>
-                <div className="tw-flex tw-items-center tw-gap-2">
-                  <span className="tw-bg-secondary tw-text-sm tw-text-muted">
-                    {getProviderLabel(model.provider, model)}
-                  </span>
-                </div>
-              </div>
-            </div>
-          </div>
+  const dropdownActions: MobileCardDropdownAction<CustomModel>[] = [];
+
+  if (onEdit) {
+    dropdownActions.push({
+      icon: <PencilLine className="tw-size-4" />,
+      label: "Edit",
+      onClick: onEdit,
+    });
+  }
 
-          <div className="tw-flex tw-items-center tw-gap-2">
-            {onEdit && (
-              <Button
-                variant="ghost"
-                size="icon"
-                onClick={(e) => {
-                  e.stopPropagation();
-                  onEdit(model);
-                }}
-              >
-                <Pencil className="tw-size-4" />
-              </Button>
-            )}
-            <DropdownMenu>
-              <DropdownMenuTrigger asChild>
-                <Button variant="ghost" size="icon">
-                  <MoreVertical className="tw-size-4" />
-                </Button>
-              </DropdownMenuTrigger>
-              <DropdownMenuContent align="end" container={container}>
-                {onEdit && (
-                  <DropdownMenuItem
-                    onClick={(e) => {
-                      e.stopPropagation();
-                      onEdit(model);
-                    }}
-                  >
-                    <PencilLine className="tw-mr-2 tw-size-4" />
-                    Edit
-                  </DropdownMenuItem>
-                )}
+  if (onCopy && !model.core) {
+    dropdownActions.push({
+      icon: <Copy className="tw-size-4" />,
+      label: "Copy",
+      onClick: onCopy,
+    });
+  }
 
-                {onCopy && !model.core && (
-                  <DropdownMenuItem
-                    onClick={(e) => {
-                      e.stopPropagation();
-                      onCopy(model);
-                    }}
-                  >
-                    <Copy className="tw-mr-2 tw-size-4" />
-                    Copy
-                  </DropdownMenuItem>
-                )}
+  if (!model.core) {
+    dropdownActions.push({
+      icon: <Trash2 className="tw-size-4" />,
+      label: "Delete",
+      onClick: () => onDelete(getModelKeyFromModel(model)),
+      variant: "destructive",
+    });
+  }
 
-                {!model.core && (
-                  <DropdownMenuItem
-                    onClick={(e) => {
-                      e.stopPropagation();
-                      onDelete(getModelKeyFromModel(model));
-                    }}
-                    className="tw-text-error"
-                  >
-                    <Trash2 className="tw-mr-2 tw-size-4" />
-                    Delete
-                  </DropdownMenuItem>
-                )}
-              </DropdownMenuContent>
-            </DropdownMenu>
-          </div>
+  const expandedContent = (
+    <div className="tw-flex tw-justify-around">
+      {!model.isEmbeddingModel && (
+        <div className="tw-flex tw-items-center tw-gap-2">
+          <span className="tw-text-sm">Enabled</span>
+          <Checkbox
+            checked={model.enabled}
+            onCheckedChange={(checked: boolean) => onUpdateModel({ ...model, enabled: checked })}
+          />
         </div>
-      </CardHeader>
-      <div
-        className={cn(
-          "tw-transition-all tw-duration-300 tw-ease-in-out",
-          isExpanded ? "tw-max-h-20 tw-opacity-100" : "tw-max-h-0 tw-overflow-hidden tw-opacity-0"
-        )}
-      >
-        <CardContent className="tw-p-3 tw-pt-0">
-          <div className="tw-flex tw-justify-around">
-            {!model.isEmbeddingModel && (
-              <div className="tw-flex tw-items-center tw-gap-2">
-                <span className="tw-text-sm">Enabled</span>
-                <Checkbox
-                  checked={model.enabled}
-                  onCheckedChange={(checked: boolean) =>
-                    onUpdateModel({ ...model, enabled: checked })
-                  }
-                />
-              </div>
-            )}
-            <div className="tw-flex tw-items-center tw-gap-2">
-              <span className="tw-text-sm">CORS</span>
-              <Checkbox
-                checked={model.enableCors}
-                onCheckedChange={(checked: boolean) =>
-                  onUpdateModel({ ...model, enableCors: checked })
-                }
-              />
-            </div>
-          </div>
-        </CardContent>
+      )}
+      <div className="tw-flex tw-items-center tw-gap-2">
+        <span className="tw-text-sm">CORS</span>
+        <Checkbox
+          checked={model.enableCors}
+          onCheckedChange={(checked: boolean) => onUpdateModel({ ...model, enableCors: checked })}
+        />
       </div>
-    </Card>
+    </div>
+  );
+
+  return (
+    <MobileCard
+      id={id}
+      item={model}
+      title={model.displayName || model.name}
+      subtitle={getProviderLabel(model.provider, model)}
+      badge={
+        model.capabilities && model.capabilities.length > 0 ? (
+          <ModelCapabilityIcons capabilities={model.capabilities} iconSize={14} />
+        ) : undefined
+      }
+      isDraggable={!model.core}
+      isExpandable
+      expandedContent={expandedContent}
+      primaryAction={
+        onEdit
+          ? {
+              icon: <Pencil className="tw-size-4" />,
+              onClick: onEdit,
+              tooltip: "Edit Model",
+            }
+          : undefined
+      }
+      dropdownActions={dropdownActions}
+      containerRef={containerRef}
+    />
   );
 };
 
@@ -309,12 +215,12 @@ const DesktopSortableTableRow: React.FC<{
   onDelete: (modelKey: string) => void;
   onUpdateModel: (model: CustomModel) => void;
   isEmbeddingModel: boolean;
-}> = ({ model, onEdit, onCopy, onDelete, onUpdateModel, isEmbeddingModel }) => {
+  containerRef: React.RefObject<HTMLDivElement>;
+}> = ({ model, onEdit, onCopy, onDelete, onUpdateModel, isEmbeddingModel, containerRef }) => {
   const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
     id: getModelKeyFromModel(model),
     disabled: model.core,
   });
-  const container = useContainerContext();
 
   const style = {
     transform: CSS.Transform.toString(transform),
@@ -387,7 +293,7 @@ const DesktopSortableTableRow: React.FC<{
                   <MoreVertical className="tw-size-4" />
                 </Button>
               </DropdownMenuTrigger>
-              <DropdownMenuContent align="end" container={container}>
+              <DropdownMenuContent align="end" container={containerRef.current}>
                 {onEdit && (
                   <DropdownMenuItem onClick={() => onEdit(model)}>
                     <PencilLine className="tw-mr-2 tw-size-4" />
@@ -452,6 +358,8 @@ export const ModelTable: React.FC<ModelTableProps> = ({
     })
   );
 
+  const containerRef = useRef<HTMLDivElement>(null);
+
   // Find the index of the first non-core model
   const firstDraggableIndex = models.findIndex((model) => !model.core);
 
@@ -529,16 +437,25 @@ export const ModelTable: React.FC<ModelTableProps> = ({
         collisionDetection={closestCenter}
         onDragEnd={handleDragEnd}
         modifiers={[createDragModifier(true)]}
+        autoScroll={{
+          enabled: true,
+          acceleration: 10,
+          threshold: {
+            x: 0,
+            y: 0.2,
+          },
+        }}
       >
         <SortableContext
           items={models.map((model) => getModelKeyFromModel(model))}
           strategy={verticalListSortingStrategy}
         >
-          <div className="tw-relative tw-space-y-2">
+          <div className="tw-relative tw-touch-auto tw-space-y-2 tw-overflow-auto tw-pb-2">
             {models.map((model) => (
               <ModelCard
                 key={getModelKeyFromModel(model)}
                 id={getModelKeyFromModel(model)}
+                containerRef={containerRef}
                 model={model}
                 onEdit={onEdit}
                 onCopy={onCopy}
@@ -553,7 +470,23 @@ export const ModelTable: React.FC<ModelTableProps> = ({
   );
 
   return (
-    <div className="tw-mb-4">
+    <div ref={containerRef} className="tw-mb-4">
+      <div className="tw-mt-4 tw-flex tw-justify-between tw-gap-2 md:tw-justify-end">
+        {onRefresh && (
+          <Button
+            onClick={onRefresh}
+            variant="secondary"
+            className="tw-flex tw-items-center tw-gap-2"
+          >
+            <RefreshCw className="tw-size-2 md:tw-size-4" />
+            Refresh Built-ins
+          </Button>
+        )}
+        <Button onClick={onAdd} variant="default" className="tw-flex tw-items-center tw-gap-2">
+          <Plus className="tw-size-2 md:tw-size-4" />
+          Add Model
+        </Button>
+      </div>
       {/* Desktop view */}
       <div className="tw-hidden md:tw-block">
         <DndContext
@@ -583,6 +516,7 @@ export const ModelTable: React.FC<ModelTableProps> = ({
                   {models.map((model) => (
                     <DesktopSortableTableRow
                       key={getModelKeyFromModel(model)}
+                      containerRef={containerRef}
                       model={model}
                       onEdit={onEdit}
                       onCopy={onCopy}
@@ -600,23 +534,6 @@ export const ModelTable: React.FC<ModelTableProps> = ({
 
       {/* Mobile view */}
       {renderMobileView()}
-
-      <div className="tw-mt-4 tw-flex tw-justify-end tw-gap-2">
-        {onRefresh && (
-          <Button
-            onClick={onRefresh}
-            variant="secondary"
-            className="tw-flex tw-items-center tw-gap-2"
-          >
-            <RefreshCw className="tw-size-4" />
-            Refresh Built-in Models
-          </Button>
-        )}
-        <Button onClick={onAdd} variant="secondary" className="tw-flex tw-items-center tw-gap-2">
-          <Plus className="tw-size-4" />
-          Add Custom Model
-        </Button>
-      </div>
     </div>
   );
 };
diff --git a/src/settings/v2/components/PlusSettings.tsx b/src/settings/v2/components/PlusSettings.tsx
index 9acba7a..dcbe75b 100644
--- a/src/settings/v2/components/PlusSettings.tsx
+++ b/src/settings/v2/components/PlusSettings.tsx
@@ -31,8 +31,12 @@ export function PlusSettings() {
       <div className="tw-flex tw-flex-col tw-gap-2 tw-text-sm tw-text-muted">
         <div>
           Copilot Plus takes your Obsidian experience to the next level with cutting-edge AI
-          capabilities. This premium tier unlocks advanced features, including chat context, PDF and
-          image support, web search integration, exclusive chat and embedding models, and much more.
+          capabilities. This premium tier unlocks advanced features:{" "}
+          <strong>
+            {" "}
+            including chat context, PDF and image support, web search integration, exclusive chat
+            and embedding models, and much more.
+          </strong>
         </div>
         <div>
           Copilot Plus is evolving fast, with new features and improvements rolling out regularly.
@@ -62,12 +66,16 @@ export function PlusSettings() {
               new CopilotPlusWelcomeModal(app).open();
             }
           }}
-          className="tw-min-w-20"
+          className="tw-min-w-10 tw-text-xs md:tw-text-sm"
         >
-          {isChecking ? <Loader2 className="tw-size-4 tw-animate-spin" /> : "Apply"}
+          {isChecking ? <Loader2 className="tw-size-2 tw-animate-spin md:tw-size-4" /> : "Apply"}
         </Button>
-        <Button variant="secondary" onClick={() => navigateToPlusPage(PLUS_UTM_MEDIUMS.SETTINGS)}>
-          Join Now <ExternalLink className="tw-size-4" />
+        <Button
+          className="tw-text-xs md:tw-text-sm"
+          variant="secondary"
+          onClick={() => navigateToPlusPage(PLUS_UTM_MEDIUMS.SETTINGS)}
+        >
+          Join Now <ExternalLink className="tw-size-2 md:tw-size-4" />
         </Button>
       </div>
       <div className="tw-text-error">{error}</div>
diff --git a/src/settings/v2/components/QASettings.tsx b/src/settings/v2/components/QASettings.tsx
index f3016cb..6a54962 100644
--- a/src/settings/v2/components/QASettings.tsx
+++ b/src/settings/v2/components/QASettings.tsx
@@ -4,10 +4,9 @@ import { SemanticSearchToggleModal } from "@/components/modals/SemanticSearchTog
 import { Button } from "@/components/ui/button";
 import { getModelDisplayWithIcons } from "@/components/ui/model-display";
 import { SettingItem } from "@/components/ui/setting-item";
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
+import { HelpTooltip } from "@/components/ui/help-tooltip";
 import { VAULT_VECTOR_STORE_STRATEGIES } from "@/constants";
 import { getModelKeyFromModel, updateSetting, useSettingsValue } from "@/settings/model";
-import { HelpCircle } from "lucide-react";
 import { Notice } from "obsidian";
 import React from "react";
 
@@ -40,6 +39,15 @@ export const QASettings: React.FC = () => {
     <div className="tw-space-y-4">
       <section>
         <div className="tw-space-y-4">
+          {/* Enable Inline Citations */}
+          <SettingItem
+            type="switch"
+            title="Enable Inline Citations (experimental)"
+            description="When enabled, AI responses will include footnote-style citations within the text and numbered sources at the end (This is an experimental feature and may not work as expected for all models.)"
+            checked={settings.enableInlineCitations}
+            onCheckedChange={(checked) => updateSetting("enableInlineCitations", checked)}
+          />
+
           {/* Enable Semantic Search (v3) */}
           <SettingItem
             type="switch"
@@ -74,25 +82,20 @@ export const QASettings: React.FC = () => {
                     Powers Semantic Vault Search and Relevant Notes. Enable Semantic Search to use
                     it.
                   </span>
-                  <TooltipProvider delayDuration={0}>
-                    <Tooltip>
-                      <TooltipTrigger asChild>
-                        <HelpCircle className="tw-size-4" />
-                      </TooltipTrigger>
-                      <TooltipContent className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2">
-                        <div className="tw-pt-2 tw-text-sm tw-text-muted">
-                          This model converts text into vector representations, essential for
-                          semantic search and Question Answering (QA) functionality. Changing the
-                          embedding model will:
-                        </div>
-                        <ul className="tw-pl-4 tw-text-sm tw-text-muted">
-                          <li>Require rebuilding your vault&#39;s vector index</li>
-                          <li>Affect semantic search quality</li>
-                          <li>Impact Question Answering feature performance</li>
-                        </ul>
-                      </TooltipContent>
-                    </Tooltip>
-                  </TooltipProvider>
+                  <HelpTooltip
+                    content={<div className="tw-flex tw-max-w-96 tw-flex-col tw-gap-2">
+                      <div className="tw-pt-2 tw-text-sm tw-text-muted">
+                        This model converts text into vector representations, essential for
+                        semantic search and Question Answering (QA) functionality. Changing the
+                        embedding model will:
+                      </div>
+                      <ul className="tw-pl-4 tw-text-sm tw-text-muted">
+                        <li>Require rebuilding your vault&#39;s vector index</li>
+                        <li>Affect semantic search quality</li>
+                        <li>Impact Question Answering feature performance</li>
+                      </ul>
+                    </div>}
+                  />
                 </div>
               </div>
             }
@@ -114,51 +117,46 @@ export const QASettings: React.FC = () => {
                 <span className="tw-leading-none">
                   Decide when you want the vault to be indexed.
                 </span>
-                <TooltipProvider delayDuration={0}>
-                  <Tooltip>
-                    <TooltipTrigger asChild>
-                      <HelpCircle className="tw-size-4" />
-                    </TooltipTrigger>
-                    <TooltipContent>
-                      <div className="tw-space-y-2 tw-py-2">
-                        <div className="tw-space-y-1">
-                          <div className="tw-text-sm tw-text-muted">
-                            Choose when to index your vault:
-                          </div>
-                          <ul className="tw-list-disc tw-space-y-1 tw-pl-2 tw-text-sm">
-                            <li>
-                              <div className="tw-flex tw-items-center tw-gap-1">
-                                <strong className="tw-inline-block tw-whitespace-nowrap">
-                                  NEVER:
-                                </strong>
-                                <span>Manual indexing via command or refresh only</span>
-                              </div>
-                            </li>
-                            <li>
-                              <div className="tw-flex tw-items-center tw-gap-1">
-                                <strong className="tw-inline-block tw-whitespace-nowrap">
-                                  ON STARTUP:
-                                </strong>
-                                <span>Index updates when plugin loads or reloads</span>
-                              </div>
-                            </li>
-                            <li>
-                              <div className="tw-flex tw-items-center tw-gap-1">
-                                <strong className="tw-inline-block tw-whitespace-nowrap">
-                                  ON MODE SWITCH:
-                                </strong>
-                                <span>Updates when entering QA mode (Recommended)</span>
-                              </div>
-                            </li>
-                          </ul>
+                <HelpTooltip
+                  content={
+                    <div className="tw-space-y-2 tw-py-2">
+                      <div className="tw-space-y-1">
+                        <div className="tw-text-sm tw-text-muted">
+                          Choose when to index your vault:
                         </div>
-                        <p className="tw-text-sm tw-text-callout-warning">
-                          Warning: Cost implications for large vaults with paid models
-                        </p>
+                        <ul className="tw-list-disc tw-space-y-1 tw-pl-2 tw-text-sm">
+                          <li>
+                            <div className="tw-flex tw-items-center tw-gap-1">
+                              <strong className="tw-inline-block tw-whitespace-nowrap">
+                                NEVER:
+                              </strong>
+                              <span>Manual indexing via command or refresh only</span>
+                            </div>
+                          </li>
+                          <li>
+                            <div className="tw-flex tw-items-center tw-gap-1">
+                              <strong className="tw-inline-block tw-whitespace-nowrap">
+                                ON STARTUP:
+                              </strong>
+                              <span>Index updates when plugin loads or reloads</span>
+                            </div>
+                          </li>
+                          <li>
+                            <div className="tw-flex tw-items-center tw-gap-1">
+                              <strong className="tw-inline-block tw-whitespace-nowrap">
+                                ON MODE SWITCH:
+                              </strong>
+                              <span>Updates when entering QA mode (Recommended)</span>
+                            </div>
+                          </li>
+                        </ul>
                       </div>
-                    </TooltipContent>
-                  </Tooltip>
-                </TooltipProvider>
+                      <p className="tw-text-sm tw-text-callout-warning">
+                        Warning: Cost implications for large vaults with paid models
+                      </p>
+                    </div>
+                  }
+                />
               </div>
             }
             value={settings.indexVaultToVectorStore}
diff --git a/src/styles/tailwind.css b/src/styles/tailwind.css
index 1aeb2eb..a28d64a 100644
--- a/src/styles/tailwind.css
+++ b/src/styles/tailwind.css
@@ -881,3 +881,85 @@ If your plugin does not need CSS, delete this file.
 .workspace-leaf-content[data-type="copilot-chat-view"] .view-content {
   padding-bottom: max(var(--safe-area-inset-bottom), var(--size-4-8)) !important;
 }
+
+/* Collapsible sources styling */
+.copilot-sources {
+  margin-top: var(--size-4-1);
+  padding: calc(var(--size-4-2) / 2) var(--size-4-2);
+  border-radius: var(--radius-s);
+  border: 1px solid color-mix(in srgb, var(--background-modifier-border) 70%, transparent);
+  background-color: color-mix(
+    in srgb,
+    var(--background-primary) 94%,
+    var(--background-modifier-border) 6%
+  );
+  width: min(100%, 32rem);
+}
+
+.copilot-sources__summary {
+  font-size: var(--font-ui-smaller);
+  color: color-mix(in srgb, var(--text-muted) 90%, transparent);
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: var(--size-4-2);
+  padding: calc(var(--size-4-2) / 4) 0;
+  padding-left: var(--size-4-2);
+  padding-right: var(--size-4-2);
+  margin: 0;
+}
+
+.copilot-sources__summary::-webkit-details-marker {
+  color: color-mix(in srgb, var(--text-muted) 80%, transparent);
+}
+
+.copilot-sources__summary::after {
+  content: ">";
+  font-size: var(--font-ui-smaller);
+  color: color-mix(in srgb, var(--text-muted) 70%, transparent);
+  transition: transform 0.15s ease;
+}
+
+.copilot-sources[open] .copilot-sources__summary {
+  margin-bottom: calc(var(--size-4-2) / 2);
+}
+
+.copilot-sources[open] .copilot-sources__summary::after {
+  transform: rotate(90deg);
+}
+
+.copilot-sources__list {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+  padding-left: var(--size-4-2);
+  padding-right: var(--size-4-2);
+  display: flex;
+  flex-direction: column;
+  gap: calc(var(--size-4-2) / 2);
+  font-size: var(--font-ui-smaller);
+  color: color-mix(in srgb, var(--text-muted) 85%, transparent);
+}
+
+.copilot-sources__item {
+  display: flex;
+  gap: var(--size-4-2);
+  align-items: baseline;
+}
+
+.copilot-sources__index {
+  color: color-mix(in srgb, var(--text-muted) 65%, transparent);
+  font-weight: var(--font-medium);
+  min-width: var(--size-4-3);
+  text-align: right;
+}
+
+.copilot-sources__text {
+  color: color-mix(in srgb, var(--text-muted) 90%, transparent);
+  line-height: 1.4;
+}
+
+.copilot-sources__text a {
+  color: var(--text-accent);
+}
diff --git a/src/tools/SearchTools.dedupe.test.ts b/src/tools/SearchTools.dedupe.test.ts
new file mode 100644
index 0000000..22f8c8b
--- /dev/null
+++ b/src/tools/SearchTools.dedupe.test.ts
@@ -0,0 +1,67 @@
+import { deduplicateSources } from "@/LLMProviders/chainRunner/utils/toolExecution";
+
+describe("SearchTools deduplication flow", () => {
+  function runDedup(
+    formattedResults: Array<{
+      title: string;
+      path: string;
+      rerank_score?: number;
+      score?: number;
+    }>
+  ) {
+    const sourcesLike = formattedResults.map((d) => ({
+      title: d.title || d.path || "Untitled",
+      path: d.path || d.title || "",
+      score: d.rerank_score || d.score || 0,
+    }));
+
+    const dedupedSources = deduplicateSources(sourcesLike);
+
+    const bestByKey = new Map<string, (typeof formattedResults)[number]>();
+    for (const d of formattedResults) {
+      const key = (d.path || d.title).toLowerCase();
+      const existing = bestByKey.get(key);
+      if (!existing || (d.rerank_score || 0) > (existing.rerank_score || 0)) {
+        bestByKey.set(key, d);
+      }
+    }
+
+    return dedupedSources
+      .map((source) => bestByKey.get((source.path || source.title).toLowerCase()))
+      .filter(Boolean) as typeof formattedResults;
+  }
+
+  it("keeps document when title fallback is used", () => {
+    const formattedResults = [
+      {
+        title: "Untitled",
+        path: "",
+        rerank_score: 0.9,
+      },
+    ];
+
+    const deduped = runDedup(formattedResults);
+    expect(deduped).toHaveLength(1);
+    expect(deduped[0].title).toBe("Untitled");
+  });
+
+  it("deduplicates by path while preserving highest score", () => {
+    const formattedResults = [
+      {
+        title: "Note A",
+        path: "folder/note-a.md",
+        rerank_score: 0.6,
+      },
+      {
+        title: "Alt Title",
+        path: "folder/note-a.md",
+        rerank_score: 0.8,
+      },
+    ];
+
+    const deduped = runDedup(formattedResults);
+    expect(deduped).toHaveLength(1);
+    expect(deduped[0].rerank_score).toBe(0.8);
+    expect(deduped[0].title).toBe("Alt Title");
+  });
+});
diff --git a/src/tools/SearchTools.ts b/src/tools/SearchTools.ts
index 1c0388e..2b01103 100644
--- a/src/tools/SearchTools.ts
+++ b/src/tools/SearchTools.ts
@@ -4,7 +4,9 @@ import { BrevilabsClient } from "@/LLMProviders/brevilabsClient";
 import { logInfo } from "@/logger";
 import { getSettings } from "@/settings/model";
 import { z } from "zod";
+import { deduplicateSources } from "@/LLMProviders/chainRunner/utils/toolExecution";
 import { createTool, SimpleTool } from "./SimpleTool";
+import { getWebSearchCitationInstructions } from "@/LLMProviders/chainRunner/utils/citationUtils";
 
 // Define Zod schema for localSearch
 const localSearchSchema = z.object({
@@ -78,8 +80,28 @@ const lexicalSearchTool = createTool({
         explanation: doc.metadata.explanation ?? null,
       };
     });
+    // Reuse the same dedupe logic used by Show Sources (path fallback to title, keep highest score)
+    const sourcesLike = formattedResults.map((d) => ({
+      title: d.title || d.path || "Untitled",
+      path: d.path || d.title || "",
+      score: d.rerank_score || d.score || 0,
+    }));
+    const dedupedSources = deduplicateSources(sourcesLike);
 
-    return JSON.stringify(formattedResults);
+    // Map back to document objects in the same deduped order
+    const bestByKey = new Map<string, any>();
+    for (const d of formattedResults) {
+      const key = (d.path || d.title).toLowerCase();
+      const existing = bestByKey.get(key);
+      if (!existing || (d.rerank_score || 0) > (existing.rerank_score || 0)) {
+        bestByKey.set(key, d);
+      }
+    }
+    const dedupedDocs = dedupedSources
+      .map((s) => bestByKey.get((s.path || s.title).toLowerCase()))
+      .filter(Boolean);
+
+    return JSON.stringify({ type: "local_search", documents: dedupedDocs });
   },
 });
 
@@ -140,8 +162,27 @@ const semanticSearchTool = createTool({
         explanation: doc.metadata.explanation ?? null,
       };
     });
+    // Reuse the same dedupe logic used by Show Sources
+    const sourcesLike = formattedResults.map((d) => ({
+      title: d.title || d.path || "Untitled",
+      path: d.path || d.title || "",
+      score: d.rerank_score || d.score || 0,
+    }));
+    const dedupedSources = deduplicateSources(sourcesLike);
+
+    const bestByKey = new Map<string, any>();
+    for (const d of formattedResults) {
+      const key = (d.path || d.title).toLowerCase();
+      const existing = bestByKey.get(key);
+      if (!existing || (d.rerank_score || 0) > (existing.rerank_score || 0)) {
+        bestByKey.set(key, d);
+      }
+    }
+    const dedupedDocs = dedupedSources
+      .map((s) => bestByKey.get((s.path || s.title).toLowerCase()))
+      .filter(Boolean);
 
-    return JSON.stringify(formattedResults);
+    return JSON.stringify({ type: "local_search", documents: dedupedDocs });
   },
 });
 
@@ -241,8 +282,10 @@ const webSearchTool = createTool({
           type: "web_search",
           content: webContent,
           citations: citations,
-          instruction:
-            "Provide a response based on this information. Include source citations at the end under '#### Sources' as markdown links.",
+          // Instruct the model to use footnote-style citations and definitions.
+          // Chat UI will render [^n] as [n] for readability and show a simple numbered Sources list.
+          // When inserted into a note, the original [^n] footnotes will remain valid Markdown footnotes.
+          instruction: getWebSearchCitationInstructions(getSettings().enableInlineCitations),
         },
       ];
 
diff --git a/src/tools/ToolRegistry.ts b/src/tools/ToolRegistry.ts
index b06c6f6..46acad9 100644
--- a/src/tools/ToolRegistry.ts
+++ b/src/tools/ToolRegistry.ts
@@ -11,6 +11,7 @@ export interface ToolMetadata {
   isAlwaysEnabled?: boolean; // Tools that are always available (e.g., time tools)
   requiresVault?: boolean; // Tools that need vault access
   customPromptInstructions?: string; // Optional custom instructions for this tool
+  copilotCommands?: string[]; // Optional Copilot slash command aliases (e.g., "@vault")
 }
 
 /**
@@ -112,6 +113,33 @@ export class ToolRegistry {
     return Array.from(this.tools.values()).filter((def) => !def.metadata.isAlwaysEnabled);
   }
 
+  /**
+   * Build a map of Copilot command aliases to tool definitions.
+   *
+   * @returns Map keyed by lower-case Copilot command aliases pointing to their tool definitions.
+   */
+  getCopilotCommandMappings(): Map<string, ToolDefinition> {
+    const mappings = new Map<string, ToolDefinition>();
+
+    for (const definition of this.tools.values()) {
+      const commands = definition.metadata.copilotCommands;
+
+      if (!commands) {
+        continue;
+      }
+
+      for (const command of commands) {
+        const normalizedCommand = command.toLowerCase();
+
+        if (!mappings.has(normalizedCommand)) {
+          mappings.set(normalizedCommand, definition);
+        }
+      }
+    }
+
+    return mappings;
+  }
+
   /**
    * Get tool metadata by ID
    */
diff --git a/src/tools/ToolResultFormatter.test.ts b/src/tools/ToolResultFormatter.test.ts
index 10941b6..faa958e 100644
--- a/src/tools/ToolResultFormatter.test.ts
+++ b/src/tools/ToolResultFormatter.test.ts
@@ -80,21 +80,22 @@ Recent content.
       expect(formatted).toContain("... and 5 more results");
     });
 
-    it("should fall back to JSON parsing for non-XML results", () => {
-      const jsonResult = JSON.stringify([
-        {
-          title: "JSON Note",
-          path: "json/note.md",
-          score: 0.95,
-          content: "JSON content",
-        },
-      ]);
+    it("should support structured JSON fallback when not XML", () => {
+      const jsonResult = JSON.stringify({
+        type: "local_search",
+        documents: [
+          {
+            title: "JSON Note",
+            path: "json/note.md",
+            score: 0.95,
+            content: "JSON content",
+          },
+        ],
+      });
 
       const formatted = ToolResultFormatter.format("localSearch", jsonResult);
 
       expect(formatted).toContain("📚 Found 1 relevant notes");
-      // The formatSearchItem function uses path.split('/').pop() to get filename
-      // and removes .md extension, so "json/note.md" becomes "note"
       expect(formatted).toContain("1. note");
       expect(formatted).toContain("📊 Relevance: 0.9500");
     });
diff --git a/src/tools/ToolResultFormatter.ts b/src/tools/ToolResultFormatter.ts
index b6a60de..a0b79d9 100644
--- a/src/tools/ToolResultFormatter.ts
+++ b/src/tools/ToolResultFormatter.ts
@@ -77,18 +77,19 @@ export class ToolResultFormatter {
           return "📚 Found 0 relevant notes\n\nNo matching notes found.";
         }
 
-        // Extract document information for display
+        // Robustly extract document information regardless of tag ordering
         const documents: any[] = [];
-        const docRegex =
-          /<document>\s*<title>([^<]*)<\/title>(?:\s*<path>([^<]*)<\/path>)?(?:\s*<modified>([^<]*)<\/modified>)?[\s\S]*?<\/document>/g;
-        let match;
-
-        while ((match = docRegex.exec(xmlContent)) !== null) {
-          documents.push({
-            title: match[1] || "Untitled",
-            path: match[2] || "",
-            mtime: match[3] || null,
-          });
+        const blockRegex = /<document>([\s\S]*?)<\/document>/g;
+        let blockMatch;
+        while ((blockMatch = blockRegex.exec(xmlContent)) !== null) {
+          const block = blockMatch[1];
+          const titleMatch = block.match(/<title>([\s\S]*?)<\/title>/);
+          const pathMatch = block.match(/<path>([\s\S]*?)<\/path>/);
+          const modifiedMatch = block.match(/<modified>([\s\S]*?)<\/modified>/);
+          const title = (titleMatch?.[1] || "Untitled").trim();
+          const path = (pathMatch?.[1] || "").trim();
+          const mtime = (modifiedMatch?.[1] || "").trim();
+          documents.push({ title, path, mtime: mtime || null });
         }
 
         const topResults = documents.slice(0, 10);
@@ -137,14 +138,24 @@ export class ToolResultFormatter {
   }
 
   private static parseSearchResults(result: any): any[] {
-    if (Array.isArray(result)) return result;
-    if (typeof result === "object" && result !== null) return [result];
+    // Only support the new structured format or pre-formatted XML flow
+    if (typeof result === "object" && result !== null) {
+      if ((result as any).type === "local_search" && Array.isArray((result as any).documents)) {
+        return (result as any).documents;
+      }
+      return [];
+    }
     if (typeof result === "string") {
-      const trimmed = result.trim();
-      if (!trimmed.startsWith("[") && !trimmed.startsWith("{")) {
-        return [];
+      // Allow parsing of structured JSON string
+      try {
+        const parsed = JSON.parse(result);
+        if (parsed && parsed.type === "local_search" && Array.isArray(parsed.documents)) {
+          return parsed.documents;
+        }
+      } catch {
+        // ignore JSON parse errors; fall through to empty array
       }
-      return this.tryParseJson(result);
+      return [];
     }
     return [];
   }
diff --git a/src/tools/builtinTools.ts b/src/tools/builtinTools.ts
index 8cd3e60..ae5caf2 100644
--- a/src/tools/builtinTools.ts
+++ b/src/tools/builtinTools.ts
@@ -24,6 +24,7 @@ export const BUILTIN_TOOLS: ToolDefinition[] = [
       displayName: "Vault Search",
       description: "Search through your vault notes",
       category: "search",
+      copilotCommands: ["@vault"],
       customPromptInstructions: `For localSearch (searching notes in the vault):
 - You MUST always provide both "query" (string) and "salientTerms" (array of strings)
 - salientTerms MUST be extracted from the user's original query - never invent new terms
@@ -85,6 +86,7 @@ For localSearch with non-English query (PRESERVE ORIGINAL LANGUAGE):
       displayName: "Web Search",
       description: "Search the internet for information",
       category: "search",
+      copilotCommands: ["@websearch", "@web"],
       customPromptInstructions: `For webSearch:
 - Only use when the user explicitly requests web/internet search
 - Always provide an empty chatHistory array
@@ -182,6 +184,7 @@ Example - "what time is 6pm PT in Tokyo" (PT is UTC-8 or UTC-7, Tokyo is UTC+9):
       displayName: "Pomodoro Timer",
       description: "Manage time with Pomodoro technique",
       category: "time",
+      copilotCommands: ["@pomodoro"],
     },
   },
 
@@ -194,6 +197,7 @@ Example - "what time is 6pm PT in Tokyo" (PT is UTC-8 or UTC-7, Tokyo is UTC+9):
       description: "Create or modify files in your vault",
       category: "file",
       requiresVault: true,
+      copilotCommands: ["@composer"],
       customPromptInstructions: `For writeToFile:
 - NEVER display the file content directly in your response
 - Always pass the complete file content to the tool
@@ -250,6 +254,7 @@ Example usage:
       displayName: "YouTube Transcription",
       description: "Get transcripts from YouTube videos",
       category: "media",
+      copilotCommands: ["@youtube"],
       customPromptInstructions: `For youtubeTranscription:
 - Use when user provides YouTube URLs
 - No parameters needed - the tool will process URLs from the conversation



Test patch:
diff --git a/src/integration_tests/AgentPrompt.test.ts b/src/integration_tests/AgentPrompt.test.ts
index d70c8e8..a184dc0 100644
--- a/src/integration_tests/AgentPrompt.test.ts
+++ b/src/integration_tests/AgentPrompt.test.ts
@@ -287,11 +287,11 @@ describe("Agent Prompt Integration Test - Direct Model Testing", () => {
       <title>test.md</title>
       <content>
       New York City, USA - The city that never sleeps, New York buzzes with energy, towering skyscrapers, cultural diversity, and endless ambition. From Broadway to Wall Street, it’s a global symbol of dreams and hustle.
-          
+
       Tokyo, Japan - A dazzling blend of futuristic tech and centuries-old tradition, Tokyo moves fast yet bows deep. Neon lights, tranquil shrines, and sushi perfection coexist in this mesmerizing metropolis.
-          
+
       Paris, France - Romantic and refined, Paris is a living museum of art, fashion, and gastronomy. Every street corner whispers history, and every café terrace invites you to linger.
-          
+
       London, UK - A city of kings and punks, rain and rebellion. London blends royal heritage with cutting-edge creativity, from the Tower of London to Shoreditch street art.
       </content>
       </active_note>
@@ -301,13 +301,9 @@ describe("Agent Prompt Integration Test - Direct Model Testing", () => {
           toolName: "replaceInFile",
           // Check if args.diff contains the correct search text
           argumentValidator: (args) => {
-            expect(args).toEqual(
-              expect.objectContaining({
-                path: "test.md",
-                diff: expect.stringContaining(
-                  `------- SEARCH\nLondon, UK - A city of kings and punks, rain and rebellion. London blends royal heritage with cutting-edge creativity, from the Tower of London to Shoreditch street art.\n=======`
-                ),
-              })
+            expect(args.path).toBe("test.md");
+            expect(args.diff).toContain(
+              `------- SEARCH\nLondon, UK - A city of kings and punks, rain and rebellion. London blends royal heritage with cutting-edge creativity, from the Tower of London to Shoreditch street art.\n=======`
             );
           },
           mockedReturnValue: "File updated successfully",
@@ -323,11 +319,11 @@ describe("Agent Prompt Integration Test - Direct Model Testing", () => {
       <title>test.md</title>
       <content>
       New York City, USA - The city that never sleeps, New York buzzes with energy, towering skyscrapers, cultural diversity, and endless ambition. From Broadway to Wall Street, it’s a global symbol of dreams and hustle.
-          
+
       Tokyo, Japan - A dazzling blend of futuristic tech and centuries-old tradition, Tokyo moves fast yet bows deep. Neon lights, tranquil shrines, and sushi perfection coexist in this mesmerizing metropolis.
-          
+
       Paris, France - Romantic and refined, Paris is a living museum of art, fashion, and gastronomy. Every street corner whispers history, and every café terrace invites you to linger.
-          
+
       London, UK - A city of kings and punks, rain and rebellion. London blends royal heritage with cutting-edge creativity, from the Tower of London to Shoreditch street art.
       </content>
       </active_note>