diff --git a/context/CONTEXT_2026-02-13_subscriptions.md b/context/CONTEXT_2026-02-13_subscriptions.md new file mode 100644 index 00000000..83bca6fc --- /dev/null +++ b/context/CONTEXT_2026-02-13_subscriptions.md @@ -0,0 +1,16 @@ +# Context + +- **File:** `js/subscriptions.js` +- **Reason:** Migrating `innerHTML` assignments to safe DOM APIs as part of security hardening. +- **Date:** 2026-02-13 +- **Commit:** (will be added) +- **Node:** v22.22.0 +- **npm:** 11.7.0 + +## Plan + +1. Replace `innerHTML` assignments with `textContent` where possible. +2. Implement `renderStatusMessage` helper for simple messages. +3. Implement `renderLoadingIndicator` helper to replace `getSidebarLoadingMarkup`. +4. Verify changes with lint and tests. +5. Update `check-innerhtml` baseline. diff --git a/decisions/DECISIONS_2026-02-13_subscriptions.md b/decisions/DECISIONS_2026-02-13_subscriptions.md new file mode 100644 index 00000000..e86d1988 --- /dev/null +++ b/decisions/DECISIONS_2026-02-13_subscriptions.md @@ -0,0 +1,7 @@ +# Decisions + +- **File:** `js/subscriptions.js` +- **Migration:** Replaced `innerHTML` assignments with `replaceChildren()` and `document.createElement()`. +- **Helpers:** Added `_renderStatusMessage` and `_renderLoading` to `SubscriptionsManager` to encapsulate DOM creation for status and loading states. +- **Dependency:** Removed dependency on `getSidebarLoadingMarkup` (which returned a string) in this file, preferring local DOM creation. +- **Baseline:** Updated baseline for `js/subscriptions.js` from 7 to 0. diff --git a/docs/agents/task-logs/daily/2026-02-13_16-43-28_innerhtml-migration-agent_started.md b/docs/agents/task-logs/daily/2026-02-13_16-43-28_innerhtml-migration-agent_started.md new file mode 100644 index 00000000..15cd5497 --- /dev/null +++ b/docs/agents/task-logs/daily/2026-02-13_16-43-28_innerhtml-migration-agent_started.md @@ -0,0 +1,8 @@ +# Agent Task Log Entry + +- **Date:** 2026-02-13 +- **Agent:** innerhtml-migration-agent +- **Prompt:** bitvid-innerhtml-migration-agent.md +- **Status:** started +- **Branch:** chore/scheduler/innerhtml-migration-2026-02-13 +- **Summary:** Task claimed — execution beginning diff --git a/docs/agents/task-logs/daily/2026-02-13_16-50-00_innerhtml-migration-agent_completed.md b/docs/agents/task-logs/daily/2026-02-13_16-50-00_innerhtml-migration-agent_completed.md new file mode 100644 index 00000000..4336d2a6 --- /dev/null +++ b/docs/agents/task-logs/daily/2026-02-13_16-50-00_innerhtml-migration-agent_completed.md @@ -0,0 +1,8 @@ +# Agent Task Log Entry + +- **Date:** 2026-02-13 +- **Agent:** innerhtml-migration-agent +- **Prompt:** bitvid-innerhtml-migration-agent.md +- **Status:** completed +- **Branch:** ai/innerhtml-fix/subscriptions +- **Summary:** Migrated 7 innerHTML assignments in js/subscriptions.js to safe DOM APIs and updated baseline diff --git a/js/subscriptions.js b/js/subscriptions.js index d7eae458..420a6071 100644 --- a/js/subscriptions.js +++ b/js/subscriptions.js @@ -19,7 +19,6 @@ import { NOTE_TYPES, } from "./nostrEventSchemas.js"; import { CACHE_POLICIES, STORAGE_TIERS } from "./nostr/cachePolicies.js"; -import { getSidebarLoadingMarkup } from "./sidebarLoading.js"; import { publishEventToRelays, assertAnyRelayAccepted @@ -1563,8 +1562,7 @@ class SubscriptionsManager { const container = document.getElementById(containerId); if (!userPubkey) { if (container) { - container.innerHTML = - "

Please log in first.

"; + this._renderStatusMessage(container, "Please log in first."); } this.lastRunOptions = null; this.lastResult = null; @@ -1593,8 +1591,7 @@ class SubscriptionsManager { } if (!channelHexes.length) { - container.innerHTML = - "

No subscriptions found.

"; + this._renderStatusMessage(container, "No subscriptions found."); this.lastRunOptions = { actorPubkey: userPubkey, limit, @@ -1606,7 +1603,7 @@ class SubscriptionsManager { } if (!this.hasRenderedOnce) { - container.innerHTML = getSidebarLoadingMarkup("Fetching subscriptions…"); + this._renderLoading(container, "Fetching subscriptions…"); } this.lastRunOptions = { @@ -1634,8 +1631,7 @@ class SubscriptionsManager { const engine = this.getFeedEngine(); if (!engine || typeof engine.run !== "function") { - container.innerHTML = - "

Subscriptions are unavailable right now.

