From e622f65fd61e260f36a62250b8894f555b8680dc Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Mon, 8 Jun 2026 02:30:43 -0500 Subject: feat(theme-selector): magit and elfeed bespoke previews (tier-3 phase 5) I added renderMagitPreview and renderElfeedPreview. The magit one is a status buffer: head and branch lines, an untracked section, a diff hunk with context, removed, and added lines, and recent commits with hashes, authors, and a keyword and tag. The elfeed one is a search list: the filter line, dated entries with feed, unread and read titles, and tags, plus log lines colored by level. The preview pane dispatches to each app's bespoke renderer, the generic fallback covers everything else, and the pane label now names the app and says whether the preview is bespoke or generic. --- scripts/theme-selector/generate.py | 36 ++++++++++++++++++++++++++++-- scripts/theme-selector/theme-selector.html | 36 ++++++++++++++++++++++++++++-- todo.org | 4 ++-- 3 files changed, 70 insertions(+), 6 deletions(-) diff --git a/scripts/theme-selector/generate.py b/scripts/theme-selector/generate.py index 7fc00145..80e9ad54 100644 --- a/scripts/theme-selector/generate.py +++ b/scripts/theme-selector/generate.py @@ -304,7 +304,7 @@ HTML = """theme-selector
facefgbgweightinheritsizecontrast
-
+
@@ -571,8 +571,39 @@ function renderOrgPreview(){const a='org-mode',L=[]; L.push(' '+os(a,'org-table','| blue | #67809c |')); return `
${L.join('\\n')}
`; } +function renderMagitPreview(){const a='magit',L=[]; + L.push(os(a,'magit-head','Head:')+' '+os(a,'magit-branch-local','main')+' '+os(a,'magit-diff-revision-summary','Ship the tool')); + L.push(os(a,'magit-head','Merge:')+' '+os(a,'magit-branch-remote','origin/main')); + L.push(os(a,'magit-head','Push:')+' '+os(a,'magit-branch-remote','origin/main')); + L.push(''); + L.push(os(a,'magit-section-heading','Untracked files (1)')); + L.push(' '+os(a,'magit-filename','notes.txt')); + L.push(''); + L.push(os(a,'magit-section-heading','Unstaged changes (1)')); + L.push(os(a,'magit-diff-file-heading','modified generate.py')); + L.push(os(a,'magit-diff-hunk-heading','@@ -1,4 +1,5 @@ def main')); + L.push(os(a,'magit-diff-context',' unchanged context')); + L.push(os(a,'magit-diff-removed','- old line')); + L.push(os(a,'magit-diff-added','+ new line')); + L.push(''); + L.push(os(a,'magit-section-heading','Recent commits')); + L.push(os(a,'magit-hash','b5b1869f')+' '+os(a,'magit-log-author','Craig')+' enlarge the picker'); + L.push(os(a,'magit-hash','4fa5e995')+' '+os(a,'magit-log-author','Craig')+' '+os(a,'magit-keyword','feat')+' color picker'); + L.push(os(a,'magit-hash','de07e01a')+' '+os(a,'magit-log-author','Craig')+' '+os(a,'magit-tag','v0.3')+' relative height'); + return `
${L.join('\\n')}
`;} +function renderElfeedPreview(){const a='elfeed',L=[]; + L.push(os(a,'elfeed-search-filter-face','@6-months-ago +unread')+' '+os(a,'elfeed-search-unread-count-face','3/120')); + L.push(''); + L.push(os(a,'elfeed-search-date-face','2026-06-08')+' '+os(a,'elfeed-search-feed-face','Planet Emacs')+' '+os(a,'elfeed-search-unread-title-face','New release of Magit')+' '+os(a,'elfeed-search-tag-face',':emacs:')); + L.push(os(a,'elfeed-search-date-face','2026-06-07')+' '+os(a,'elfeed-search-feed-face','LWN')+' '+os(a,'elfeed-search-unread-title-face','Kernel 6.18 lands')+' '+os(a,'elfeed-search-tag-face',':linux:')); + L.push(os(a,'elfeed-search-date-face','2026-06-05')+' '+os(a,'elfeed-search-feed-face','Hacker News')+' '+os(a,'elfeed-search-title-face','Show HN: a theme editor')+' '+os(a,'elfeed-search-tag-face',':show:')); + L.push(''); + L.push(os(a,'elfeed-log-date-face','02:24:01')+' '+os(a,'elfeed-log-info-level-face','INFO ')+' updated 12 feeds'); + L.push(os(a,'elfeed-log-date-face','02:24:02')+' '+os(a,'elfeed-log-warn-level-face','WARN ')+' slow feed: example.com'); + L.push(os(a,'elfeed-log-date-face','02:24:03')+' '+os(a,'elfeed-log-error-level-face','ERROR')+' failed: bad.example'); + return `
${L.join('\\n')}
`;} function genericPreview(app){let h='
';for(const [face,label,def] of APPS[app].faces){const f=PKGMAP[app][face],efg=pkgEffFg(app,face)||MAP['p'],ebg=pkgEffBg(app,face);h+=`
${esc(label)}
`;}return h+'
';} -function buildPkgPreview(){const app=curApp(),p=document.getElementById('pkgpreview');if(!p)return;p.innerHTML=(APPS[app].preview==='org')?renderOrgPreview():genericPreview(app);p.style.background=MAP['bg'];} +function buildPkgPreview(){const app=curApp(),p=document.getElementById('pkgpreview');if(!p)return;const pv=APPS[app].preview;const bespoke=pv==='org'||pv==='magit'||pv==='elfeed';p.innerHTML=pv==='org'?renderOrgPreview():pv==='magit'?renderMagitPreview():pv==='elfeed'?renderElfeedPreview():genericPreview(app);p.style.background=MAP['bg'];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 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';} function paintUI(face){const pv=document.getElementById('uiprev-'+face);if(!pv)return;pv.style.color=UIMAP[face].fg||MAP['p'];pv.style.background=UIMAP[face].bg||MAP['bg'];} @@ -611,6 +642,7 @@ function pkgSelftest(){ } if(location.hash==='#selftest')pkgSelftest(); if(location.hash.startsWith('#pick')){openPicker();const m=location.hash.slice(5);if(m){const b=document.querySelector('.pmode button[data-m="'+m+'"]');if(b)b.click();}} +if(location.hash.startsWith('#app')){const ap=location.hash.slice(4),s=document.getElementById('appsel');if(s&&ap){s.value=ap;pkgChanged();}} """ HTML=(HTML.replace("SAMPLES_J",json.dumps(SAMPLES)) .replace("PALETTE_J",json.dumps(PALETTE)).replace("CATS_J",json.dumps(CATS)) diff --git a/scripts/theme-selector/theme-selector.html b/scripts/theme-selector/theme-selector.html index 92190bc5..bc6308ff 100644 --- a/scripts/theme-selector/theme-selector.html +++ b/scripts/theme-selector/theme-selector.html @@ -128,7 +128,7 @@
facefgbgweightinheritsizecontrast
-
+
@@ -395,8 +395,39 @@ function renderOrgPreview(){const a='org-mode',L=[]; L.push(' '+os(a,'org-table','| blue | #67809c |')); return `
${L.join('\n')}
`; } +function renderMagitPreview(){const a='magit',L=[]; + L.push(os(a,'magit-head','Head:')+' '+os(a,'magit-branch-local','main')+' '+os(a,'magit-diff-revision-summary','Ship the tool')); + L.push(os(a,'magit-head','Merge:')+' '+os(a,'magit-branch-remote','origin/main')); + L.push(os(a,'magit-head','Push:')+' '+os(a,'magit-branch-remote','origin/main')); + L.push(''); + L.push(os(a,'magit-section-heading','Untracked files (1)')); + L.push(' '+os(a,'magit-filename','notes.txt')); + L.push(''); + L.push(os(a,'magit-section-heading','Unstaged changes (1)')); + L.push(os(a,'magit-diff-file-heading','modified generate.py')); + L.push(os(a,'magit-diff-hunk-heading','@@ -1,4 +1,5 @@ def main')); + L.push(os(a,'magit-diff-context',' unchanged context')); + L.push(os(a,'magit-diff-removed','- old line')); + L.push(os(a,'magit-diff-added','+ new line')); + L.push(''); + L.push(os(a,'magit-section-heading','Recent commits')); + L.push(os(a,'magit-hash','b5b1869f')+' '+os(a,'magit-log-author','Craig')+' enlarge the picker'); + L.push(os(a,'magit-hash','4fa5e995')+' '+os(a,'magit-log-author','Craig')+' '+os(a,'magit-keyword','feat')+' color picker'); + L.push(os(a,'magit-hash','de07e01a')+' '+os(a,'magit-log-author','Craig')+' '+os(a,'magit-tag','v0.3')+' relative height'); + return `
${L.join('\n')}
`;} +function renderElfeedPreview(){const a='elfeed',L=[]; + L.push(os(a,'elfeed-search-filter-face','@6-months-ago +unread')+' '+os(a,'elfeed-search-unread-count-face','3/120')); + L.push(''); + L.push(os(a,'elfeed-search-date-face','2026-06-08')+' '+os(a,'elfeed-search-feed-face','Planet Emacs')+' '+os(a,'elfeed-search-unread-title-face','New release of Magit')+' '+os(a,'elfeed-search-tag-face',':emacs:')); + L.push(os(a,'elfeed-search-date-face','2026-06-07')+' '+os(a,'elfeed-search-feed-face','LWN')+' '+os(a,'elfeed-search-unread-title-face','Kernel 6.18 lands')+' '+os(a,'elfeed-search-tag-face',':linux:')); + L.push(os(a,'elfeed-search-date-face','2026-06-05')+' '+os(a,'elfeed-search-feed-face','Hacker News')+' '+os(a,'elfeed-search-title-face','Show HN: a theme editor')+' '+os(a,'elfeed-search-tag-face',':show:')); + L.push(''); + L.push(os(a,'elfeed-log-date-face','02:24:01')+' '+os(a,'elfeed-log-info-level-face','INFO ')+' updated 12 feeds'); + L.push(os(a,'elfeed-log-date-face','02:24:02')+' '+os(a,'elfeed-log-warn-level-face','WARN ')+' slow feed: example.com'); + L.push(os(a,'elfeed-log-date-face','02:24:03')+' '+os(a,'elfeed-log-error-level-face','ERROR')+' failed: bad.example'); + return `
${L.join('\n')}
`;} function genericPreview(app){let h='
';for(const [face,label,def] of APPS[app].faces){const f=PKGMAP[app][face],efg=pkgEffFg(app,face)||MAP['p'],ebg=pkgEffBg(app,face);h+=`
${esc(label)}
`;}return h+'
';} -function buildPkgPreview(){const app=curApp(),p=document.getElementById('pkgpreview');if(!p)return;p.innerHTML=(APPS[app].preview==='org')?renderOrgPreview():genericPreview(app);p.style.background=MAP['bg'];} +function buildPkgPreview(){const app=curApp(),p=document.getElementById('pkgpreview');if(!p)return;const pv=APPS[app].preview;const bespoke=pv==='org'||pv==='magit'||pv==='elfeed';p.innerHTML=pv==='org'?renderOrgPreview():pv==='magit'?renderMagitPreview():pv==='elfeed'?renderElfeedPreview():genericPreview(app);p.style.background=MAP['bg'];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 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';} function paintUI(face){const pv=document.getElementById('uiprev-'+face);if(!pv)return;pv.style.color=UIMAP[face].fg||MAP['p'];pv.style.background=UIMAP[face].bg||MAP['bg'];} @@ -435,4 +466,5 @@ function pkgSelftest(){ } if(location.hash==='#selftest')pkgSelftest(); if(location.hash.startsWith('#pick')){openPicker();const m=location.hash.slice(5);if(m){const b=document.querySelector('.pmode button[data-m="'+m+'"]');if(b)b.click();}} +if(location.hash.startsWith('#app')){const ap=location.hash.slice(4),s=document.getElementById('appsel');if(s&&ap){s.value=ap;pkgChanged();}} \ No newline at end of file diff --git a/todo.org b/todo.org index d1566800..e75a456a 100644 --- a/todo.org +++ b/todo.org @@ -78,8 +78,8 @@ Added the "package faces" section: app selector (org/magit/elfeed), per-app face *** 2026-06-08 Mon @ 02:27:51 -0500 Phase 4 — org preview landed Added =renderOrgPreview()=: a mock org document painted live from the org package faces (title, headings with heights, TODO/DONE, tag, scheduled date, property drawer, inline code/verbatim, link, checkbox, quote, src block, header-row table). The preview pane dispatches on the app's preview key; org-mode gets this, others keep the generic list. Verified: node, headless screenshot, self-test PASS. -*** TODO [#B] Phase 5 — magit + elfeed previews :solo: -Bespoke mocks: a magit status buffer and an elfeed search list, live from their faces. Verify: screenshot. Fidelity to Manual testing. Spec phase 5. +*** 2026-06-08 Mon @ 02:30:42 -0500 Phase 5 — magit + elfeed previews landed +Bespoke =renderMagitPreview()= (status buffer: head/branches, untracked, a diff hunk with context/added/removed, recent commits with hashes/authors/keyword/tag) and =renderElfeedPreview()= (search list: filter, dated entries with feed/unread-title/read-title/tags, log lines by level). The preview label now names the app and notes generic vs bespoke. Verified: node, headless screenshots, self-test PASS. *** TODO [#B] Phase 6 — generated all-package inventory :solo: Build step queries Emacs for installed packages' faces grouped by package, writes a data file =generate.py= embeds; the dropdown then lists every package with an editable table + a labeled generic fallback preview. Verify: data file generates, dropdown lists packages, a fallback package edits and round-trips. Spec phase 6. -- cgit v1.2.3