From 0de051ed6a11e15ed53fa4029f57ddc816e6b820 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sat, 13 Jun 2026 19:23:52 -0500 Subject: Clarify theme studio reset and erase actions --- scripts/theme-studio/README.md | 17 +++++--- scripts/theme-studio/app.js | 20 +++++++--- scripts/theme-studio/browser-gates.js | 24 ++++++++---- scripts/theme-studio/theme-studio.html | 52 +++++++++++++++++-------- scripts/theme-studio/theme-studio.template.html | 8 ++-- 5 files changed, 83 insertions(+), 38 deletions(-) (limited to 'scripts') diff --git a/scripts/theme-studio/README.md b/scripts/theme-studio/README.md index e319a324..b680e6a8 100644 --- a/scripts/theme-studio/README.md +++ b/scripts/theme-studio/README.md @@ -105,11 +105,14 @@ Three tiers of faces, plus the palette: function, type, comment, and the rest), each with normal/bold/italic and a contrast rating. Click a category to flash its tokens in the code; click a token to flash its row. `lock all` flips to `unlock all` when every row in the - tier is locked; `clear unlocked` leaves locked rows untouched. + tier is locked. `reset unlocked` restores unlocked rows to the captured syntax + defaults; `erase unlocked` blanks unlocked rows. Both leave locked rows + untouched. - **UI faces** — cursor, region, mode-line, fringe, line numbers, isearch, paren match, link, error/warning/success, and the rest, foreground and background - per face, shown in a live mock Emacs buffer. The same lock-all and clear - unlocked controls apply to the UI face tier. + per face, shown in a live mock Emacs buffer. `reset unlocked` restores captured + UI face defaults; `erase unlocked` blanks unlocked rows to no explicit fg/bg. + Both leave locked rows untouched. - **Package faces** — per-package face tables with a live preview (below). ## Color columns @@ -190,9 +193,11 @@ Pick an application from the dropdown to edit its faces. Each row has a foreground and background dropdown, bold/italic toggles, an `inherit` dropdown (base faces like `fixed-pitch`/`link` plus the app's own faces), a relative height stepper, a contrast readout, and a per-face reset. There's a per-app -reset and a text filter for the large sets. Package `lock all` / `unlock all` -applies to the whole currently selected package, not only the rows visible under -the text filter. +reset and a text filter for the large sets. Package `reset unlocked` restores unlocked +rows to the captured package defaults; `erase unlocked` blanks unlocked rows to +no fg/bg/style/inherit override. Both leave locked rows untouched. Package +`lock all` / `unlock all` applies to the whole currently selected package, not +only the rows visible under the text filter. Twenty applications have bespoke previews that exercise nearly all of their faces: org-mode (a document plus an agenda view), magit (a status buffer plus diff --git a/scripts/theme-studio/app.js b/scripts/theme-studio/app.js index 88ddbaa9..97d1f076 100644 --- a/scripts/theme-studio/app.js +++ b/scripts/theme-studio/app.js @@ -1,7 +1,8 @@ const SAMPLES=SAMPLES_J, CATS=CATS_J, UI_FACES=UIFACES_J, APPS=APPS_J; let MAP=MAP_J, PALETTE=PALETTE_J, BOLD=BOLD_J, ITALIC=ITALIC_J, UIMAP=UIMAP_J; -let LOCKED=new Set(LOCKS_J); // syntax categories whose element↔color is decided (dropdown disabled, skipped by clear-unlocked) +let LOCKED=new Set(LOCKS_J); // rows whose choice is decided (controls disabled, skipped by erase/reset batch actions) const DELTAE_MIN=0.02; // OKLab ΔE below this = colors too close to tell apart (perceptual-metrics spec) +const DEFAULT_MAP=Object.assign({},MAP), DEFAULT_BOLD=Object.assign({},BOLD), DEFAULT_ITALIC=Object.assign({},ITALIC), DEFAULT_UIMAP=JSON.parse(JSON.stringify(UIMAP)); // --- tier-3 package faces: pure state helpers (Phase 1) --- // Thin wrappers over the pure logic in app-core.js (inlined further down), // passing the live module state. packagesForExport / mergePackagesInto live in @@ -133,16 +134,25 @@ function toggleAllLocks(tier){ } function clearUnlocked(){ clearUnlockedRows(CATS,c=>(c[0]==='bg'||c[0]==='p')?null:c[0],c=>{MAP[c[0]]='';}); - buildTable();renderCode();notify('cleared unlocked elements to default',false); + buildTable();renderCode();notify('erased unlocked elements to default',false); +} +function resetUnlocked(){ + clearUnlockedRows(CATS,c=>c[0],c=>{const k=c[0];MAP[k]=DEFAULT_MAP[k]||'';BOLD[k]=!!DEFAULT_BOLD[k];ITALIC[k]=!!DEFAULT_ITALIC[k];}); + buildTable();buildUITable();buildPkgTable();buildPkgPreview();renderCode();applyGround();repaintCovered(); + notify('reset unlocked syntax elements to captured defaults',false); } function clearUnlockedUI(){ clearUnlockedRows(UI_FACES,f=>'ui:'+f[0],f=>{UIMAP[f[0]]=uiFaceBlank();}); - buildUITable();buildMockFrame();notify('cleared unlocked UI faces to default',false); + buildUITable();buildMockFrame();notify('erased unlocked UI faces to default',false); +} +function resetUnlockedUI(){ + clearUnlockedRows(UI_FACES,f=>'ui:'+f[0],f=>{UIMAP[f[0]]=JSON.parse(JSON.stringify(DEFAULT_UIMAP[f[0]]||uiFaceBlank()));}); + buildUITable();buildMockFrame();notify('reset unlocked UI faces to captured defaults',false); } function clearUnlockedPkg(){ const app=curApp(); clearUnlockedRows(APPS[app].faces,f=>'pkg:'+app+':'+f[0],f=>{PKGMAP[app][f[0]]=normalizePkgFace({source:'cleared'},'cleared');}); - pkgChanged();notify('cleared unlocked '+app+' faces to default',false); + pkgChanged();notify('erased unlocked '+app+' faces to default',false); } function buildTable(){ const tb=document.getElementById('legbody');tb.innerHTML=''; @@ -767,7 +777,7 @@ function renderTelegaPreview(){const a='telega',L=[]; return `
${L.join('\n')}
`;} function genericPreview(app){let h='
';for(const [face,label,def] of APPS[app].faces){const f=PKGMAP[app][face],efg=effFg(pkgEffFg(app,face)),ebg=pkgEffBg(app,face);h+=`
${esc(label)}
`;}return h+'
';} function buildPkgPreview(){const app=curApp(),p=document.getElementById('pkgpreview');if(!p)return;const pv=APPS[app].preview;const bespoke=['org','magit','elfeed','ghostel','dashboard','mu4e','lsp','gitgutter','flycheck','dired','dirvish','calibredb','erc','orgdrill','orgnoter','signel','pearl','slack','telega','shr'].includes(pv);p.innerHTML=pv==='org'?renderOrgPreview():pv==='magit'?renderMagitPreview():pv==='elfeed'?renderElfeedPreview():pv==='ghostel'?renderGhostelPreview():pv==='dashboard'?renderDashboardPreview():pv==='mu4e'?renderMu4ePreview():pv==='lsp'?renderLspPreview():pv==='gitgutter'?renderGitGutterPreview():pv==='flycheck'?renderFlycheckPreview():pv==='dired'?renderDiredPreview():pv==='dirvish'?renderDirvishPreview():pv==='calibredb'?renderCalibredbPreview():pv==='erc'?renderErcPreview():pv==='orgdrill'?renderOrgdrillPreview():pv==='orgnoter'?renderOrgnoterPreview():pv==='signel'?renderSignelPreview():pv==='pearl'?renderPearlPreview():pv==='slack'?renderSlackPreview():pv==='telega'?renderTelegaPreview():pv==='shr'?renderShrPreview():genericPreview(app);p.style.background=MAP['bg'];p.onclick=(e)=>{const u=e.target.closest('[data-face]');if(u)flashPkg(u.dataset.face);};const lbl=document.getElementById('pkgprevlabel');if(lbl)lbl.textContent=bespoke?(APPS[app].label+' preview'):'preview (generic — face names in their own colors)';} -function resetApp(){const app=curApp();PKGMAP[app]={};for(const [face,label,d] of APPS[app].faces)PKGMAP[app][face]=seedFace(d);pkgChanged();} +function resetApp(){const app=curApp();for(const [face,label,d] of APPS[app].faces)if(!LOCKED.has('pkg:'+app+':'+face))PKGMAP[app][face]=seedFace(d);pkgChanged();notify('reset unlocked '+app+' faces to package defaults',false);} function syncPkgHeight(){const t=document.getElementById('pkgtable'),m=document.getElementById('pkgpreview');if(!t||!m)return;const lb=m.previousElementSibling,lbh=lb?lb.getBoundingClientRect().height+10:30;m.style.height=Math.max(t.getBoundingClientRect().height-lbh,220)+'px';} // --- worst-case readout for the covered overlay faces (spec Phase 4) --------- // Default WCAG target for the worst-case verdict (AA). AAA is selectable. diff --git a/scripts/theme-studio/browser-gates.js b/scripts/theme-studio/browser-gates.js index cf45c317..6aa5c0b5 100644 --- a/scripts/theme-studio/browser-gates.js +++ b/scripts/theme-studio/browser-gates.js @@ -21,10 +21,9 @@ function pkgSelftest(){ } if(location.hash==='#selftest')pkgSelftest(); // Lock-mechanism gate (open with #locktest): two behaviors the refactor must -// preserve, across all three tiers. (1) Locking a row disables its control via -// the shared mkLockCell — syntax uses a swatch div (data-locked), UI a native -// select (.disabled). (2) clear-unlocked wipes unlocked rows to default but -// leaves locked rows (syntax bare-kind, ui:, pkg: keys) untouched. +// preserve, across all three tiers. (1) Locking a row disables its controls via +// the shared mkLockCell. (2) reset/erase batch actions update unlocked rows but +// leave locked rows (syntax bare-kind, ui:, pkg: keys) untouched. if(location.hash==='#locktest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; LOCKED.clear();buildTable(); {const k=CATS.map(c=>c[0]).filter(k=>k!=='bg'&&k!=='p')[0]; @@ -50,13 +49,24 @@ if(location.hash==='#locktest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c A(PKGMAP[app][face].fg==='#888888','pkg left arrow steps to darker color');} {const ks=CATS.map(c=>c[0]).filter(k=>k!=='bg'&&k!=='p'),k1=ks[0],k2=ks[1]; MAP[k1]='#111111';MAP[k2]='#222222';LOCKED.clear();LOCKED.add(k1);clearUnlocked(); - A(MAP[k1]==='#111111','syntax-clear-keeps-locked');A(MAP[k2]==='','syntax-clear-wipes-unlocked');} + A(MAP[k1]==='#111111','syntax-erase-keeps-locked');A(MAP[k2]==='','syntax-erase-wipes-unlocked');} + {const ks=CATS.map(c=>c[0]).filter(k=>k!=='bg'&&k!=='p'),k1=ks[0],k2=ks[1]; + MAP[k1]='#111111';MAP[k2]='#222222';LOCKED.clear();LOCKED.add(k1);resetUnlocked(); + A(MAP[k1]==='#111111','syntax-reset-keeps-locked');A(MAP[k2]===DEFAULT_MAP[k2],'syntax-reset-restores-unlocked-default');} {const f1=UI_FACES[0][0],f2=UI_FACES[1][0]; UIMAP[f1].fg='#111111';UIMAP[f2].fg='#222222';LOCKED.clear();LOCKED.add('ui:'+f1);clearUnlockedUI(); - A(UIMAP[f1].fg==='#111111','ui-clear-keeps-locked');A(UIMAP[f2].fg===null,'ui-clear-wipes-unlocked');} + A(UIMAP[f1].fg==='#111111','ui-erase-keeps-locked');A(UIMAP[f2].fg===null,'ui-erase-wipes-unlocked');} + {const f1=UI_FACES[0][0],f2=UI_FACES[1][0]; + UIMAP[f1].fg='#111111';UIMAP[f2].fg='#222222';LOCKED.clear();LOCKED.add('ui:'+f1);resetUnlockedUI(); + A(UIMAP[f1].fg==='#111111','ui-reset-keeps-locked');A(JSON.stringify(UIMAP[f2])===JSON.stringify(DEFAULT_UIMAP[f2]),'ui-reset-restores-unlocked-default');} {const app=curApp(),pf=APPS[app].faces.map(r=>r[0]),p1=pf[0],p2=pf[1]; PKGMAP[app][p1].fg='#111111';PKGMAP[app][p2].fg='#222222';LOCKED.clear();LOCKED.add('pkg:'+app+':'+p1);clearUnlockedPkg(); - A(PKGMAP[app][p1].fg==='#111111','pkg-clear-keeps-locked');A(PKGMAP[app][p2].fg===null,'pkg-clear-wipes-unlocked');} + A(PKGMAP[app][p1].fg==='#111111','pkg-erase-keeps-locked');A(PKGMAP[app][p2].fg===null,'pkg-erase-wipes-unlocked');} + {const app=curApp(),rows=APPS[app].faces,p1=rows[0][0],p2=rows[1][0],d2=rows[1][2]; + PKGMAP[app][p1].fg='#111111';PKGMAP[app][p2]=normalizePkgFace({fg:'#222222',bg:'#333333',bold:true,source:'user'},'user'); + LOCKED.clear();LOCKED.add('pkg:'+app+':'+p1);resetApp(); + A(PKGMAP[app][p1].fg==='#111111','pkg-reset-keeps-locked'); + A(JSON.stringify(PKGMAP[app][p2])===JSON.stringify(seedFace(d2)),'pkg-reset-restores-unlocked-default');} {LOCKED.clear();buildTable();const b=document.getElementById('syntaxlocktoggle');A(b&&b.textContent==='lock all','syntax toggle starts as lock all');b.click(); A(syntaxLockKeys().every(k=>LOCKED.has(k))&&b.textContent==='unlock all','syntax lock-all locks every syntax row and flips label');b.click(); A(syntaxLockKeys().every(k=>!LOCKED.has(k))&&b.textContent==='lock all','syntax unlock-all clears every syntax lock and flips label');} diff --git a/scripts/theme-studio/theme-studio.html b/scripts/theme-studio/theme-studio.html index ef439993..7cb5b9c0 100644 --- a/scripts/theme-studio/theme-studio.html +++ b/scripts/theme-studio/theme-studio.html @@ -153,7 +153,7 @@

code/color assignments

-
+
elements △color △stylecontrastexample
@@ -164,7 +164,7 @@

ui faces

-
+
face △foreground △background △stylecontrast △previewbox
@@ -176,9 +176,9 @@
- + - +
@@ -192,8 +192,9 @@