Skip to content
Closed
41 changes: 33 additions & 8 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,7 @@ function createWarningBadge(warning) {
}

const persistentOwnerColorMap = new Map();
let ownerBadgeTemplate = null;

function createOwnerCellContent(owner) {
if (!owner) {
Expand All @@ -982,18 +983,31 @@ function createOwnerCellContent(owner) {
persistentOwnerColorMap.set(owner, OWNER_COLORS[persistentOwnerColorMap.size % OWNER_COLORS.length]);
}

const badge = document.createElement('span');
badge.className = 'owner-badge';
// ⚡ Bolt: Cache unattached static DOM structures as templates and use `.cloneNode(false)`
// to drastically reduce JS-to-C++ instantiation overhead in hot rendering paths (O(N) tables).
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');

// ⚡ Bolt: Use cloned templates instead of document.createElement for O(N) rendering.
if (!statusBadgeTemplate) {
statusBadgeTemplate = document.createElement('span');
}

const badge = statusBadgeTemplate.cloneNode(false);
badge.className = `status-badge ${progressState.className}`;
badge.textContent = progressState.label;
if (progressState.description) {
Expand All @@ -1014,12 +1028,24 @@ function createMetricText(value, testId = '') {
return metric;
}

let actualProgressLabelTemplate = null;
let actualProgressSrOnlyTemplate = null;
let actualProgressValidationTemplate = null;

function createActualProgressCellContent(task, taskMetrics) {
const label = document.createElement('label');
// ⚡ Bolt: Caching deeply nested/multiple DOM nodes for table cells avoids repetitive C++ bridge overhead.
if (!actualProgressLabelTemplate) {
actualProgressLabelTemplate = document.createElement('label');
actualProgressSrOnlyTemplate = document.createElement('span');
actualProgressSrOnlyTemplate.className = 'sr-only';
actualProgressValidationTemplate = document.createElement('div');
actualProgressValidationTemplate.className = 'validation-message';
}

const label = actualProgressLabelTemplate.cloneNode(false);
const fieldId = `actual-progress-${task.id}`;
label.htmlFor = fieldId;
const srOnly = document.createElement('span');
srOnly.className = 'sr-only';
const srOnly = actualProgressSrOnlyTemplate.cloneNode(false);
const rowEntityName = task.task || task.activity || task.phase || '작업';
srOnly.textContent = `실적진척상태 - ${rowEntityName}`;
if (!actualProgressSelectTemplate) {
Expand All @@ -1042,9 +1068,8 @@ function createActualProgressCellContent(task, taskMetrics) {

const warning = taskMetrics.plannedDateWarning || taskMetrics.actualDateWarning;
if (warning) {
const validation = document.createElement('div');
const validation = actualProgressValidationTemplate.cloneNode(false);
validation.id = `actual-progress-error-${task.id}`;
validation.className = 'validation-message';
validation.textContent = warning;
label.appendChild(validation);
select.setAttribute('aria-invalid', 'true');
Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading