From 5c543cd718f5a0e636cb4b5f5235014e00110f90 Mon Sep 17 00:00:00 2001 From: Lucy Farnik Date: Mon, 30 Mar 2026 10:30:52 +0100 Subject: [PATCH 1/3] add opt-in split buttons to block headers Add two icon buttons (horizontal/vertical split) to every block's header bar, gated behind a new `app:showsplitbuttons` setting (default false). When enabled, the buttons appear before the settings cog and allow splitting panes with just the mouse. The new pane clones the current block's meta so terminals inherit shell/connection config. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/app/block/blockenv.ts | 1 + frontend/app/block/blockframe-header.tsx | 41 +++++++++++++++++++++++- frontend/types/gotypes.d.ts | 1 + pkg/wconfig/defaultconfig/settings.json | 1 + pkg/wconfig/metaconsts.go | 1 + pkg/wconfig/settingsconfig.go | 1 + schema/settings.json | 3 ++ 7 files changed, 48 insertions(+), 1 deletion(-) diff --git a/frontend/app/block/blockenv.ts b/frontend/app/block/blockenv.ts index f4eebb192..54ec1c7d0 100644 --- a/frontend/app/block/blockenv.ts +++ b/frontend/app/block/blockenv.ts @@ -13,6 +13,7 @@ export type BlockEnv = WaveEnvSubset<{ getSettingsKeyAtom: SettingsKeyAtomFnType< | "app:focusfollowscursor" | "app:showoverlayblocknums" + | "app:showsplitbuttons" | "window:magnifiedblockblurprimarypx" | "window:magnifiedblockopacity" >; diff --git a/frontend/app/block/blockframe-header.tsx b/frontend/app/block/blockframe-header.tsx index 319e9b4a4..7069d3d02 100644 --- a/frontend/app/block/blockframe-header.tsx +++ b/frontend/app/block/blockframe-header.tsx @@ -11,7 +11,13 @@ import { import { ConnectionButton } from "@/app/block/connectionbutton"; import { DurableSessionFlyover } from "@/app/block/durable-session-flyover"; import { getBlockBadgeAtom } from "@/app/store/badge"; -import { recordTEvent, refocusNode } from "@/app/store/global"; +import { + createBlockSplitHorizontally, + createBlockSplitVertically, + recordTEvent, + refocusNode, + WOS, +} from "@/app/store/global"; import { globalStore } from "@/app/store/jotaiStore"; import { uxCloseBlock } from "@/app/store/keymodel"; import { TabRpcClient } from "@/app/store/wshrpcutil"; @@ -119,12 +125,45 @@ const HeaderEndIcons = React.memo(({ viewModel, nodeModel, blockId }: HeaderEndI const ephemeral = jotai.useAtomValue(nodeModel.isEphemeral); const numLeafs = jotai.useAtomValue(nodeModel.numLeafs); const magnifyDisabled = numLeafs <= 1; + const showSplitButtons = jotai.useAtomValue(blockEnv.getSettingsKeyAtom("app:showsplitbuttons")); const endIconsElem: React.ReactElement[] = []; if (endIconButtons && endIconButtons.length > 0) { endIconsElem.push(...endIconButtons.map((button, idx) => )); } + if (showSplitButtons) { + const splitHorizontalDecl: IconButtonDecl = { + elemtype: "iconbutton", + icon: "columns", + title: "Split Horizontally", + click: (e) => { + e.stopPropagation(); + const blockAtom = WOS.getWaveObjectAtom(WOS.makeORef("block", blockId)); + const blockData = globalStore.get(blockAtom); + const blockDef: BlockDef = { + meta: blockData?.meta || { view: "term", controller: "shell" }, + }; + createBlockSplitHorizontally(blockDef, blockId, "after"); + }, + }; + const splitVerticalDecl: IconButtonDecl = { + elemtype: "iconbutton", + icon: "grip-lines", + title: "Split Vertically", + click: (e) => { + e.stopPropagation(); + const blockAtom = WOS.getWaveObjectAtom(WOS.makeORef("block", blockId)); + const blockData = globalStore.get(blockAtom); + const blockDef: BlockDef = { + meta: blockData?.meta || { view: "term", controller: "shell" }, + }; + createBlockSplitVertically(blockDef, blockId, "after"); + }, + }; + endIconsElem.push(); + endIconsElem.push(); + } const settingsDecl: IconButtonDecl = { elemtype: "iconbutton", icon: "cog", diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index b4906cb03..bc203e256 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -1381,6 +1381,7 @@ declare global { "app:ctrlvpaste"?: boolean; "app:confirmquit"?: boolean; "app:hideaibutton"?: boolean; + "app:showsplitbuttons"?: boolean; "app:disablectrlshiftarrows"?: boolean; "app:disablectrlshiftdisplay"?: boolean; "app:focusfollowscursor"?: string; diff --git a/pkg/wconfig/defaultconfig/settings.json b/pkg/wconfig/defaultconfig/settings.json index 8ed6af723..28c496afe 100644 --- a/pkg/wconfig/defaultconfig/settings.json +++ b/pkg/wconfig/defaultconfig/settings.json @@ -7,6 +7,7 @@ "app:tabbar": "top", "app:confirmquit": true, "app:hideaibutton": false, + "app:showsplitbuttons": false, "app:disablectrlshiftarrows": false, "app:disablectrlshiftdisplay": false, "app:focusfollowscursor": "off", diff --git a/pkg/wconfig/metaconsts.go b/pkg/wconfig/metaconsts.go index df048b304..ea1e434f0 100644 --- a/pkg/wconfig/metaconsts.go +++ b/pkg/wconfig/metaconsts.go @@ -14,6 +14,7 @@ const ( ConfigKey_AppCtrlVPaste = "app:ctrlvpaste" ConfigKey_AppConfirmQuit = "app:confirmquit" ConfigKey_AppHideAiButton = "app:hideaibutton" + ConfigKey_AppShowSplitButtons = "app:showsplitbuttons" ConfigKey_AppDisableCtrlShiftArrows = "app:disablectrlshiftarrows" ConfigKey_AppDisableCtrlShiftDisplay = "app:disablectrlshiftdisplay" ConfigKey_AppFocusFollowsCursor = "app:focusfollowscursor" diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index b55cab8cb..3f9288190 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -65,6 +65,7 @@ type SettingsType struct { AppCtrlVPaste *bool `json:"app:ctrlvpaste,omitempty"` AppConfirmQuit *bool `json:"app:confirmquit,omitempty"` AppHideAiButton bool `json:"app:hideaibutton,omitempty"` + AppShowSplitButtons bool `json:"app:showsplitbuttons,omitempty"` AppDisableCtrlShiftArrows bool `json:"app:disablectrlshiftarrows,omitempty"` AppDisableCtrlShiftDisplay bool `json:"app:disablectrlshiftdisplay,omitempty"` AppFocusFollowsCursor string `json:"app:focusfollowscursor,omitempty" jsonschema:"enum=off,enum=on,enum=term"` diff --git a/schema/settings.json b/schema/settings.json index 67d8f5b9d..7cfbf3928 100644 --- a/schema/settings.json +++ b/schema/settings.json @@ -29,6 +29,9 @@ "app:hideaibutton": { "type": "boolean" }, + "app:showsplitbuttons": { + "type": "boolean" + }, "app:disablectrlshiftarrows": { "type": "boolean" }, From 700354b46e506781ba879916ecc2b221c9ad7b54 Mon Sep 17 00:00:00 2001 From: Lucy Farnik Date: Wed, 1 Apr 2026 00:42:44 +0100 Subject: [PATCH 2/3] rename setting from app:showsplitbuttons to term:showsplitbuttons Per maintainer feedback, move the setting to the term: namespace. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/app/block/blockenv.ts | 2 +- frontend/app/block/blockframe-header.tsx | 2 +- frontend/types/gotypes.d.ts | 2 +- pkg/wconfig/defaultconfig/settings.json | 2 +- pkg/wconfig/metaconsts.go | 2 +- pkg/wconfig/settingsconfig.go | 2 +- schema/settings.json | 6 +++--- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/app/block/blockenv.ts b/frontend/app/block/blockenv.ts index 54ec1c7d0..8a529be11 100644 --- a/frontend/app/block/blockenv.ts +++ b/frontend/app/block/blockenv.ts @@ -13,7 +13,7 @@ export type BlockEnv = WaveEnvSubset<{ getSettingsKeyAtom: SettingsKeyAtomFnType< | "app:focusfollowscursor" | "app:showoverlayblocknums" - | "app:showsplitbuttons" + | "term:showsplitbuttons" | "window:magnifiedblockblurprimarypx" | "window:magnifiedblockopacity" >; diff --git a/frontend/app/block/blockframe-header.tsx b/frontend/app/block/blockframe-header.tsx index 7069d3d02..499bc1160 100644 --- a/frontend/app/block/blockframe-header.tsx +++ b/frontend/app/block/blockframe-header.tsx @@ -125,7 +125,7 @@ const HeaderEndIcons = React.memo(({ viewModel, nodeModel, blockId }: HeaderEndI const ephemeral = jotai.useAtomValue(nodeModel.isEphemeral); const numLeafs = jotai.useAtomValue(nodeModel.numLeafs); const magnifyDisabled = numLeafs <= 1; - const showSplitButtons = jotai.useAtomValue(blockEnv.getSettingsKeyAtom("app:showsplitbuttons")); + const showSplitButtons = jotai.useAtomValue(blockEnv.getSettingsKeyAtom("term:showsplitbuttons")); const endIconsElem: React.ReactElement[] = []; diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index bc203e256..5c61e4199 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -1381,7 +1381,6 @@ declare global { "app:ctrlvpaste"?: boolean; "app:confirmquit"?: boolean; "app:hideaibutton"?: boolean; - "app:showsplitbuttons"?: boolean; "app:disablectrlshiftarrows"?: boolean; "app:disablectrlshiftdisplay"?: boolean; "app:focusfollowscursor"?: string; @@ -1423,6 +1422,7 @@ declare global { "term:bellindicator"?: boolean; "term:osc52"?: string; "term:durable"?: boolean; + "term:showsplitbuttons"?: boolean; "editor:minimapenabled"?: boolean; "editor:stickyscrollenabled"?: boolean; "editor:wordwrap"?: boolean; diff --git a/pkg/wconfig/defaultconfig/settings.json b/pkg/wconfig/defaultconfig/settings.json index 28c496afe..ff6dbbe48 100644 --- a/pkg/wconfig/defaultconfig/settings.json +++ b/pkg/wconfig/defaultconfig/settings.json @@ -7,7 +7,7 @@ "app:tabbar": "top", "app:confirmquit": true, "app:hideaibutton": false, - "app:showsplitbuttons": false, + "term:showsplitbuttons": false, "app:disablectrlshiftarrows": false, "app:disablectrlshiftdisplay": false, "app:focusfollowscursor": "off", diff --git a/pkg/wconfig/metaconsts.go b/pkg/wconfig/metaconsts.go index ea1e434f0..6c813954f 100644 --- a/pkg/wconfig/metaconsts.go +++ b/pkg/wconfig/metaconsts.go @@ -14,7 +14,6 @@ const ( ConfigKey_AppCtrlVPaste = "app:ctrlvpaste" ConfigKey_AppConfirmQuit = "app:confirmquit" ConfigKey_AppHideAiButton = "app:hideaibutton" - ConfigKey_AppShowSplitButtons = "app:showsplitbuttons" ConfigKey_AppDisableCtrlShiftArrows = "app:disablectrlshiftarrows" ConfigKey_AppDisableCtrlShiftDisplay = "app:disablectrlshiftdisplay" ConfigKey_AppFocusFollowsCursor = "app:focusfollowscursor" @@ -60,6 +59,7 @@ const ( ConfigKey_TermBellIndicator = "term:bellindicator" ConfigKey_TermOsc52 = "term:osc52" ConfigKey_TermDurable = "term:durable" + ConfigKey_TermShowSplitButtons = "term:showsplitbuttons" ConfigKey_EditorMinimapEnabled = "editor:minimapenabled" ConfigKey_EditorStickyScrollEnabled = "editor:stickyscrollenabled" diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go index 3f9288190..2a1927630 100644 --- a/pkg/wconfig/settingsconfig.go +++ b/pkg/wconfig/settingsconfig.go @@ -65,7 +65,6 @@ type SettingsType struct { AppCtrlVPaste *bool `json:"app:ctrlvpaste,omitempty"` AppConfirmQuit *bool `json:"app:confirmquit,omitempty"` AppHideAiButton bool `json:"app:hideaibutton,omitempty"` - AppShowSplitButtons bool `json:"app:showsplitbuttons,omitempty"` AppDisableCtrlShiftArrows bool `json:"app:disablectrlshiftarrows,omitempty"` AppDisableCtrlShiftDisplay bool `json:"app:disablectrlshiftdisplay,omitempty"` AppFocusFollowsCursor string `json:"app:focusfollowscursor,omitempty" jsonschema:"enum=off,enum=on,enum=term"` @@ -111,6 +110,7 @@ type SettingsType struct { TermBellIndicator *bool `json:"term:bellindicator,omitempty"` TermOsc52 string `json:"term:osc52,omitempty" jsonschema:"enum=focus,enum=always"` TermDurable *bool `json:"term:durable,omitempty"` + TermShowSplitButtons bool `json:"term:showsplitbuttons,omitempty"` EditorMinimapEnabled bool `json:"editor:minimapenabled,omitempty"` EditorStickyScrollEnabled bool `json:"editor:stickyscrollenabled,omitempty"` diff --git a/schema/settings.json b/schema/settings.json index 7cfbf3928..91de939c3 100644 --- a/schema/settings.json +++ b/schema/settings.json @@ -29,9 +29,6 @@ "app:hideaibutton": { "type": "boolean" }, - "app:showsplitbuttons": { - "type": "boolean" - }, "app:disablectrlshiftarrows": { "type": "boolean" }, @@ -171,6 +168,9 @@ "term:durable": { "type": "boolean" }, + "term:showsplitbuttons": { + "type": "boolean" + }, "editor:minimapenabled": { "type": "boolean" }, From a8a75214f62e556150930a6da339ce0f6929242b Mon Sep 17 00:00:00 2001 From: sawka Date: Tue, 31 Mar 2026 17:06:41 -0700 Subject: [PATCH 3/3] limit to only terminal blocks --- frontend/app/block/blockframe-header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/app/block/blockframe-header.tsx b/frontend/app/block/blockframe-header.tsx index 499bc1160..a70f323e7 100644 --- a/frontend/app/block/blockframe-header.tsx +++ b/frontend/app/block/blockframe-header.tsx @@ -132,7 +132,7 @@ const HeaderEndIcons = React.memo(({ viewModel, nodeModel, blockId }: HeaderEndI if (endIconButtons && endIconButtons.length > 0) { endIconsElem.push(...endIconButtons.map((button, idx) => )); } - if (showSplitButtons) { + if (showSplitButtons && viewModel?.viewType === "term") { const splitHorizontalDecl: IconButtonDecl = { elemtype: "iconbutton", icon: "columns",