Merge pull request #2569 from PR0M3TH3AN/agents/daily/innerhtml-migration-8582795435768461769

Refactor js/docsView.js to replace innerHTML with safe DOM APIs
This commit is contained in:
thePR0M3TH3AN authored and GitHub committed 2026-02-16 17:03:56 -05:00
commit 11ec714a71
3 files changed
+63 -18

No files matched your search

@@ -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/`
+41 -17
View File
@@ -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 `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4">
<path fill-rule="evenodd" 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" clip-rule="evenodd" />
</svg>`;
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 xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-4 h-4">
<path fill-rule="evenodd" 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" clip-rule="evenodd" />
</svg>`;
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 =
'<p class="text-critical-strong">Error loading content. Please try again later.</p>';
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 =
'<p class="text-critical-strong">Unable to load docs table of contents.</p>';
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 =
'<p class="text-critical-strong">Error loading content. Please try again later.</p>';
const p = document.createElement("p");
p.className = "text-critical-strong";
p.textContent = "Error loading content. Please try again later.";
container.replaceChildren(p);
}
}
}
+1 -1
View File
@@ -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,