From b900c9ac5d6ffc7075049cc5030a28b2ef224d8a Mon Sep 17 00:00:00 2001 From: Max <112043822+Maxaubert@users.noreply.github.com> Date: Sun, 4 Oct 2026 13:13:14 +0200 Subject: [PATCH] fix(settings): Settings keeps its page while its tab stays open (#123) Settings unmounts whenever another tab is in front, so coming back always landed on General. App now holds the page; closing the Settings tab forgets it. The settingsPage e2e holds both halves. Version 0.28.1. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01FHHaWKR4M5QtW7Wecyuk4t --- package-lock.json | 4 ++-- package.json | 2 +- src/renderer/src/App.tsx | 13 +++++++++++- src/renderer/src/components/Settings.tsx | 19 ++++++++++++------ tools/e2e/run.mjs | 25 ++++++++++++++++++++++++ 5 files changed, 53 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 26967d4..582e39e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "prism-terminal", - "version": "0.28.0", + "version": "0.28.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "prism-terminal", - "version": "0.28.0", + "version": "0.28.1", "license": "MIT", "dependencies": { "@xterm/addon-fit": "^0.11.0", diff --git a/package.json b/package.json index 7d91215..3fff1a1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "prism-terminal", "productName": "Prism Terminal", - "version": "0.28.0", + "version": "0.28.1", "description": "A tabbed Windows terminal for AI CLIs.", "main": "./out/main/index.js", "author": "Max", diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 7451f94..b34f521 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -14,6 +14,7 @@ import { TabStrip } from './components/TabStrip' import TitleBar, { TitleButtons } from './components/TitleBar' import { useTitleBarMode } from './lib/titleBarPrefs' import EmptyState from './components/EmptyState' +import type { SettingsPage } from './components/Settings' import { Dialog } from './components/Dialog' import { addTab, @@ -159,6 +160,16 @@ export default function App(): JSX.Element { const active = tabs.find((t) => t.id === activeId) ?? null // A placeholder has no shell yet (#106): nothing that needs one may see it. const activeShell = active && active.kind !== 'settings' && !active.pending ? active : null + // The Settings tab keeps its page while it stays open (#123; owner, + // 2026-10-04: "if i go from settings to another tab then to settings again + // it should be the same tab it was on ... unless i close the settings tab + // and reopen"). The page unmounts behind another tab, so App holds it, and + // closing the tab is what forgets it. + const [settingsPage, setSettingsPage] = useState('general') + const settingsOpen = tabs.some((t) => t.kind === 'settings') + // Adjusted while rendering, React's own pattern for state that follows other + // state: no effect, so no frame painted with the stale page. + if (!settingsOpen && settingsPage !== 'general') setSettingsPage('general') // Tabs still coming back to an agent wear a ring in the strip (#106): the // placeholders, then the sessions resuming until their agent has drawn. const resumingNow = useSyncExternalStore(onResumingChange, resumingIds) @@ -670,7 +681,7 @@ export default function App(): JSX.Element { {active?.kind === 'settings' && (
- +
)} diff --git a/src/renderer/src/components/Settings.tsx b/src/renderer/src/components/Settings.tsx index 3f1822c..50d063b 100644 --- a/src/renderer/src/components/Settings.tsx +++ b/src/renderer/src/components/Settings.tsx @@ -370,7 +370,8 @@ function AppearanceTab(): JSX.Element { /* ---------- page shell ---------- */ -type TabId = 'general' | 'appearance' | 'dictation' +export type SettingsPage = 'general' | 'appearance' | 'dictation' +type TabId = SettingsPage const Ico = ({ d }: { d: string }): JSX.Element => ( ('general') +export default function Settings({ + page: tab, + onPage: setTab +}: { + page: SettingsPage + onPage: (page: SettingsPage) => void +}): JSX.Element { const active = TABS.find((t) => t.id === tab) ?? TABS[0] return (
(await tabLabels(page)).length === 1), 'one tab open') + const current = () => + page.evaluate(() => document.querySelector('[data-settings-tab][aria-current="page"]')?.getAttribute('data-settings-tab') ?? null) + const settingsTab = page.locator('[data-tab]', { hasText: 'Settings' }) + await page.locator('[data-title-settings]').click() + ok(await until(async () => (await current()) === 'general'), 'Settings opens on General') + await page.locator('[data-settings-tab="dictation"]').click() + ok(await until(async () => (await current()) === 'dictation'), 'Dictation picked') + await page.locator('[data-tab]').first().click() + ok(await until(async () => (await current()) === null), 'the shell is in front, Settings unmounted') + await settingsTab.click() + ok(await until(async () => (await current()) === 'dictation'), `back on Settings it is still Dictation (${await current()})`) + await settingsTab.locator('[data-tab-close]').click({ force: true }) + ok(await until(async () => (await settingsTab.count()) === 0), 'the Settings tab closed') + await page.locator('[data-title-settings]').click() + ok(await until(async () => (await current()) === 'general'), `opened again it starts on General (${await current()})`) + await closeApp(app) + }, + // LAUNCH WITH RESTORED AGENT TABS (#106; spec // docs/superpowers/specs/2026-09-30-launch-skeleton-design.md; owner, // 2026-09-30: "you see the no tab screen (false, there are three tabs