diff --git a/.jules/bolt.md b/.jules/bolt.md index b08b203a..57c56625 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -4,3 +4,6 @@ ## 2026-07-12 - Optimize renderTaskRow DOM allocations **Learning:** Caching unattached template nodes and instantiating them via `.cloneNode(false)` reduces DOM instantiation overhead in O(N) render loops significantly. **Action:** Apply this optimization to other hot-path rendering elements such as rows, cells, and stack containers. +## 2026-08-05 - Optimize DOM allocations in text/badge cell rendering +**Learning:** Repeatedly calling document.createElement() in hot O(N) render paths for small elements like text wrappers, owner badges, and status badges causes high JS-to-C++ bridge overhead and GC pressure. +**Action:** Cache these small structural DOM elements as unattached templates on first use, and instantiate them via .cloneNode(false) to significantly reduce allocation costs. diff --git a/app.js b/app.js index a04aae71..93c56cbe 100644 --- a/app.js +++ b/app.js @@ -919,6 +919,10 @@ function createTreeCellContent(value, depth) { return treeValue; } +// ⚡ Bolt: Cache text cell DOM structures as templates to avoid repetitive document.createElement() overhead +let textCellWrapperTemplate = null; +let textCellValidationTemplate = null; + function createTextCellContent(value, warning = '') { if (!value) { return warning ? createWarningBadge(warning) : createEmptyCell(); @@ -926,10 +930,16 @@ function createTextCellContent(value, warning = '') { if (!warning) { return document.createTextNode(value); } - const wrapper = document.createElement('div'); + + if (!textCellWrapperTemplate) { + textCellWrapperTemplate = document.createElement('div'); + textCellValidationTemplate = document.createElement('div'); + textCellValidationTemplate.className = 'validation-message'; + } + + const wrapper = textCellWrapperTemplate.cloneNode(false); wrapper.appendChild(document.createTextNode(value)); - const validation = document.createElement('div'); - validation.className = 'validation-message'; + const validation = textCellValidationTemplate.cloneNode(false); validation.textContent = warning; wrapper.appendChild(validation); return wrapper; @@ -972,6 +982,7 @@ function createWarningBadge(warning) { } const persistentOwnerColorMap = new Map(); +let ownerBadgeTemplate = null; function createOwnerCellContent(owner) { if (!owner) { @@ -982,18 +993,27 @@ function createOwnerCellContent(owner) { persistentOwnerColorMap.set(owner, OWNER_COLORS[persistentOwnerColorMap.size % OWNER_COLORS.length]); } - const badge = document.createElement('span'); - badge.className = 'owner-badge'; + if (!ownerBadgeTemplate) { + ownerBadgeTemplate = document.createElement('span'); + ownerBadgeTemplate.className = 'owner-badge'; + } + + const badge = ownerBadgeTemplate.cloneNode(false); badge.style.background = persistentOwnerColorMap.get(owner); badge.textContent = owner; return badge; } +let statusBadgeTemplate = null; + function createStatusCellContent(progressState) { if (!progressState.label) { return createEmptyCell(); } - const badge = document.createElement('span'); + if (!statusBadgeTemplate) { + statusBadgeTemplate = document.createElement('span'); + } + const badge = statusBadgeTemplate.cloneNode(false); badge.className = `status-badge ${progressState.className}`; badge.textContent = progressState.label; if (progressState.description) { diff --git a/package-lock.json b/package-lock.json index a1c5e22b..079e2031 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "dependencies": { "@hono/node-server": "^2.0.12", - "hono": "^4.13.1" + "hono": "^4.12.32" }, "devDependencies": { "@playwright/test": "1.61.1", @@ -31,9 +31,9 @@ } }, "node_modules/@hono/node-server": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.0.tgz", - "integrity": "sha512-XovyyCCnBzW+zKu+z/zq8hwNs4KOR5rEMAOxo2f40Q5xoOI37IMm6MIg2COOUtUApo0i6850MTBKH2u4QLGIqg==", + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.12.tgz", + "integrity": "sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==", "license": "MIT", "engines": { "node": ">=20" @@ -382,9 +382,9 @@ } }, "node_modules/hono": { - "version": "4.13.1", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.1.tgz", - "integrity": "sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==", + "version": "4.12.32", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.32.tgz", + "integrity": "sha512-XcuyW9qE2kJn07PkecMOBd5Vq/hMy7mmGw+idz1yblbg9N17ijJODrvPkn7/dwL3Kulj8LcRJ69DLOWf91dRUg==", "license": "MIT", "engines": { "node": ">=16.9.0" diff --git a/package.json b/package.json index 65735b6f..7790e678 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ }, "dependencies": { "@hono/node-server": "^2.0.12", - "hono": "^4.13.1" + "hono": "^4.12.32" }, "devDependencies": { "@playwright/test": "1.61.1",