refactor local match row rendering

This commit is contained in:
Dariusz L
2026-08-05 17:34:36 +02:00
parent d9ca634077
commit 7bd7a37312
2 changed files with 125 additions and 50 deletions
+74 -2
View File
@@ -5667,6 +5667,7 @@ test('local match status labels hash matches distinctly from exact matches', ()
test('local match refreshes preserve keyed rows and button handlers', () => {
const renderLocalMatchesContent = extractMethod(searchPanelMethodsSource, 'renderLocalMatchesContent');
const renderLocalMatchRow = extractMethod(searchPanelMethodsSource, 'renderLocalMatchRow');
const patchLocalMatchesContainer = extractMethod(
resolveDownloadMethodsSource,
'patchLocalMatchesContainer'
@@ -5678,8 +5679,9 @@ test('local match refreshes preserve keyed rows and button handlers', () => {
const wireLocalMatchButtons = extractMethod(searchPanelMethodsSource, 'wireLocalMatchButtons');
const refreshMissingListRow = extractMethod(missingBrowserMethodsSource, 'refreshMissingListRow');
assert.match(renderLocalMatchesContent, /data-local-match-key/);
assert.match(renderLocalMatchesContent, /encodeURIComponent/);
assert.match(renderLocalMatchesContent, /renderLocalMatchRow/);
assert.match(renderLocalMatchRow, /data-local-match-key/);
assert.match(renderLocalMatchRow, /encodeURIComponent/);
assert.match(patchLocalMatchesContainer, /local-match-key|localMatchKey/);
assert.match(patchLocalMatchesContainer, /mr-local-alternatives-toggle/);
assert.match(refreshLocalMatchesUiForMissing, /patchLocalMatchesContainer/);
@@ -5692,6 +5694,76 @@ test('local match refreshes preserve keyed rows and button handlers', () => {
assert.match(refreshMissingListRow, /innerHTML !== renderedSources/);
});
test('local match rows preserve best-match and alternative rendering contracts', () => {
globalThis.getSvgIcon = () => '<svg></svg>';
const renderLocalMatchRow = eval(`(${extractMethod(searchPanelMethodsSource, 'renderLocalMatchRow')})`);
const renderLocalMatchesContent = eval(`(${extractMethod(searchPanelMethodsSource, 'renderLocalMatchesContent')})`);
const dialog = {
renderLocalMatchRow,
getHashMatchLabelMap() {
return null;
},
buildContextMenuModelData() {
return { model: 'context' };
},
getLocalMatchContextData() {
return { source: 'local' };
},
getLocalMatchIdentity(match) {
return match.identity;
},
escapeHtml(value) {
return String(value).replace(/[&<>"']/g, char => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
}[char]));
},
getContextMenuAttrs() {
return ' data-context-menu="true"';
},
getConfidenceBadge(confidence) {
return `<span class="confidence">${confidence}</span>`;
},
getModelPreviewTooltipAttrs() {
return '';
},
renderLocalMatchStatusGroup() {
return '<span class="status-group">Status</span>';
},
areLocalMatchAlternativesCollapsed() {
return false;
},
};
const html = renderLocalMatchesContent.call(dialog, {
node_id: 1,
widget_index: 2,
matches: [
{
confidence: 100,
identity: 'path:/models/best.safetensors',
model: { relative_path: 'models/best.safetensors' },
},
{
confidence: 80,
identity: 'path:/models/alternative.safetensors',
model: { relative_path: 'models/alternative.safetensors' },
},
],
}, 0);
assert.match(html, /class="mr-match-row mr-best-match"/);
assert.match(html, /id="resolve-0-1-2-0"/);
assert.match(html, /data-local-match-key="path%3A%2Fmodels%2Fbest\.safetensors"/);
assert.match(html, /data-context-menu="true"/g);
assert.match(html, /data-local-match-alternative="true"/);
assert.match(html, /id="resolve-alt-0-1-2-0"/);
assert.match(html, /data-local-match-key="path%3A%2Fmodels%2Falternative\.safetensors"/);
assert.equal((html.match(/class="status-group"/g) || []).length, 2);
});
test('URN resolution preserves the download panel container', () => {
const resolveUrnAsync = extractMethod(resolveDownloadMethodsSource, 'resolveUrnAsync');
+51 -48
View File
@@ -2358,6 +2358,45 @@ export const searchPanelMethods = {
} catch (_e) {}
},
renderLocalMatchRow(missing, match, {
missingIndex = 0,
matchIndex = 0,
hashLabelMap = null,
alternative = false,
bestMatch = false,
} = {}) {
const buttonPrefix = alternative ? 'resolve-alt' : 'resolve';
const buttonId = `${buttonPrefix}-${missingIndex}-${missing.node_id}-${missing.widget_index}-${matchIndex}`;
const matchPath = match.model?.relative_path || match.model?.path || match.path || match.filename || '';
const contextModel = this.buildContextMenuModelData(
match.model || {},
match.filename || '',
this.getLocalMatchContextData(missing, match)
);
const localMatchIdentity = this.getLocalMatchIdentity?.(match) || '';
const identityAttr = localMatchIdentity
? ` data-local-match-identity="${this.escapeHtml(localMatchIdentity)}"`
: '';
const fallbackKey = alternative ? `alternative-${matchIndex}` : `match-${matchIndex}`;
const localMatchKey = localMatchIdentity
|| matchPath
|| match.filename
|| fallbackKey;
const localMatchKeyAttr = ` data-local-match-key="${this.escapeHtml(encodeURIComponent(String(localMatchKey)))}"`;
const alternativeAttr = alternative ? ' data-local-match-alternative="true"' : '';
const rowClass = `mr-match-row${bestMatch ? ' mr-best-match' : ''}`;
let html = `<div class="${rowClass}" data-local-match-index="${matchIndex}"${alternativeAttr}${localMatchKeyAttr}${identityAttr}${this.getContextMenuAttrs(contextModel)}>`;
html += this.getConfidenceBadge(match.confidence);
html += `<span class="mr-match-filename"${this.getModelPreviewTooltipAttrs(match.model || match, matchPath)}>${this.escapeHtml(matchPath)}</span>`;
html += this.renderLocalMatchStatusGroup(missing, match, hashLabelMap);
html += `<button id="${buttonId}" class="mr-btn mr-btn-secondary mr-btn-sm mr-btn-icon-only mr-local-link-btn" data-tooltip="Link this local match" aria-label="Link this local match">`;
html += getSvgIcon('link');
html += `</button>`;
html += `</div>`;
return html;
},
renderLocalMatchesContent(missing, missingIndex = 0) {
const allMatches = missing.matches || [];
const filteredMatches = allMatches.filter(m => m.confidence >= 70);
@@ -2381,33 +2420,13 @@ export const searchPanelMethods = {
for (let matchIndex = 0; matchIndex < sortedMatches.length; matchIndex++) {
const match = sortedMatches[matchIndex];
const buttonId = `resolve-${missingIndex}-${missing.node_id}-${missing.widget_index}-${matchIndex}`;
const matchPath = match.model?.relative_path || match.model?.path || match.path || match.filename || '';
const isBestMatch = matchIndex === 0 && match.confidence >= 95;
const contextModel = this.buildContextMenuModelData(
match.model || {},
match.filename || '',
this.getLocalMatchContextData(missing, match)
);
const localMatchIdentity = this.getLocalMatchIdentity?.(match) || '';
const identityAttr = localMatchIdentity
? ` data-local-match-identity="${this.escapeHtml(localMatchIdentity)}"`
: '';
const localMatchKey = localMatchIdentity
|| matchPath
|| match.filename
|| `match-${matchIndex}`;
const localMatchKeyAttr = ` data-local-match-key="${this.escapeHtml(encodeURIComponent(String(localMatchKey)))}"`;
const rowClass = `mr-match-row${isBestMatch ? ' mr-best-match' : ''}`;
html += `<div class="${rowClass}" data-local-match-index="${matchIndex}"${localMatchKeyAttr}${identityAttr}${this.getContextMenuAttrs(contextModel)}>`;
html += this.getConfidenceBadge(match.confidence);
html += `<span class="mr-match-filename"${this.getModelPreviewTooltipAttrs(match.model || match, matchPath)}>${this.escapeHtml(matchPath)}</span>`;
html += this.renderLocalMatchStatusGroup(missing, match, hashLabelMap);
html += `<button id="${buttonId}" class="mr-btn mr-btn-secondary mr-btn-sm mr-btn-icon-only mr-local-link-btn" data-tooltip="Link this local match" aria-label="Link this local match">`;
html += getSvgIcon('link');
html += `</button>`;
html += `</div>`;
html += this.renderLocalMatchRow(missing, match, {
missingIndex,
matchIndex,
hashLabelMap,
bestMatch: isBestMatch,
});
}
if (perfectMatches.length > 0 && otherMatches.length > 0) {
@@ -2422,28 +2441,12 @@ export const searchPanelMethods = {
html += `<div id="${matchId}" class="mr-stack-sm ${alternativesCollapsed ? 'mr-hidden' : ''}">`;
for (let mIdx = 0; mIdx < otherMatches.length; mIdx++) {
const match = otherMatches[mIdx];
const altBtnId = `resolve-alt-${missingIndex}-${missing.node_id}-${missing.widget_index}-${mIdx}`;
const contextModel = this.buildContextMenuModelData(
match.model || {},
match.filename || '',
this.getLocalMatchContextData(missing, match)
);
const matchPath = match.model?.relative_path || match.model?.path || match.path || match.filename || '';
const localMatchIdentity = this.getLocalMatchIdentity?.(match) || '';
const identityAttr = localMatchIdentity
? ` data-local-match-identity="${this.escapeHtml(localMatchIdentity)}"`
: '';
const localMatchKey = localMatchIdentity
|| matchPath
|| match.filename
|| `alternative-${mIdx}`;
const localMatchKeyAttr = ` data-local-match-key="${this.escapeHtml(encodeURIComponent(String(localMatchKey)))}"`;
html += `<div class="mr-match-row" data-local-match-index="${mIdx}" data-local-match-alternative="true"${localMatchKeyAttr}${identityAttr}${this.getContextMenuAttrs(contextModel)}>`;
html += this.getConfidenceBadge(match.confidence);
html += `<span class="mr-match-filename"${this.getModelPreviewTooltipAttrs(match.model || match, matchPath)}>${this.escapeHtml(matchPath)}</span>`;
html += this.renderLocalMatchStatusGroup(missing, match, hashLabelMap);
html += `<button id="${altBtnId}" class="mr-btn mr-btn-secondary mr-btn-sm mr-btn-icon-only mr-local-link-btn" data-tooltip="Link this local match" aria-label="Link this local match">${getSvgIcon('link')}</button>`;
html += `</div>`;
html += this.renderLocalMatchRow(missing, match, {
missingIndex,
matchIndex: mIdx,
hashLabelMap,
alternative: true,
});
}
html += `</div>`;
}