From 3e0676df725d91bf6886041a99559058db48853a Mon Sep 17 00:00:00 2001 From: "praisonai-triage-agent[bot]" <272766704+praisonai-triage-agent[bot]@users.noreply.github.com> Date: Thu, 27 Aug 2026 11:24:04 +0000 Subject: [PATCH] fix: show setup UI when a restored Train view hides it on first run (fixes #4441) The desktop restores the last-used view from localStorage synchronously at startup. When that is Train, body.training is set and the stylesheet hides async; when it resolves to setup-needed/failed, firstRun()/failWithSetup() render the setup wizard or failure banner into #thread -- which is now hidden behind the Train view. The title bar shows "setup needed" with nothing on screen to act on: the reported Windows dead end. Force the Chat view (showEngineGate) when the engine is not ready so the deliberate Train choice returns once the engine is up. Engine and Rust supervisor untouched. Co-authored-by: Mervin Praison --- .../frontend/tests/first-run.test.mjs | 23 ++++++++++++++++++- src/praisonai-desktop/ui/index.html | 7 ++++++ 2 files changed, 29 insertions(+), 1 deletion(-) 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';