aboutsummaryrefslogtreecommitdiff
path: root/scripts/theme-studio
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-15 19:19:38 -0500
committerCraig Jennings <c@cjennings.net>2026-06-15 19:19:38 -0500
commit7e7b871fe4f8daff724c3df37feb5572464532c1 (patch)
tree3dcfb0e760a22b460bc1188a9b6d7a6b5433a5a3 /scripts/theme-studio
parent5ab506d9b65a0d043c08690b86976b1f97889eaf (diff)
downloaddotemacs-7e7b871fe4f8daff724c3df37feb5572464532c1.tar.gz
dotemacs-7e7b871fe4f8daff724c3df37feb5572464532c1.zip
feat(theme-studio): flag unused palette tiles and columns
I added usedPaletteHexes, a reverse lookup over the syntax, ui, and package assignments (plus the ground endpoints) that resolves each reference to a hex. renderPalette outlines a tile whose color is referenced nowhere and outlines a whole column when none of its colors are used, so dead colors stand out for pruning before a theme ships. The check is biased safe: an unresolvable reference marks nothing, so a color that is actually used is never flagged. Node tests cover the lookup. A #unusedtest gate covers the tile and column flags.
Diffstat (limited to 'scripts/theme-studio')
-rw-r--r--scripts/theme-studio/app-core.js17
-rw-r--r--scripts/theme-studio/browser-gates.js21
-rw-r--r--scripts/theme-studio/palette-actions.js9
-rw-r--r--scripts/theme-studio/styles.css2
-rw-r--r--scripts/theme-studio/test-columns.mjs17
-rw-r--r--scripts/theme-studio/theme-studio.html47
6 files changed, 105 insertions, 8 deletions
diff --git a/scripts/theme-studio/app-core.js b/scripts/theme-studio/app-core.js
index 8642388e2..d21254e40 100644
--- a/scripts/theme-studio/app-core.js
+++ b/scripts/theme-studio/app-core.js
@@ -240,6 +240,21 @@ function toggleLockSet(keys,locked){
// from the palette, and ground+N entries are reserved for that column. Everything
// else groups by its stable column id, not by OKLCH hue/chroma or display name.
// Legacy two-field entries fall back to their generated-name stem until edited.
+// Reverse lookup: every palette hex referenced by an assignment (syntax, ui, or
+// package fg / bg / box-color), plus the ground endpoints, which are always in
+// use. Values may be palette names or hexes; nameToHex resolves both, so a tile
+// whose hex is absent from this set is genuinely unreferenced. Biased safe: an
+// unresolvable value simply marks nothing, so a used color is never flagged.
+function usedPaletteHexes(palette,syntax,uimap,pkgmap,ground){
+ const used=new Set();
+ const add=v=>{const h=nameToHex(v,palette);if(h)used.add(h.toLowerCase());};
+ const addFace=f=>{if(!f)return;add(f.fg);add(f.bg);if(f.box&&f.box.color)add(f.box.color);};
+ if(ground){if(ground.bg)add(ground.bg);if(ground.fg)add(ground.fg);}
+ for(const k in (syntax||{}))addFace(syntax[k]);
+ for(const face in (uimap||{}))addFace(uimap[face]);
+ for(const app in (pkgmap||{}))for(const face in pkgmap[app])addFace(pkgmap[app][face]);
+ return used;
+}
function columnsFromPalette(palette,ground){
const bg=ground&&ground.bg,fg=ground&&ground.fg;
const groundStrip=[];
@@ -352,4 +367,4 @@ function spanNeighborHex(cur,palette,ground,dir){
return null;
}
-export { nameToHex, normalizePkgFace, buildPkgmap, packagesForExport, mergePackagesInto, effResolve, resolveSyntaxFg, resolveUiAttr, dropdownRowTextColor, paletteOptionList, spanNeighborHex, slugify, fgSetFor, floor, lMax, COVERED_FACES, columnsFromPalette, regenColumn, rankByLightness, stepRepointPlan, sortColumns, sortColumnMembers, groundRoleOfEntry, groundColumnMembersFromPalette, clearPalettePlan, deletePaletteColumnPlan, areAllLocked, lockToggleLabel, toggleLockSet };
+export { nameToHex, normalizePkgFace, buildPkgmap, packagesForExport, mergePackagesInto, effResolve, resolveSyntaxFg, resolveUiAttr, dropdownRowTextColor, paletteOptionList, spanNeighborHex, slugify, fgSetFor, floor, lMax, COVERED_FACES, columnsFromPalette, usedPaletteHexes, regenColumn, rankByLightness, stepRepointPlan, sortColumns, sortColumnMembers, groundRoleOfEntry, groundColumnMembersFromPalette, clearPalettePlan, deletePaletteColumnPlan, areAllLocked, lockToggleLabel, toggleLockSet };
diff --git a/scripts/theme-studio/browser-gates.js b/scripts/theme-studio/browser-gates.js
index bca2c7aff..b6aa1c4d0 100644
--- a/scripts/theme-studio/browser-gates.js
+++ b/scripts/theme-studio/browser-gates.js
@@ -729,3 +729,24 @@ if(location.hash==='#paltoggletest'){let ok=true;const notes=[];const A=(c,n)=>{
PALETTE=saveP;for(const k in MAP)delete MAP[k];Object.assign(MAP,saveM);syncSyntaxFromCache();renderPalette();
document.title='PALTOGGLETEST '+(ok?'PASS':'FAIL');
const d=document.createElement('div');d.id='paltoggletest';d.textContent='PALTOGGLETEST '+(ok?'PASS':'FAIL')+(notes.length?' fails='+notes.join(','):'');document.body.appendChild(d);}
+// Unused-tile gate (open with #unusedtest): a palette color referenced nowhere
+// in the theme gets the .unused flag; a column with no used members gets
+// .unused-col; referenced colors stay unflagged.
+if(location.hash==='#unusedtest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}};
+ const saveP=PALETTE.slice(),saveM=Object.assign({},MAP),saveSyn=JSON.parse(JSON.stringify(SYNTAX)),saveU=JSON.parse(JSON.stringify(UIMAP));
+ setSyntaxFg('bg','#101010');setSyntaxFg('p','#f0f0f0');
+ PALETTE=[['#101010','bg','ground'],['#f0f0f0','fg','ground'],['#67809c','blue','blue'],['#123456','teal','teal']];
+ for(const f in UIMAP)UIMAP[f]={fg:null,bg:null,bold:false,italic:false,underline:false,strike:false};
+ setSyntaxFg('kw','#67809c');
+ renderPalette();
+ const tealStrip=document.querySelector('#pals .fstrip[data-column="teal"]');
+ const blueStrip=document.querySelector('#pals .fstrip[data-column="blue"]');
+ const tealChip=tealStrip&&tealStrip.querySelector('.pchip');
+ const blueChip=blueStrip&&blueStrip.querySelector('.pchip');
+ A(tealChip&&tealChip.classList.contains('unused'),'unreferenced-tile-flagged');
+ A(blueChip&&!blueChip.classList.contains('unused'),'referenced-tile-not-flagged');
+ A(tealStrip&&tealStrip.classList.contains('unused-col'),'all-unused-column-flagged');
+ A(blueStrip&&!blueStrip.classList.contains('unused-col'),'used-column-not-flagged');
+ PALETTE=saveP;for(const k in MAP)delete MAP[k];Object.assign(MAP,saveM);for(const k in SYNTAX)delete SYNTAX[k];Object.assign(SYNTAX,saveSyn);for(const f in UIMAP)delete UIMAP[f];Object.assign(UIMAP,saveU);syncSyntaxFromCache();renderPalette();
+ document.title='UNUSEDTEST '+(ok?'PASS':'FAIL');
+ const d=document.createElement('div');d.id='unusedtest';d.textContent='UNUSEDTEST '+(ok?'PASS':'FAIL')+(notes.length?' fails='+notes.join(','):'');document.body.appendChild(d);}
diff --git a/scripts/theme-studio/palette-actions.js b/scripts/theme-studio/palette-actions.js
index d5826ea0d..dd060dc8c 100644
--- a/scripts/theme-studio/palette-actions.js
+++ b/scripts/theme-studio/palette-actions.js
@@ -87,13 +87,14 @@ function renderPaletteWarnings(warnings,overflow){
}
// One palette chip for PALETTE[i], with its remove / rename / select handlers.
// Families sort deterministically, so the old move-arrow / drag reordering is gone.
-function paletteChip(i,nearest){
+function paletteChip(i,nearest,used){
const [hex,name]=PALETTE[i],tc=textOn(hex),nde=nearest[i];
const role=groundRoleOfEntry(PALETTE[i],{bg:MAP['bg'],fg:MAP['p']});
const locked=(role==='bg'||role==='fg');
const d=document.createElement('div');d.className='pchip'+(i===selectedIdx?' sel':'');d.style.background=hex;
d.dataset.paletteIndex=String(i);
d.title=name+' '+hex+(nde===Infinity||nde===undefined?'':' — nearest ΔE '+nde.toFixed(3));
+ if(used&&!used.has(hex.toLowerCase())){d.classList.add('unused');d.title+=' — not used in the theme';}
const rm=locked?`<span class="lock" title="${role==='bg'?'background':'foreground'} — can't remove" style="color:${tc}">&#128274;</span>`:`<button class="rm" title="remove" style="color:${tc}">×</button>`;
d.innerHTML=`${rm}<input class="nm" value="${name}" readonly style="color:${tc}"><div class="hx" style="color:${tc}">${hex}</div>`;
if(!locked)d.querySelector('.rm').onclick=(e)=>{e.stopPropagation();rememberGone(hex,name);PALETTE.splice(i,1);if(selectedIdx===i)selectedIdx=null;refreshPaletteState({code:false,ground:false});};
@@ -183,6 +184,7 @@ function renderPalette(){
p.appendChild(tg);
const {warnings,overflow,nearest}=paletteWarnings(PALETTE,DELTAE_MIN,5);
const {ground,columns}=columnsFromPalette(PALETTE,{bg:MAP['bg'],fg:MAP['p']});
+ const usedHexes=usedPaletteHexes(PALETTE,SYNTAX,UIMAP,PKGMAP,{bg:MAP['bg'],fg:MAP['p']});
const used=new Set();
const idxOf=(hex,name)=>{for(let i=0;i<PALETTE.length;i++)if(!used.has(i)&&PALETTE[i][0]===hex&&PALETTE[i][1]===name){used.add(i);return i;}return -1;};
const strip=(cls)=>{const s=document.createElement('div');s.className='fstrip'+(cls||'');p.appendChild(s);return s;};
@@ -192,7 +194,7 @@ function renderPalette(){
gs.appendChild(groundSpanControl());
(paletteShowFull?groundColumnMembers():groundColumnMembers().filter(m=>!/^ground[+-]\d+$/i.test(m.name||''))).forEach(m=>{
const i=idxOf(m.hex,m.name);
- if(i>=0)gs.appendChild(paletteChip(i,nearest));
+ if(i>=0)gs.appendChild(paletteChip(i,nearest,usedHexes));
else{const tc=textOn(m.hex),sw=document.createElement('div');sw.className='pchip';sw.style.background=m.hex;sw.title=(m.name||'ground')+' '+m.hex;
sw.innerHTML=`<input class="nm" value="${m.name||'ground'}" disabled style="color:${tc}"><div class="hx" style="color:${tc}">${m.hex}</div>`;gs.appendChild(sw);}
});
@@ -204,7 +206,8 @@ function renderPalette(){
const s=strip('');s.dataset.column=f.column||f.base;
s.appendChild(columnHeader(f,pos,ordered.length));
s.appendChild(columnCountControl(f));
- (paletteShowFull?f.members:f.members.filter(m=>m.hex.toLowerCase()===f.base.toLowerCase())).forEach(m=>{const i=idxOf(m.hex,m.name);if(i>=0)s.appendChild(paletteChip(i,nearest));});
+ (paletteShowFull?f.members:f.members.filter(m=>m.hex.toLowerCase()===f.base.toLowerCase())).forEach(m=>{const i=idxOf(m.hex,m.name);if(i>=0)s.appendChild(paletteChip(i,nearest,usedHexes));});
+ if(f.members.every(m=>!usedHexes.has(m.hex.toLowerCase())))s.classList.add('unused-col');
});
renderPaletteWarnings(warnings,overflow);
buildUITable();if(document.getElementById('pkgbody'))buildPkgTable();
diff --git a/scripts/theme-studio/styles.css b/scripts/theme-studio/styles.css
index 39f219843..fba40ac80 100644
--- a/scripts/theme-studio/styles.css
+++ b/scripts/theme-studio/styles.css
@@ -47,6 +47,8 @@
.cat{color:#b4b1a2} .ex{font-size:17px}
.crerr{display:inline-block;margin-left:8px;padding:0 4px;border-radius:3px;background:#2b130e;color:#cb6b4d;border:1px solid #7b3324;font:9pt monospace;vertical-align:middle}
.paltoggle{align-self:flex-start;width:22px;height:22px;padding:0;border:1px solid #3a3a3a;border-radius:4px;background:#1f1c19;color:#e8bd30;font:12px monospace;line-height:1;cursor:pointer;margin-right:6px}
+ .pchip.unused{outline:2px dashed #cb6b4d;outline-offset:-2px}
+ .fstrip.unused-col{outline:1px dashed #cb6b4d;outline-offset:2px;border-radius:8px}
.sbtn{width:17px;height:15px;border:1px solid #3a3a3a;border-radius:3px;background:#eaeaea;color:#111;cursor:pointer;font-size:13px;line-height:1;margin-right:2px;padding:0}
.sbtn.on{background:#0d0b0a;color:#cdced1;border-color:#8a9496}
.pals{display:flex;flex-direction:row;flex-wrap:wrap;gap:10px;align-items:flex-start}
diff --git a/scripts/theme-studio/test-columns.mjs b/scripts/theme-studio/test-columns.mjs
index c7e0e5160..ba949cea4 100644
--- a/scripts/theme-studio/test-columns.mjs
+++ b/scripts/theme-studio/test-columns.mjs
@@ -5,7 +5,7 @@
import { test } from 'node:test';
import assert from 'node:assert/strict';
-import { columnsFromPalette, regenColumn, groundRoleOfEntry, rankByLightness, stepRepointPlan, sortColumns } from './app-core.js';
+import { columnsFromPalette, usedPaletteHexes, regenColumn, groundRoleOfEntry, rankByLightness, stepRepointPlan, sortColumns } from './app-core.js';
const columnOf = (columns, name) => columns.find(f => f.members.some(m => m.name === name));
@@ -222,3 +222,18 @@ test('regenColumn: Boundary - a near-black bg yields no duplicate pure-black til
test('regenColumn: Boundary - no ground falls back to the black/white span', () => {
assert.equal(regenColumn('#67809c', 2).members.length, 5);
});
+
+// --- usedPaletteHexes (unused-tile flagging) --------------------------------
+test('usedPaletteHexes: Normal - records ground, syntax-by-hex, ui-by-name, pkg box color', () => {
+ const palette = [['#101010','bg','ground'],['#f0f0f0','fg','ground'],['#67809c','blue','blue'],['#aa3344','red','red'],['#123456','teal','teal']];
+ const used = usedPaletteHexes(palette, { kw: { fg: '#67809c' } }, { region: { bg: 'red' } }, { magit: { m: { box: { color: '#aa3344' } } } }, { bg: '#101010', fg: '#f0f0f0' });
+ assert.ok(used.has('#101010') && used.has('#f0f0f0'), 'ground endpoints are always used');
+ assert.ok(used.has('#67809c'), 'a syntax hex reference is recorded');
+ assert.ok(used.has('#aa3344'), 'a ui name reference resolves to its hex');
+ assert.ok(!used.has('#123456'), 'an unreferenced color is absent');
+});
+
+test('usedPaletteHexes: Boundary - empty maps leave only the ground endpoints', () => {
+ const used = usedPaletteHexes([['#101010','bg','ground'],['#f0f0f0','fg','ground']], {}, {}, {}, { bg: '#101010', fg: '#f0f0f0' });
+ assert.deepEqual([...used].sort(), ['#101010', '#f0f0f0']);
+});
diff --git a/scripts/theme-studio/theme-studio.html b/scripts/theme-studio/theme-studio.html
index e30dda001..5813342e9 100644
--- a/scripts/theme-studio/theme-studio.html
+++ b/scripts/theme-studio/theme-studio.html
@@ -49,6 +49,8 @@
.cat{color:#b4b1a2} .ex{font-size:17px}
.crerr{display:inline-block;margin-left:8px;padding:0 4px;border-radius:3px;background:#2b130e;color:#cb6b4d;border:1px solid #7b3324;font:9pt monospace;vertical-align:middle}
.paltoggle{align-self:flex-start;width:22px;height:22px;padding:0;border:1px solid #3a3a3a;border-radius:4px;background:#1f1c19;color:#e8bd30;font:12px monospace;line-height:1;cursor:pointer;margin-right:6px}
+ .pchip.unused{outline:2px dashed #cb6b4d;outline-offset:-2px}
+ .fstrip.unused-col{outline:1px dashed #cb6b4d;outline-offset:2px;border-radius:8px}
.sbtn{width:17px;height:15px;border:1px solid #3a3a3a;border-radius:3px;background:#eaeaea;color:#111;cursor:pointer;font-size:13px;line-height:1;margin-right:2px;padding:0}
.sbtn.on{background:#0d0b0a;color:#cdced1;border-color:#8a9496}
.pals{display:flex;flex-direction:row;flex-wrap:wrap;gap:10px;align-items:flex-start}
@@ -734,6 +736,21 @@ function toggleLockSet(keys,locked){
// from the palette, and ground+N entries are reserved for that column. Everything
// else groups by its stable column id, not by OKLCH hue/chroma or display name.
// Legacy two-field entries fall back to their generated-name stem until edited.
+// Reverse lookup: every palette hex referenced by an assignment (syntax, ui, or
+// package fg / bg / box-color), plus the ground endpoints, which are always in
+// use. Values may be palette names or hexes; nameToHex resolves both, so a tile
+// whose hex is absent from this set is genuinely unreferenced. Biased safe: an
+// unresolvable value simply marks nothing, so a used color is never flagged.
+function usedPaletteHexes(palette,syntax,uimap,pkgmap,ground){
+ const used=new Set();
+ const add=v=>{const h=nameToHex(v,palette);if(h)used.add(h.toLowerCase());};
+ const addFace=f=>{if(!f)return;add(f.fg);add(f.bg);if(f.box&&f.box.color)add(f.box.color);};
+ if(ground){if(ground.bg)add(ground.bg);if(ground.fg)add(ground.fg);}
+ for(const k in (syntax||{}))addFace(syntax[k]);
+ for(const face in (uimap||{}))addFace(uimap[face]);
+ for(const app in (pkgmap||{}))for(const face in pkgmap[app])addFace(pkgmap[app][face]);
+ return used;
+}
function columnsFromPalette(palette,ground){
const bg=ground&&ground.bg,fg=ground&&ground.fg;
const groundStrip=[];
@@ -1542,13 +1559,14 @@ function renderPaletteWarnings(warnings,overflow){
}
// One palette chip for PALETTE[i], with its remove / rename / select handlers.
// Families sort deterministically, so the old move-arrow / drag reordering is gone.
-function paletteChip(i,nearest){
+function paletteChip(i,nearest,used){
const [hex,name]=PALETTE[i],tc=textOn(hex),nde=nearest[i];
const role=groundRoleOfEntry(PALETTE[i],{bg:MAP['bg'],fg:MAP['p']});
const locked=(role==='bg'||role==='fg');
const d=document.createElement('div');d.className='pchip'+(i===selectedIdx?' sel':'');d.style.background=hex;
d.dataset.paletteIndex=String(i);
d.title=name+' '+hex+(nde===Infinity||nde===undefined?'':' — nearest ΔE '+nde.toFixed(3));
+ if(used&&!used.has(hex.toLowerCase())){d.classList.add('unused');d.title+=' — not used in the theme';}
const rm=locked?`<span class="lock" title="${role==='bg'?'background':'foreground'} — can't remove" style="color:${tc}">&#128274;</span>`:`<button class="rm" title="remove" style="color:${tc}">×</button>`;
d.innerHTML=`${rm}<input class="nm" value="${name}" readonly style="color:${tc}"><div class="hx" style="color:${tc}">${hex}</div>`;
if(!locked)d.querySelector('.rm').onclick=(e)=>{e.stopPropagation();rememberGone(hex,name);PALETTE.splice(i,1);if(selectedIdx===i)selectedIdx=null;refreshPaletteState({code:false,ground:false});};
@@ -1638,6 +1656,7 @@ function renderPalette(){
p.appendChild(tg);
const {warnings,overflow,nearest}=paletteWarnings(PALETTE,DELTAE_MIN,5);
const {ground,columns}=columnsFromPalette(PALETTE,{bg:MAP['bg'],fg:MAP['p']});
+ const usedHexes=usedPaletteHexes(PALETTE,SYNTAX,UIMAP,PKGMAP,{bg:MAP['bg'],fg:MAP['p']});
const used=new Set();
const idxOf=(hex,name)=>{for(let i=0;i<PALETTE.length;i++)if(!used.has(i)&&PALETTE[i][0]===hex&&PALETTE[i][1]===name){used.add(i);return i;}return -1;};
const strip=(cls)=>{const s=document.createElement('div');s.className='fstrip'+(cls||'');p.appendChild(s);return s;};
@@ -1647,7 +1666,7 @@ function renderPalette(){
gs.appendChild(groundSpanControl());
(paletteShowFull?groundColumnMembers():groundColumnMembers().filter(m=>!/^ground[+-]\d+$/i.test(m.name||''))).forEach(m=>{
const i=idxOf(m.hex,m.name);
- if(i>=0)gs.appendChild(paletteChip(i,nearest));
+ if(i>=0)gs.appendChild(paletteChip(i,nearest,usedHexes));
else{const tc=textOn(m.hex),sw=document.createElement('div');sw.className='pchip';sw.style.background=m.hex;sw.title=(m.name||'ground')+' '+m.hex;
sw.innerHTML=`<input class="nm" value="${m.name||'ground'}" disabled style="color:${tc}"><div class="hx" style="color:${tc}">${m.hex}</div>`;gs.appendChild(sw);}
});
@@ -1659,7 +1678,8 @@ function renderPalette(){
const s=strip('');s.dataset.column=f.column||f.base;
s.appendChild(columnHeader(f,pos,ordered.length));
s.appendChild(columnCountControl(f));
- (paletteShowFull?f.members:f.members.filter(m=>m.hex.toLowerCase()===f.base.toLowerCase())).forEach(m=>{const i=idxOf(m.hex,m.name);if(i>=0)s.appendChild(paletteChip(i,nearest));});
+ (paletteShowFull?f.members:f.members.filter(m=>m.hex.toLowerCase()===f.base.toLowerCase())).forEach(m=>{const i=idxOf(m.hex,m.name);if(i>=0)s.appendChild(paletteChip(i,nearest,usedHexes));});
+ if(f.members.every(m=>!usedHexes.has(m.hex.toLowerCase())))s.classList.add('unused-col');
});
renderPaletteWarnings(warnings,overflow);
buildUITable();if(document.getElementById('pkgbody'))buildPkgTable();
@@ -3251,4 +3271,25 @@ if(location.hash==='#paltoggletest'){let ok=true;const notes=[];const A=(c,n)=>{
PALETTE=saveP;for(const k in MAP)delete MAP[k];Object.assign(MAP,saveM);syncSyntaxFromCache();renderPalette();
document.title='PALTOGGLETEST '+(ok?'PASS':'FAIL');
const d=document.createElement('div');d.id='paltoggletest';d.textContent='PALTOGGLETEST '+(ok?'PASS':'FAIL')+(notes.length?' fails='+notes.join(','):'');document.body.appendChild(d);}
+// Unused-tile gate (open with #unusedtest): a palette color referenced nowhere
+// in the theme gets the .unused flag; a column with no used members gets
+// .unused-col; referenced colors stay unflagged.
+if(location.hash==='#unusedtest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}};
+ const saveP=PALETTE.slice(),saveM=Object.assign({},MAP),saveSyn=JSON.parse(JSON.stringify(SYNTAX)),saveU=JSON.parse(JSON.stringify(UIMAP));
+ setSyntaxFg('bg','#101010');setSyntaxFg('p','#f0f0f0');
+ PALETTE=[['#101010','bg','ground'],['#f0f0f0','fg','ground'],['#67809c','blue','blue'],['#123456','teal','teal']];
+ for(const f in UIMAP)UIMAP[f]={fg:null,bg:null,bold:false,italic:false,underline:false,strike:false};
+ setSyntaxFg('kw','#67809c');
+ renderPalette();
+ const tealStrip=document.querySelector('#pals .fstrip[data-column="teal"]');
+ const blueStrip=document.querySelector('#pals .fstrip[data-column="blue"]');
+ const tealChip=tealStrip&&tealStrip.querySelector('.pchip');
+ const blueChip=blueStrip&&blueStrip.querySelector('.pchip');
+ A(tealChip&&tealChip.classList.contains('unused'),'unreferenced-tile-flagged');
+ A(blueChip&&!blueChip.classList.contains('unused'),'referenced-tile-not-flagged');
+ A(tealStrip&&tealStrip.classList.contains('unused-col'),'all-unused-column-flagged');
+ A(blueStrip&&!blueStrip.classList.contains('unused-col'),'used-column-not-flagged');
+ PALETTE=saveP;for(const k in MAP)delete MAP[k];Object.assign(MAP,saveM);for(const k in SYNTAX)delete SYNTAX[k];Object.assign(SYNTAX,saveSyn);for(const f in UIMAP)delete UIMAP[f];Object.assign(UIMAP,saveU);syncSyntaxFromCache();renderPalette();
+ document.title='UNUSEDTEST '+(ok?'PASS':'FAIL');
+ const d=document.createElement('div');d.id='unusedtest';d.textContent='UNUSEDTEST '+(ok?'PASS':'FAIL')+(notes.length?' fails='+notes.join(','):'');document.body.appendChild(d);}
</script>