"; + this._renderStatusMessage(container, "Subscriptions are unavailable right now."); this.hasRenderedOnce = true; return null; } @@ -1714,8 +1710,7 @@ class SubscriptionsManager { reason: fallbackReason, }); } else if (container) { - container.innerHTML = - "

Unable to load subscriptions right now.

"; + this._renderStatusMessage(container, "Unable to load subscriptions right now."); } this.hasRenderedOnce = Boolean(container); return this.lastResult; @@ -1904,10 +1899,11 @@ class SubscriptionsManager { const listView = this.getListView(container, app); if (!listView) { - container.innerHTML = ` -

- Unable to render subscriptions feed. -

`; + this._renderStatusMessage( + container, + "Unable to render subscriptions feed.", + "flex justify-center items-center h-full w-full text-center" + ); return; } @@ -1942,7 +1938,7 @@ class SubscriptionsManager { ? message.trim() : "No playable subscription videos found yet. We'll keep watching for new posts."; - container.innerHTML = getSidebarLoadingMarkup(copy, { showSpinner: false }); + this._renderLoading(container, copy, false); if (this.subscriptionListView && this.subscriptionListView.state) { const currentMetadata = @@ -2365,6 +2361,43 @@ class SubscriptionsManager { convertEventToVideo(evt) { return sharedConvertEventToVideo(evt); } + + _renderStatusMessage(container, message, extraClasses = "") { + if (!container) return; + container.replaceChildren(); + const p = document.createElement("p"); + p.className = `text-muted-strong ${extraClasses}`.trim(); + p.textContent = message; + container.appendChild(p); + } + + _renderLoading(container, message, showSpinner = true) { + if (!container) return; + container.replaceChildren(); + + const wrapper = document.createElement("div"); + wrapper.className = "sidebar-loading-wrapper"; + wrapper.setAttribute("role", "status"); + wrapper.setAttribute("aria-live", "polite"); + + const indicator = document.createElement("div"); + indicator.className = "sidebar-loading-indicator"; + + if (showSpinner) { + const spinner = document.createElement("span"); + spinner.className = "status-spinner status-spinner--inline"; + spinner.setAttribute("aria-hidden", "true"); + indicator.appendChild(spinner); + } + + const text = document.createElement("span"); + text.className = "sidebar-loading-text"; + text.textContent = message; + indicator.appendChild(text); + + wrapper.appendChild(indicator); + container.appendChild(wrapper); + } } SubscriptionsManager.listVideoViewEvents = listVideoViewEventsApi; diff --git a/scripts/check-innerhtml.mjs b/scripts/check-innerhtml.mjs index f4890bf5..7766754d 100644 --- a/scripts/check-innerhtml.mjs +++ b/scripts/check-innerhtml.mjs @@ -31,7 +31,6 @@ const BASELINE = { "js/historyView.js": 4, "js/index.js": 1, "js/kidsView.js": 1, - "js/subscriptions.js": 7, "js/ui/components/DeleteModal.js": 3, "js/ui/components/EditModal.js": 1, "js/ui/components/EmbedVideoModal.js": 1, diff --git a/test_logs/TEST_LOG_2026-02-13_subscriptions.md b/test_logs/TEST_LOG_2026-02-13_subscriptions.md new file mode 100644 index 00000000..a32b3559 --- /dev/null +++ b/test_logs/TEST_LOG_2026-02-13_subscriptions.md @@ -0,0 +1,17 @@ +# Test Log + +- **Date:** 2026-02-13 +- **File:** `js/subscriptions.js` + +## Commands + +### Lint +`npm run lint` -> Passed (after `npm ci`). + +### innerHTML Audit +`node scripts/check-innerhtml.mjs --report` -> `js/subscriptions.js` removed from list (count 0). + +## Verification +- Replaced 7 assignments of `innerHTML` with `_renderStatusMessage` and `_renderLoading`. +- These helpers use `replaceChildren()` and `document.createElement()`. +- Verified that `getSidebarLoadingMarkup` dependency was removed from the file. diff --git a/todo/TODO_2026-02-13_subscriptions.md b/todo/TODO_2026-02-13_subscriptions.md new file mode 100644 index 00000000..4498eb0e --- /dev/null +++ b/todo/TODO_2026-02-13_subscriptions.md @@ -0,0 +1,9 @@ +# TODO - js/subscriptions.js Migration + +- [ ] Line 1566: `container.innerHTML = "

Please log in first.

";` +- [ ] Line 1596: `container.innerHTML = "

No subscriptions found.

";` +- [ ] Line 1609: `container.innerHTML = getSidebarLoadingMarkup("Fetching subscriptions…");` +- [ ] Line 1637: `container.innerHTML = "

Subscriptions are unavailable right now.

";` +- [ ] Line 1717: `container.innerHTML = "

Unable to load subscriptions right now.

";` +- [ ] Line 1907: `container.innerHTML = ...` (template literal with `p` class `flex justify-center...`) +- [ ] Line 1945: `container.innerHTML = getSidebarLoadingMarkup(copy, { showSpinner: false });`