diff --git a/docs/agents/task-logs/daily/2026-02-16_21-43-55_innerhtml-migration-agent_completed.md b/docs/agents/task-logs/daily/2026-02-16_21-43-55_innerhtml-migration-agent_completed.md new file mode 100644 index 00000000..b7badfbc --- /dev/null +++ b/docs/agents/task-logs/daily/2026-02-16_21-43-55_innerhtml-migration-agent_completed.md @@ -0,0 +1,21 @@ +# Daily Task Log: InnerHTML Migration Agent + +- **Date:** 2026-02-16 +- **Agent:** innerhtml-migration-agent +- **Status:** Completed + +## Summary +Refactored `js/docsView.js` to replace `innerHTML` usage with safe DOM manipulation methods. + +## Actions Taken +1. Analyzed `js/docsView.js` and identified 8 instances of `innerHTML`. +2. Refactored 7 instances to use `document.createElementNS`, `replaceChildren`, and `setAttribute`. +3. Retained 1 instance for `marked.parse()` as it renders trusted Markdown content; added documentation. +4. Updated `scripts/check-innerhtml.mjs` baseline. +5. Verified changes with `npm run lint` and `npm run test:unit`. +6. Verified frontend functionality with Playwright and visual inspection. + +## Artifacts +- Code changes in `js/docsView.js` +- Updated baseline in `scripts/check-innerhtml.mjs` +- Log files moved to `artifacts/innerhtml-migration/2026-02-16/` diff --git a/js/docsView.js b/js/docsView.js index 59a37fc9..43f78a62 100644 --- a/js/docsView.js +++ b/js/docsView.js @@ -106,16 +106,32 @@ function buildLink(item) { } function getChevronIcon(expanded) { + const xmlns = "http://www.w3.org/2000/svg"; + const svg = document.createElementNS(xmlns, "svg"); + svg.setAttribute("viewBox", "0 0 20 20"); + svg.setAttribute("fill", "currentColor"); + svg.setAttribute("class", "w-4 h-4"); + + const path = document.createElementNS(xmlns, "path"); + path.setAttribute("fill-rule", "evenodd"); + path.setAttribute("clip-rule", "evenodd"); + if (expanded) { // Chevron Down - return ``; + path.setAttribute( + "d", + "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" + ); + } else { + // Chevron Right + path.setAttribute( + "d", + "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" + ); } - // Chevron Right - return ``; + + svg.appendChild(path); + return svg; } function buildToggleButton(item, controlsId, expanded, groupList) { @@ -125,12 +141,12 @@ function buildToggleButton(item, controlsId, expanded, groupList) { button.setAttribute("aria-expanded", expanded ? "true" : "false"); button.setAttribute("aria-controls", controlsId); button.setAttribute("aria-label", `Toggle ${item.title || item.slug} section`); - button.innerHTML = getChevronIcon(expanded); + button.replaceChildren(getChevronIcon(expanded)); button.addEventListener("click", () => { const isExpanded = button.getAttribute("aria-expanded") === "true"; const nextExpanded = !isExpanded; button.setAttribute("aria-expanded", nextExpanded ? "true" : "false"); - button.innerHTML = getChevronIcon(nextExpanded); + button.replaceChildren(getChevronIcon(nextExpanded)); if (groupList) { groupList.hidden = !nextExpanded; groupList.classList.toggle("hidden", !nextExpanded); @@ -199,7 +215,7 @@ function renderToc(items) { tocState.groupLookup.clear(); roots.forEach((root) => { - root.innerHTML = ""; + root.replaceChildren(); const groupPrefix = root.id ? `docs-${root.id}` : "docs-toc"; renderTocItems(items, root, 0, groupPrefix); }); @@ -237,7 +253,7 @@ function updateActiveToc(slug) { groups.forEach((group) => { if (group?.button && group?.list) { group.button.setAttribute("aria-expanded", "true"); - group.button.innerHTML = getChevronIcon(true); + group.button.replaceChildren(getChevronIcon(true)); group.list.hidden = false; group.list.classList.remove("hidden"); } @@ -439,6 +455,8 @@ async function renderMarkdown(path) { } const markdownText = await response.text(); const html = window.marked.parse(markdownText); + // TRUSTED: Docs content is internal and trusted. + // Migration blocked: No sanitizer available (DOMPurify). container.innerHTML = html; highlightCodeBlocks(container); const schedule = @@ -450,8 +468,10 @@ async function renderMarkdown(path) { }); } catch (error) { logger.user.error("Error loading docs content.", error); - container.innerHTML = - '
Error loading content. Please try again later.
'; + const p = document.createElement("p"); + p.className = "text-critical-strong"; + p.textContent = "Error loading content. Please try again later."; + container.replaceChildren(p); } } @@ -619,13 +639,17 @@ async function initDocsView() { logger.user.error("Failed to initialize docs view.", error); const tocRoot = document.getElementById(TOC_LIST_ID); if (tocRoot) { - tocRoot.innerHTML = - 'Unable to load docs table of contents.
'; + const p = document.createElement("p"); + p.className = "text-critical-strong"; + p.textContent = "Unable to load docs table of contents."; + tocRoot.replaceChildren(p); } const container = document.getElementById("markdown-container"); if (container) { - container.innerHTML = - 'Error loading content. Please try again later.
'; + const p = document.createElement("p"); + p.className = "text-critical-strong"; + p.textContent = "Error loading content. Please try again later."; + container.replaceChildren(p); } } } diff --git a/scripts/check-innerhtml.mjs b/scripts/check-innerhtml.mjs index a294fc49..e218763e 100644 --- a/scripts/check-innerhtml.mjs +++ b/scripts/check-innerhtml.mjs @@ -24,7 +24,7 @@ const SOURCE_DIR = join(ROOT, "js"); const BASELINE = { "js/app.js": 1, "js/app/feedCoordinator.js": 1, - "js/docsView.js": 8, + "js/docsView.js": 1, "js/exploreView.js": 1, "js/forYouView.js": 1, "js/historyView.js": 4,