fix(desktop): keep Close and + on a lone workspace even when tabs are hidden
A lone workspace pane is stranded without its strip: Close still empties it to a draft and + still opens a tab, so a chromeless workspace is a dead zone for both handles. Treat any lone main pane as stranded, workspace included, which also makes the auto-only lone-main clause unnecessary. Approach from #107444 by @abundantbeing. Co-authored-by: abundantbeing <beingsabundant@gmail.com>
This commit is contained in:
@@ -44,11 +44,16 @@ describe('no dead zone', () => {
|
||||
expect(resolveTabStripVisible({ mode: 'never', shown: [toolPanel()] })).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps the strip for a lone workspace even when the zone says never', () => {
|
||||
// Workspace cannot leave the tree, but Close still empties it to a draft
|
||||
// and + still opens a tab. Chromeless is a dead zone for those handles.
|
||||
expect(resolveTabStripVisible({ mode: 'never', shown: [workspace()] })).toBe(true)
|
||||
})
|
||||
|
||||
it('still hides a zone that cannot strand anything', () => {
|
||||
// The workspace is uncloseable, a stack is reachable by tab cycling, and
|
||||
// hide-only chrome (sessions / Bots) keeps its panes + ⌘⌥T — the invariant
|
||||
// protects handles, it does not veto hiding as such.
|
||||
expect(resolveTabStripVisible({ mode: 'never', shown: [workspace()] })).toBe(false)
|
||||
// A stack is reachable by tab cycling, and hide-only chrome (sessions /
|
||||
// Bots) keeps its panes + ⌘⌥T — the invariant protects handles, it does
|
||||
// not veto hiding as such.
|
||||
expect(resolveTabStripVisible({ mode: 'never', shown: [toolPanel(), toolPanel()] })).toBe(false)
|
||||
expect(resolveTabStripVisible({ mode: 'never', shown: [sideChrome()] })).toBe(false)
|
||||
expect(resolveTabStripVisible({ mode: 'never', shown: [sideChrome(), sideChrome()] })).toBe(false)
|
||||
@@ -86,15 +91,13 @@ describe('a full-page view', () => {
|
||||
})
|
||||
|
||||
// Every launch and client update lands on a lone workspace. Chromeless, that
|
||||
// layout had no session switcher and no "+" until the app-wide default was
|
||||
// flipped to always — the "tab header disappeared" reports (#89350).
|
||||
// layout had no session switcher, no Close and no "+" (#89350).
|
||||
describe('a lone workspace', () => {
|
||||
it('keeps its strip on auto, and only on auto', () => {
|
||||
it('keeps its strip whatever the zone says', () => {
|
||||
expect(resolveTabStripVisible({ shown: [workspace()] })).toBe(true)
|
||||
expect(resolveTabStripVisible({ mode: 'never', shown: [workspace()] })).toBe(true)
|
||||
// Standing side chrome never joins the tabbed workflow.
|
||||
expect(resolveTabStripVisible({ shown: [sideChrome()] })).toBe(false)
|
||||
// Hide tabs (menu row / ⌘⌥T) still wins — the workspace strands nothing.
|
||||
expect(resolveTabStripVisible({ mode: 'never', shown: [workspace()] })).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -127,7 +130,7 @@ describe('tabStripVisibleForZone', () => {
|
||||
|
||||
it('reads placement, uncloseable and collapse off the contributions', () => {
|
||||
expect(visible(['workspace'])).toBe(true)
|
||||
expect(visible(['workspace'], 'never')).toBe(false)
|
||||
expect(visible(['workspace'], 'never')).toBe(true)
|
||||
expect(visible(['tile:a'], 'never')).toBe(true)
|
||||
expect(visible(['terminal'], 'never')).toBe(true)
|
||||
})
|
||||
@@ -137,15 +140,15 @@ describe('tabStripVisibleForZone', () => {
|
||||
expect(visible(['workspace'])).toBe(true)
|
||||
|
||||
setTabStripDefault('never')
|
||||
expect(visible(['workspace'])).toBe(false)
|
||||
expect(visible(['workspace'])).toBe(true)
|
||||
expect(visible(['workspace', 'terminal'])).toBe(false)
|
||||
})
|
||||
|
||||
it("lets a zone's own choice beat the app default", () => {
|
||||
setTabStripDefault('never')
|
||||
expect(visible(['workspace'], 'always')).toBe(true)
|
||||
expect(visible(['workspace', 'terminal'], 'always')).toBe(true)
|
||||
|
||||
setTabStripDefault('always')
|
||||
expect(visible(['workspace'], 'never')).toBe(false)
|
||||
expect(visible(['workspace', 'terminal'], 'never')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -38,12 +38,13 @@ export interface StripZone {
|
||||
|
||||
/**
|
||||
* A pane is STRANDED without a strip when the strip is the only thing carrying
|
||||
* its handle: a lone closeable tile needs its ✕, a lone tool panel needs a chip
|
||||
* to grab. The uncloseable workspace is not strandable — it cannot be closed
|
||||
* or lost, so an explicit `never` may hide its strip. Hide-only chrome (sessions
|
||||
* / Bots) is the same: the panes stay, Show/Hide is a separate verb, and a
|
||||
* hidden strip comes back via ⌘⌥T. Treating it as stranded at any count made
|
||||
* Hide tabs a silent no-op on the sessions sidebar.
|
||||
* its handle: a lone main tile needs its ✕ and +, a lone tool panel needs a
|
||||
* chip to grab. The workspace cannot leave the tree, but Close still empties
|
||||
* it to a draft and + still opens a tab — chromeless is a dead zone for those
|
||||
* handles. Hide-only chrome (sessions / Bots) is different: the panes stay,
|
||||
* Show/Hide is a separate verb, and a hidden strip comes back via ⌘⌥T.
|
||||
* Treating side chrome as stranded at any count made Hide tabs a silent no-op
|
||||
* on the sessions sidebar.
|
||||
*
|
||||
* This outranks an explicit `never` on purpose. "Hide the strip" is a request
|
||||
* about chrome, never a request to make a surface unreachable, and a zone that
|
||||
@@ -64,7 +65,7 @@ function stranded(shown: readonly StripPane[]): boolean {
|
||||
|
||||
const [only] = shown
|
||||
|
||||
return only.collapsePane || (!only.uncloseable && only.placement === 'main')
|
||||
return only.collapsePane || only.placement === 'main'
|
||||
}
|
||||
|
||||
export function resolveTabStripVisible(zone: StripZone): boolean {
|
||||
@@ -87,17 +88,10 @@ export function resolveTabStripVisible(zone: StripZone): boolean {
|
||||
return zone.mode === 'always'
|
||||
}
|
||||
|
||||
// Auto: two or more panes need a strip to switch between them.
|
||||
if (zone.shown.length > 1) {
|
||||
return true
|
||||
}
|
||||
|
||||
// A lone MAIN tile keeps its strip too. The strip is the session switcher:
|
||||
// its tab names the chat and its "+" opens the next one, so a chromeless
|
||||
// lone workspace — the layout every launch and update lands on — read as
|
||||
// "my tabs disappeared". Standing side chrome still goes without; hiding
|
||||
// the workspace's strip remains an explicit choice (`never`, ⌘⌥T).
|
||||
return zone.shown.some(pane => pane.placement === 'main')
|
||||
// Auto: two or more panes need a strip to switch between them. A lone main
|
||||
// tile never reaches here (it is stranded above), so what is left alone is
|
||||
// standing side chrome, which goes without.
|
||||
return zone.shown.length > 1
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user