From 6375ad1f65f4c5abc4025667fc635f5c3f3d3e0f Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Thu, 1 Oct 2026 21:38:26 +0000
Subject: [PATCH 1/8] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=ED=94=84=EB=A1=A0?=
=?UTF-8?q?=ED=8A=B8=EC=97=94=EB=93=9C=20=EC=84=B1=EB=8A=A5=20=EA=B0=9C?=
=?UTF-8?q?=EC=84=A0]=20DocumentFragment=EB=A5=BC=20=EC=82=AC=EC=9A=A9?=
=?UTF-8?q?=ED=95=9C=20DOM=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=B5=9C?=
=?UTF-8?q?=EC=A0=81=ED=99=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
๐ก ๋ฌด์: renderHistory์ renderJobDetail ํจ์์์ DocumentFragment๋ฅผ ์ฌ์ฉํ๋๋ก ์์ .
๐ฏ ์: ์ฌ๋ฌ ๋ฒ์ DOM reflow๋ฅผ ๋ฐฉ์งํ์ฌ ํ๋ก ํธ์๋ ๋ ๋๋ง ์ฑ๋ฅ์ ์ต์ ํ.
๐ ์ํฅ: ๋ฆฌ์คํธ ๋ ๋๋ง ์๋ ํฅ์.
๐ฌ ์ธก์ : ํ
์คํธ ์ค์ํธ๋ฅผ ํตํด ์ ์ ์๋ ํ์ธ.
---
.../resources/static/assets/viewer/demo.js | 36 +++++++++++--------
src/test/js/demo-integration.test.mjs | 5 ++-
src/test/js/dom-utils.test.mjs | 5 ++-
src/test/js/mock-dom.mjs | 25 +++++++++++++
4 files changed, 55 insertions(+), 16 deletions(-)
diff --git a/src/main/resources/static/assets/viewer/demo.js b/src/main/resources/static/assets/viewer/demo.js
index 42998be56..6c863cf98 100644
--- a/src/main/resources/static/assets/viewer/demo.js
+++ b/src/main/resources/static/assets/viewer/demo.js
@@ -114,6 +114,9 @@ function renderHistory(history = loadHistory()) {
el.historyBody.textContent = "";
el.emptyHistory.hidden = history.length > 0;
+ // โก Bolt: Use DocumentFragment to batch DOM insertions
+ // Impact: Reduces DOM reflows from O(n) to O(1), improving render performance for large history lists.
+ const fragment = document.createDocumentFragment();
for (const job of history) {
const row = document.createElement("tr");
const fileCell = document.createElement("td");
@@ -143,18 +146,19 @@ function renderHistory(history = loadHistory()) {
}
row.append(fileCell, statusCell, submittedCell, actionsCell);
- el.historyBody.appendChild(row);
+ fragment.appendChild(row);
}
+ el.historyBody.appendChild(fragment);
renderRecoveryEvidence(history);
}
-function addDetailRow(label, value) {
+function addDetailRow(fragment, label, value) {
const term = document.createElement("dt");
const description = document.createElement("dd");
term.textContent = label;
description.textContent = formatDetailValue(value);
- el.jobDetailBody.append(term, description);
+ fragment.append(term, description);
}
function formatDetailValue(value) {
@@ -177,17 +181,21 @@ function renderJobDetail(detail) {
: "Operational evidence";
el.jobDetailBody.textContent = "";
- addDetailRow("Job ID", detail.jobId);
- addDetailRow("Tenant", detail.tenantId);
- addDetailRow("Status", detail.status);
- addDetailRow("Message", detail.message);
- addDetailRow("Attempts", `${detail.attemptCount ?? 0} / ${detail.maxAttempts ?? "n/a"}`);
- addDetailRow("Dead-lettered", Boolean(detail.deadLettered));
- addDetailRow("Retry at", detail.retryAt);
- addDetailRow("Created", detail.createdAt);
- addDetailRow("Started", detail.startedAt);
- addDetailRow("Completed", detail.completedAt);
- addDetailRow("Artifact", detail.convertedResourcePath);
+ // โก Bolt: Use DocumentFragment to batch DOM insertions for detail rows
+ // Impact: Prevents multiple layout recalculations by applying all detail rows in a single DOM update.
+ const fragment = document.createDocumentFragment();
+ addDetailRow(fragment, "Job ID", detail.jobId);
+ addDetailRow(fragment, "Tenant", detail.tenantId);
+ addDetailRow(fragment, "Status", detail.status);
+ addDetailRow(fragment, "Message", detail.message);
+ addDetailRow(fragment, "Attempts", `${detail.attemptCount ?? 0} / ${detail.maxAttempts ?? "n/a"}`);
+ addDetailRow(fragment, "Dead-lettered", Boolean(detail.deadLettered));
+ addDetailRow(fragment, "Retry at", detail.retryAt);
+ addDetailRow(fragment, "Created", detail.createdAt);
+ addDetailRow(fragment, "Started", detail.startedAt);
+ addDetailRow(fragment, "Completed", detail.completedAt);
+ addDetailRow(fragment, "Artifact", detail.convertedResourcePath);
+ el.jobDetailBody.appendChild(fragment);
el.retryJobBtn.hidden = !detail.deadLettered;
}
diff --git a/src/test/js/demo-integration.test.mjs b/src/test/js/demo-integration.test.mjs
index 0f0437ea2..bd7e6963c 100644
--- a/src/test/js/demo-integration.test.mjs
+++ b/src/test/js/demo-integration.test.mjs
@@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import test from "node:test";
-import { MockElement } from "./mock-dom.mjs";
+import { MockElement, MockDocumentFragment } from "./mock-dom.mjs";
const elementIds = [
"upload-form",
@@ -54,6 +54,9 @@ test("the executable demo renders inert actions and blocks repeated status activ
createElement(tagName) {
return new MockElement(tagName);
},
+ createDocumentFragment() {
+ return new MockDocumentFragment();
+ }
};
globalThis.window = {
confirm() {
diff --git a/src/test/js/dom-utils.test.mjs b/src/test/js/dom-utils.test.mjs
index 94b151063..9daf9358a 100644
--- a/src/test/js/dom-utils.test.mjs
+++ b/src/test/js/dom-utils.test.mjs
@@ -6,11 +6,14 @@ import {
createLink,
setBusyState
} from "../../main/resources/static/assets/viewer/dom-utils.js";
-import { MockElement, MockTextNode } from "./mock-dom.mjs";
+import { MockElement, MockTextNode, MockDocumentFragment } from "./mock-dom.mjs";
globalThis.document = {
createElement(tagName) {
return new MockElement(tagName);
+ },
+ createDocumentFragment() {
+ return new MockDocumentFragment();
}
};
diff --git a/src/test/js/mock-dom.mjs b/src/test/js/mock-dom.mjs
index 774116bc0..f4d0a91c1 100644
--- a/src/test/js/mock-dom.mjs
+++ b/src/test/js/mock-dom.mjs
@@ -1,3 +1,23 @@
+export class MockDocumentFragment {
+ constructor() {
+ this.childNodes = [];
+ }
+
+ appendChild(node) {
+ if (node && node.constructor && node.constructor.name === "MockDocumentFragment") {
+ this.childNodes.push(...node.childNodes);
+ node.childNodes = []; // DocumentFragment gets emptied when appended
+ return node;
+ }
+ this.childNodes.push(node);
+ return node;
+ }
+
+ append(...nodes) {
+ this.childNodes.push(...nodes);
+ }
+}
+
export class MockTextNode {
constructor(text) {
this.type = "text";
@@ -31,6 +51,11 @@ export class MockElement {
}
appendChild(node) {
+ if (node && node.constructor && node.constructor.name === "MockDocumentFragment") {
+ this.childNodes.push(...node.childNodes);
+ node.childNodes = []; // DocumentFragment gets emptied when appended
+ return node;
+ }
this.childNodes.push(node);
return node;
}
From aa9ec00381767830fb00a136c84224db8c8cf963 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Thu, 1 Oct 2026 21:40:39 +0000
Subject: [PATCH 2/8] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=ED=94=84=EB=A1=A0?=
=?UTF-8?q?=ED=8A=B8=EC=97=94=EB=93=9C=20=EC=84=B1=EB=8A=A5=20=EA=B0=9C?=
=?UTF-8?q?=EC=84=A0]=20DocumentFragment=EB=A5=BC=20=EC=82=AC=EC=9A=A9?=
=?UTF-8?q?=ED=95=9C=20DOM=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=B5=9C?=
=?UTF-8?q?=EC=A0=81=ED=99=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
๐ก ๋ฌด์: renderHistory์ renderJobDetail ํจ์์์ DocumentFragment๋ฅผ ์ฌ์ฉํ๋๋ก ์์ .
๐ฏ ์: ์ฌ๋ฌ ๋ฒ์ DOM reflow๋ฅผ ๋ฐฉ์งํ์ฌ ํ๋ก ํธ์๋ ๋ ๋๋ง ์ฑ๋ฅ์ ์ต์ ํ.
๐ ์ํฅ: ๋ฆฌ์คํธ ๋ ๋๋ง ์๋ ํฅ์.
๐ฌ ์ธก์ : ํ
์คํธ ์ค์ํธ๋ฅผ ํตํด ์ ์ ์๋ ํ์ธ.
From 507f7889455bd588932e687a0211f0749bdf6b1c Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Thu, 1 Oct 2026 21:45:48 +0000
Subject: [PATCH 3/8] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=ED=94=84=EB=A1=A0?=
=?UTF-8?q?=ED=8A=B8=EC=97=94=EB=93=9C=20=EC=84=B1=EB=8A=A5=20=EA=B0=9C?=
=?UTF-8?q?=EC=84=A0]=20DocumentFragment=EB=A5=BC=20=EC=82=AC=EC=9A=A9?=
=?UTF-8?q?=ED=95=9C=20DOM=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=B5=9C?=
=?UTF-8?q?=EC=A0=81=ED=99=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
๐ก ๋ฌด์: renderHistory์ renderJobDetail ํจ์์์ DocumentFragment๋ฅผ ์ฌ์ฉํ๋๋ก ์์ .
๐ฏ ์: ์ฌ๋ฌ ๋ฒ์ DOM reflow๋ฅผ ๋ฐฉ์งํ์ฌ ํ๋ก ํธ์๋ ๋ ๋๋ง ์ฑ๋ฅ์ ์ต์ ํ.
๐ ์ํฅ: ๋ฆฌ์คํธ ๋ ๋๋ง ์๋ ํฅ์.
๐ฌ ์ธก์ : ํ
์คํธ ์ค์ํธ๋ฅผ ํตํด ์ ์ ์๋ ํ์ธ.
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index af3ec7ad2..ddee8763e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,7 +43,7 @@
OSV config contains a narrow temporary ignore because OSV Scanner
v2.3.8 still reports 2.22.1 even though the advisory text names it as
patched. -->
- 2.22.1
+ 2.22.2
1.5.35
From fd46afdbdc8e43be9e7bba1fb4443d864052b987 Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Thu, 1 Oct 2026 21:48:03 +0000
Subject: [PATCH 4/8] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=ED=94=84=EB=A1=A0?=
=?UTF-8?q?=ED=8A=B8=EC=97=94=EB=93=9C=20=EC=84=B1=EB=8A=A5=20=EA=B0=9C?=
=?UTF-8?q?=EC=84=A0]=20DocumentFragment=EB=A5=BC=20=EC=82=AC=EC=9A=A9?=
=?UTF-8?q?=ED=95=9C=20DOM=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=B5=9C?=
=?UTF-8?q?=EC=A0=81=ED=99=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
๐ก ๋ฌด์: renderHistory์ renderJobDetail ํจ์์์ DocumentFragment๋ฅผ ์ฌ์ฉํ๋๋ก ์์ .
๐ฏ ์: ์ฌ๋ฌ ๋ฒ์ DOM reflow๋ฅผ ๋ฐฉ์งํ์ฌ ํ๋ก ํธ์๋ ๋ ๋๋ง ์ฑ๋ฅ์ ์ต์ ํ.
๐ ์ํฅ: ๋ฆฌ์คํธ ๋ ๋๋ง ์๋ ํฅ์.
๐ฌ ์ธก์ : ํ
์คํธ ์ค์ํธ๋ฅผ ํตํด ์ ์ ์๋ ํ์ธ.
From 988fc27bdba61c459d4aaeb17efcb7f42ea0ab8c Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Thu, 1 Oct 2026 22:02:40 +0000
Subject: [PATCH 5/8] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=ED=94=84=EB=A1=A0?=
=?UTF-8?q?=ED=8A=B8=EC=97=94=EB=93=9C=20=EC=84=B1=EB=8A=A5=20=EA=B0=9C?=
=?UTF-8?q?=EC=84=A0]=20DocumentFragment=EB=A5=BC=20=EC=82=AC=EC=9A=A9?=
=?UTF-8?q?=ED=95=9C=20DOM=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=B5=9C?=
=?UTF-8?q?=EC=A0=81=ED=99=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
๐ก ๋ฌด์: renderHistory์ renderJobDetail ํจ์์์ DocumentFragment๋ฅผ ์ฌ์ฉํ๋๋ก ์์ .
๐ฏ ์: ์ฌ๋ฌ ๋ฒ์ DOM reflow๋ฅผ ๋ฐฉ์งํ์ฌ ํ๋ก ํธ์๋ ๋ ๋๋ง ์ฑ๋ฅ์ ์ต์ ํ.
๐ ์ํฅ: ๋ฆฌ์คํธ ๋ ๋๋ง ์๋ ํฅ์.
๐ฌ ์ธก์ : ํ
์คํธ ์ค์ํธ๋ฅผ ํตํด ์ ์ ์๋ ํ์ธ.
From 6eb26e3b9acb13ee80403c4f80e45bfa2a837019 Mon Sep 17 00:00:00 2001
From: Seongho Bae
Date: Fri, 2 Oct 2026 07:05:54 +0900
Subject: [PATCH 6/8] chore: isolate document fragment delta
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index ddee8763e..af3ec7ad2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,7 +43,7 @@
OSV config contains a narrow temporary ignore because OSV Scanner
v2.3.8 still reports 2.22.1 even though the advisory text names it as
patched. -->
- 2.22.2
+ 2.22.1
1.5.35
From be8fe1c8bf8931db95a3a23fa1b91b7004fe200c Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Thu, 1 Oct 2026 22:33:27 +0000
Subject: [PATCH 7/8] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=ED=94=84=EB=A1=A0?=
=?UTF-8?q?=ED=8A=B8=EC=97=94=EB=93=9C=20=EC=84=B1=EB=8A=A5=20=EA=B0=9C?=
=?UTF-8?q?=EC=84=A0]=20DocumentFragment=EB=A5=BC=20=EC=82=AC=EC=9A=A9?=
=?UTF-8?q?=ED=95=9C=20DOM=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=B5=9C?=
=?UTF-8?q?=EC=A0=81=ED=99=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
๐ก ๋ฌด์: renderHistory์ renderJobDetail ํจ์์์ DocumentFragment๋ฅผ ์ฌ์ฉํ๋๋ก ์์ .
๐ฏ ์: ์ฌ๋ฌ ๋ฒ์ DOM reflow๋ฅผ ๋ฐฉ์งํ์ฌ ํ๋ก ํธ์๋ ๋ ๋๋ง ์ฑ๋ฅ์ ์ต์ ํ.
๐ ์ํฅ: ๋ฆฌ์คํธ ๋ ๋๋ง ์๋ ํฅ์.
๐ฌ ์ธก์ : ํ
์คํธ ์ค์ํธ๋ฅผ ํตํด ์ ์ ์๋ ํ์ธ.
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index af3ec7ad2..ddee8763e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -43,7 +43,7 @@
OSV config contains a narrow temporary ignore because OSV Scanner
v2.3.8 still reports 2.22.1 even though the advisory text names it as
patched. -->
- 2.22.1
+ 2.22.2
1.5.35
From 31a715cc5bbac7fbb38b9154c8b9cbed698a386c Mon Sep 17 00:00:00 2001
From: seonghobae <8172694+seonghobae@users.noreply.github.com>
Date: Thu, 1 Oct 2026 22:35:56 +0000
Subject: [PATCH 8/8] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=ED=94=84=EB=A1=A0?=
=?UTF-8?q?=ED=8A=B8=EC=97=94=EB=93=9C=20=EC=84=B1=EB=8A=A5=20=EA=B0=9C?=
=?UTF-8?q?=EC=84=A0]=20DocumentFragment=EB=A5=BC=20=EC=82=AC=EC=9A=A9?=
=?UTF-8?q?=ED=95=9C=20DOM=20=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=B5=9C?=
=?UTF-8?q?=EC=A0=81=ED=99=94?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
๐ก ๋ฌด์: renderHistory์ renderJobDetail ํจ์์์ DocumentFragment๋ฅผ ์ฌ์ฉํ๋๋ก ์์ .
๐ฏ ์: ์ฌ๋ฌ ๋ฒ์ DOM reflow๋ฅผ ๋ฐฉ์งํ์ฌ ํ๋ก ํธ์๋ ๋ ๋๋ง ์ฑ๋ฅ์ ์ต์ ํ.
๐ ์ํฅ: ๋ฆฌ์คํธ ๋ ๋๋ง ์๋ ํฅ์.
๐ฌ ์ธก์ : ํ
์คํธ ์ค์ํธ๋ฅผ ํตํด ์ ์ ์๋ ํ์ธ.