refactor(desktop): derive the tab hover close button from the close verb - #91777
Merged
Conversation
Contributor
૮ >ﻌ< ა ci reviewran on 6e20855 — refactor(desktop): derive the tab hover close button from th
|
PaneTab gated its hover close button on two independent inputs: the onClose verb, and a showCloseButton prop that TreeGroup fed from a showCloseButton flag on the pane contribution. The middle-click and Meta-click gestures read only onClose. A tab could therefore close on a pointer gesture and advertise no control for it. The flag had no user that hideOnly did not already cover. Both setters also set hideOnly: true, which removes every close gesture: - the sessions pane (app/contrib/controller.tsx), - the Bots pane (plugins/hermes-bots/plugin.js). The flag was an opt-out marker with no reachable effect, so this change deletes it instead of teaching it to track the gestures. onClose alone now decides both shapes. A tab that closes shows the button. A tab without the verb shows nothing. To make a tab uncloseable, give it no close verb. hideOnly and uncloseable keep their meaning. They gate the verb, and both shapes follow the verb together. The DialogContent and SheetContent prop of the same name is a different prop and stays. It has no close verb to derive from, and one caller changes it while the dialog is open. Tests: the new tab-close-affordance test renders the real TreeGroup and asserts that button presence equals middle-click closure. It covers hideOnly chrome, a plain side pane, the uncloseable workspace, and a session tile. It reads closure from the layout tree, not from a spy, so a wired-up mock cannot pass it. A regression that hides the button on a closeable tab fails two of the four cases. The compiler rejects the deleted prop, so the test carries no fixture for it. The pane-tab unit test moves off the deleted prop. Verified with the full apps/desktop vitest suite, npm run typecheck, and npm run lint. Two electron process-spawn tests fail on this machine. They also fail on a clean tree, and they do not touch the pane shell.
ethernet8023
force-pushed
the
ethie/desktop-close
branch
from
August 21, 2026 19:36
4aff1ab to
6e20855
Compare
This was referenced Aug 21, 2026
9 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
The hover close button on a pane tab was wired separately from the close
gestures that do the same job.
PaneTabgated the button on two independentinputs — the
onCloseverb, and ashowCloseButtonprop thatTreeGroupfedfrom a
showCloseButtonflag on the pane contribution — while middle-click and⌘-click read only
onClose. A tab could therefore close on a pointer gestureand show no control for it.
The flag had no user that
hideOnlydid not already cover. Both setters alsoset
hideOnly: true, which removes every close gesture:sessionspane (apps/desktop/src/app/contrib/controller.tsx),apps/desktop/src/plugins/hermes-bots/plugin.js).The history explains why. #89551 added
showCloseButtonas an ✕-suppressionfix. #89572 cherry-picked that commit as its base, then built
hideOnlyon topand superseded it. The flag stayed behind as a vestigial rung, not as live
design, so this PR deletes it instead of teaching it to track the gestures.
onClosealone now decides both shapes. A tab that closes shows the button. Atab without the verb shows nothing. To make a tab uncloseable, give it no close
verb.
hideOnlyanduncloseablekeep their meaning: they gate the verb, andboth shapes follow the verb together.
Related Issue
No issue. This is follow-up cleanup on #89572, which introduced
hideOnlyandleft the superseded flag in the tree.
Type of Change
Changes Made
apps/desktop/src/components/ui/pane-tab.tsx: delete theshowCloseButtonprop. The render condition becomes
{onClose && !vertical && …}. TheonClosedoc comment states that the chip and the pointer gestures are oneaffordance.
apps/desktop/src/components/pane-shell/tree/renderer/track-model.ts: deleteshowCloseButtonfromPaneChrome. ThehideOnlycomment now says that itremoves the close verb, and that the ✕ follows the verb.
apps/desktop/src/components/pane-shell/tree/renderer/tree-group.tsx: deletethe
showCloseButton={chrome.showCloseButton !== false}callsite.apps/desktop/src/app/contrib/controller.tsx: drop the flag from thesessionspane data. ItshideOnly: truealready removes every gesture.apps/desktop/src/plugins/hermes-bots/plugin.js: the same for the Bots pane.apps/desktop/src/components/pane-shell/tree/renderer/tab-close-affordance.test.tsx:new contract test (see below). The
DialogContent/SheetContentprop ofthe same name is untouched: it is a different prop, it has no close verb to
derive from, and
updates-overlay.tsxchanges it while the dialog is open.apps/desktop/src/components/ui/pane-tab.test.tsx: move the "can hide thehover ✕" case off the deleted prop. It now asserts that a closeable tab shows
the button and honors middle-click.
How to Test
Behavior is identical before and after, so the proof is the mutation test:
cd apps/desktop && npx vitest run src/components/pane-shell src/components/ui/pane-tab.test.tsx— 154 tests pass.
PaneTabrender condition to{false && onClose && !vertical && (, which hides the button on tabs thatstill close on middle-click.
npx vitest run src/components/pane-shell/tree/renderer/tab-close-affordance.test.tsx— the
filesandsession-tile:abccases fail. The test detects a buttonthat stops tracking the verb.
In the app: hover the SESSIONS and BOTS tabs. Neither shows an ✕, and neither
closes on middle-click or ⌘-click, the same as before this PR. Hover a
filesor session tile tab. Each shows an ✕ and closes on middle-click, the same as
before this PR.
Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests pass — N/A for the Python suite (no Python file changes). The JS suite is the gate here: the fullapps/desktopvitest run gives 6954 pass, 2 fail. The 2 failures areelectron/remote-lifecycle.test.tsandelectron/update-handoff-marker.test.ts. They fail the same way on a clean tree on this machine (nix sandbox spawn paths), and they do not touch the pane shell.Documentation & Housekeeping
docs/, docstrings) — thePaneTab.onCloseandPaneChrome.hideOnlydoc comments now carry therule. No user-facing doc mentions the flag.
cli-config.yaml.exampleif I added/changed config keys — N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/Alib/middle-click.tsfor every host.Tests
tab-close-affordance.test.tsxrenders the realTreeGroupand asserts onecontract over every tab kind that shares a strip:
It reads closure from the layout tree (the pane is gone), not from a spy, so a
wired-up mock cannot pass it. Cases:
hideOnlychrome, a plain side pane, theuncloseableworkspace, and a mirrored session tile.The test carries no fixture for the deleted prop, because the compiler already
rejects it: passing
showCloseButtontoPaneTabnow fails withTS2322: Property 'showCloseButton' does not exist. A test tombstone would only restatethat, under a name the codebase still uses for the unrelated
DialogContentprop. The remaining cases still catch the regression they exist for: hiding the
button on a closeable tab fails the
filesandsession-tilecases.Screenshots / Logs
The single warning on the new file is
no-restricted-globalsfordocument.Every sibling DOM probe in that directory carries the same warning.