diff --git a/src/praisonai-desktop/frontend/tests/first-run.test.mjs b/src/praisonai-desktop/frontend/tests/first-run.test.mjs index a255d9f894..3d8a3c8610 100644 --- a/src/praisonai-desktop/frontend/tests/first-run.test.mjs +++ b/src/praisonai-desktop/frontend/tests/first-run.test.mjs @@ -40,7 +40,9 @@ async function boot({ provision = 'ok', steps = [], savedView = null, const dom = new JSDOM(HTML, { runScripts: 'dangerously', resources: 'usable', url: ORIGIN + '/', beforeParse(w) { - if (savedView) w.localStorage.setItem('view', savedView); + // Model a machine where the user last used the Train tab: the app + // restores that view synchronously, before the async engine check runs. + if (savedView) try { w.localStorage.setItem('view', savedView); } catch {} w.__TAURI__ = { core: { invoke: async (cmd) => { @@ -186,3 +188,22 @@ test('a failed setup says why and offers another go', async () => { 'the reason was swallowed'); assert.ok(b.doc.querySelector('.setup li.failed'), 'the failing step is not marked'); }); + +// A restored Train view hides #thread, where the setup wizard renders. Left +// alone, that buries setup: the title bar says "setup needed" and nothing is on +// screen to act on -- the reported Windows dead end. The engine gate must win +// over the restored view while the engine is not ready. +test('a restored Train view does not hide the setup screen', async () => { + const b = await boot({ savedView: 'train' }); + assert.ok(b.doc.querySelector('.setup'), 'no setup screen'); + assert.equal(b.doc.body.classList.contains('training'), false, + 'the Train view is still up, hiding the setup wizard'); +}); + +// The forced switch must not clobber the deliberate choice: once the engine is +// up, the user's Train tab should return, so we must not have persisted 'chat'. +test('the forced Chat switch does not overwrite the saved Train choice', async () => { + const b = await boot({ savedView: 'train' }); + assert.equal(b.window.localStorage.getItem('view'), 'train', + 'the saved view was overwritten by the engine gate'); +}); diff --git a/src/praisonai-desktop/ui/index.html b/src/praisonai-desktop/ui/index.html index 9e67f1c625..d21ed55a2a 100644 --- a/src/praisonai-desktop/ui/index.html +++ b/src/praisonai-desktop/ui/index.html @@ -1400,6 +1400,8 @@

Fine-tune a model

// is the difference between a product and an error message: "No usable // Python" is true and useless to someone who has never installed one. status.textContent='setup needed'; status.className='s-boot'; + // engineBlocked forced Chat above, so #thread is visible: a restored Train + // view can no longer bury the wizard firstRun() renders there. firstRun(st); }else{ status.textContent='engine failed'; status.className='s-fail'; @@ -2208,6 +2210,11 @@

Fine-tune a model

let trainStream=null, trainCursor=-1, trainRunId=null, lossSeries=[], logLines=[]; const MAX_LOG_LINES=600; // the pane is a tail, not an archive; the log file has it all +// A restored Train view hides #thread (body.training #thread{display:none}), +// where the setup wizard and failure banner render. While the engine is not +// ready, a requested Train view is forced to Chat so that UI stays visible; +// the deliberate choice is still persisted, and the post-boot restore below +// returns Train once the engine is up. function showView(name,{persist=true}={}){ const requested=name; if(name==='train' && engineBlocked) name='chat';