From 0679c47c54dd935a0cc7d87c64081262b4367697 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sat, 20 Jun 2026 03:34:57 -0400 Subject: feat(theme-studio): export through the save-file picker to overwrite in place Re-exporting a theme used to land a "name (1).json" duplicate. The export built a blob and clicked a download link, which routes through the browser's downloads folder, and the browser uniquifies a re-save rather than replacing the file. I switched export to the File System Access API (showSaveFilePicker): it writes straight to the file you pick, so re-exporting the same WIP.json overwrites it. Where the API is absent the old blob download still runs, mirroring importTheme's picker-with-fallback shape. A new #savetest browser gate stubs the picker and checks the written content and the close. --- scripts/theme-studio/browser-gates.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'scripts/theme-studio/browser-gates.js') diff --git a/scripts/theme-studio/browser-gates.js b/scripts/theme-studio/browser-gates.js index 86ec37e9f..ba5886a9d 100644 --- a/scripts/theme-studio/browser-gates.js +++ b/scripts/theme-studio/browser-gates.js @@ -988,3 +988,19 @@ if(location.hash==='#hovertest'){let ok=true;const notes=[];const A=(c,n)=>{if(! A(pkgCell&&pkgCell.title===FACE_DOCS[docFace]+'\n\n'+docFace,'package cat cell shows docstring on top of the face name: '+(pkgCell&&JSON.stringify(pkgCell.title)));} document.title='HOVERTEST '+(ok?'PASS':'FAIL'); const d=document.createElement('div');d.id='hovertest';d.textContent='HOVERTEST '+(ok?'PASS':'FAIL')+(notes.length?' fails='+notes.join(','):'');document.body.appendChild(d);} +// Export via the File System Access API (open with #savetest): exportTheme writes +// the theme JSON straight to the picked file handle and closes it, so re-exporting +// overwrites in place instead of the browser uniquifying to "name (1).json". +if(location.hash==='#savetest'){(async()=>{let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; + let written='',closed=false,pickerArgs=null; + const orig=window.showSaveFilePicker; + window.showSaveFilePicker=async(opts)=>{pickerArgs=opts;return {name:'WIP.json',createWritable:async()=>({write:async d=>{written+=d;},close:async()=>{closed=true;}})};}; + try{ + await exportTheme(); + A(written===JSON.stringify(exportObj(),null,1),'export writes the theme JSON to the picked file'); + A(closed,'writable stream is closed so the file is committed'); + A(pickerArgs&&/\.json$/.test(pickerArgs.suggestedName||''),'picker suggests a .json name: '+(pickerArgs&&pickerArgs.suggestedName)); + }catch(e){A(false,'exportTheme threw: '+e.message);} + finally{window.showSaveFilePicker=orig;} + document.title='SAVETEST '+(ok?'PASS':'FAIL'); + const d=document.createElement('div');d.id='savetest';d.textContent='SAVETEST '+(ok?'PASS':'FAIL')+(notes.length?' fails='+notes.join(','):'');document.body.appendChild(d);})();} -- cgit v1.2.3