diff --git a/context/CONTEXT_2026-02-20_innerhtml.md b/context/CONTEXT_2026-02-20_innerhtml.md
new file mode 100644
index 00000000..bed19ae2
--- /dev/null
+++ b/context/CONTEXT_2026-02-20_innerhtml.md
@@ -0,0 +1,17 @@
+# Context: innerHTML Migration for ShareNostrModal.js
+
+**Date:** 2026-02-20
+**Agent:** bitvid-innerhtml-migration-agent (via Jules)
+**Target File:** `js/ui/components/ShareNostrModal.js`
+**Reason:** High count (3 assignments). Contains static SVG injection and template loading.
+
+**Environment:**
+- Node: v22.22.0
+- npm: 11.7.0
+
+**Plan:**
+1. Analyze `innerHTML` assignments.
+2. Replace `wrapper.innerHTML = html` with `DOMParser` or similar safe parsing.
+3. Replace `this.relayPills.innerHTML = ""` with `replaceChildren()`.
+4. Replace `removeButton.innerHTML = SVG` with `createElementNS` or safe SVG construction.
+5. Verify with lint and tests.
diff --git a/decisions/DECISIONS_2026-02-20_innerhtml.md b/decisions/DECISIONS_2026-02-20_innerhtml.md
new file mode 100644
index 00000000..99e7b2f8
--- /dev/null
+++ b/decisions/DECISIONS_2026-02-20_innerhtml.md
@@ -0,0 +1,37 @@
+# Decisions: innerHTML Migration for ShareNostrModal.js
+
+## 1. Template Loading (L107)
+**Original:** `wrapper.innerHTML = html;`
+**New:**
+```javascript
+const parser = new DOMParser();
+const doc = parser.parseFromString(html, "text/html");
+wrapper.replaceChildren(...doc.body.childNodes);
+```
+**Rationale:** Avoids innerHTML while parsing the fetched HTML string safely.
+
+## 2. Clearing Content (L274)
+**Original:** `this.relayPills.innerHTML = "";`
+**New:** `this.relayPills.replaceChildren();`
+**Rationale:** Modern, faster, and safer method to clear DOM nodes.
+
+## 3. SVG Icon (L300)
+**Original:** `removeButton.innerHTML = '';`
+**New:**
+```javascript
+const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+svg.setAttribute("class", "h-3 w-3");
+svg.setAttribute("viewBox", "0 0 24 24");
+svg.setAttribute("fill", "none");
+svg.setAttribute("stroke", "currentColor");
+svg.setAttribute("stroke-width", "2");
+
+const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
+path.setAttribute("d", "M18 6L6 18M6 6l12 12");
+path.setAttribute("stroke-linecap", "round");
+path.setAttribute("stroke-linejoin", "round");
+
+svg.appendChild(path);
+removeButton.appendChild(svg);
+```
+**Rationale:** Using `createElementNS` is the correct way to build SVG elements without `innerHTML`.
diff --git a/docs/agents/task-logs/daily/2026-02-20_innerhtml-migration-agent_completed.md b/docs/agents/task-logs/daily/2026-02-20_innerhtml-migration-agent_completed.md
new file mode 100644
index 00000000..873d3390
--- /dev/null
+++ b/docs/agents/task-logs/daily/2026-02-20_innerhtml-migration-agent_completed.md
@@ -0,0 +1,24 @@
+# innerHTML Migration Task: ShareNostrModal.js
+
+**Date:** 2026-02-20
+**Agent:** innerhtml-migration-agent
+**Status:** Completed
+
+## Migrated File
+* `js/ui/components/ShareNostrModal.js` (3 innerHTML assignments migrated)
+
+## Changes
+1. **Template Loading:** Replaced `wrapper.innerHTML = html` with `DOMParser` logic.
+2. **Clearing Content:** Replaced `this.relayPills.innerHTML = ""` with `replaceChildren()`.
+3. **SVG Icon:** Replaced `removeButton.innerHTML = SVG` with `createElementNS`.
+
+## Verification
+* `npm run lint` passed.
+* `npm run test:unit` passed.
+* `node scripts/check-innerhtml.mjs --report` confirmed count reduction (3 -> 0 for this file).
+* `scripts/check-innerhtml.mjs` baseline updated.
+
+## Artifacts
+* `context/CONTEXT_2026-02-20_innerhtml.md`
+* `todo/TODO_2026-02-20_innerhtml.md`
+* `decisions/DECISIONS_2026-02-20_innerhtml.md`
diff --git a/js/ui/components/ShareNostrModal.js b/js/ui/components/ShareNostrModal.js
index 0f543b35..a122a938 100644
--- a/js/ui/components/ShareNostrModal.js
+++ b/js/ui/components/ShareNostrModal.js
@@ -107,7 +107,9 @@ export class ShareNostrModal {
const html = await response.text();
const wrapper = document.createElement("div");
- wrapper.innerHTML = html;
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(html, "text/html");
+ wrapper.replaceChildren(...Array.from(doc.body.childNodes));
this.removeTrackingScripts(wrapper);
targetContainer.appendChild(wrapper);
modal = wrapper.querySelector("#shareNostrModal");
@@ -273,7 +275,7 @@ export class ShareNostrModal {
if (!this.relayPills) {
return;
}
- this.relayPills.innerHTML = "";
+ this.relayPills.replaceChildren();
if (!this.currentRelays.length) {
const placeholder = document.createElement("span");
placeholder.className = "text-xs text-muted";
@@ -299,8 +301,21 @@ export class ShareNostrModal {
removeButton.setAttribute("aria-label", `Remove ${relay}`);
removeButton.dataset.action = "remove-relay";
removeButton.dataset.relay = relay;
- removeButton.innerHTML =
- '';
+
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ svg.setAttribute("class", "h-3 w-3");
+ svg.setAttribute("viewBox", "0 0 24 24");
+ svg.setAttribute("fill", "none");
+ svg.setAttribute("stroke", "currentColor");
+ svg.setAttribute("stroke-width", "2");
+
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
+ path.setAttribute("d", "M18 6L6 18M6 6l12 12");
+ path.setAttribute("stroke-linecap", "round");
+ path.setAttribute("stroke-linejoin", "round");
+
+ svg.appendChild(path);
+ removeButton.appendChild(svg);
pill.appendChild(removeButton);
this.relayPills.appendChild(pill);
diff --git a/scripts/check-innerhtml.mjs b/scripts/check-innerhtml.mjs
index 1a27dbe9..a290c95b 100644
--- a/scripts/check-innerhtml.mjs
+++ b/scripts/check-innerhtml.mjs
@@ -33,7 +33,6 @@ const BASELINE = {
"js/ui/components/EmbedVideoModal.js": 1,
"js/ui/components/EventDetailsModal.js": 1,
"js/ui/components/nip71FormManager.js": 1,
- "js/ui/components/ShareNostrModal.js": 3,
"js/ui/components/UploadModal.js": 1,
"js/ui/components/VideoModal.js": 1,
"js/ui/dm/AppShell.js": 1,
diff --git a/todo/TODO_2026-02-20_innerhtml.md b/todo/TODO_2026-02-20_innerhtml.md
new file mode 100644
index 00000000..239cd222
--- /dev/null
+++ b/todo/TODO_2026-02-20_innerhtml.md
@@ -0,0 +1,8 @@
+# TODO: innerHTML Migration for ShareNostrModal.js
+
+- [ ] L107: `wrapper.innerHTML = html;` (Template loading)
+ - Strategy: Use `new DOMParser().parseFromString(html, 'text/html')` and append children to `wrapper`.
+- [ ] L274: `this.relayPills.innerHTML = "";` (Clearing content)
+ - Strategy: `this.relayPills.replaceChildren();` or `textContent = ""` (replaceChildren is preferred for performance).
+- [ ] L300: `removeButton.innerHTML = '';` (SVG Icon)
+ - Strategy: Create SVG elements using `document.createElementNS("http://www.w3.org/2000/svg", "svg")` and children.