feat(desktop): expose a pane's layout zone to its content

PaneGroupContext, alongside PaneVisibleContext: the zone hands each pane
the group id it's rendered in, so state that belongs to a stack of tabs
rather than to a pane can key off it — and follows a pane dragged between
zones, since the provider is whichever zone renders it.
This commit is contained in:
Brooklyn Nicholson 2026-07-28 02:49:46 -05:00
parent ef26701134
commit 73e3e0860e
2 changed files with 23 additions and 5 deletions

View file

@ -28,6 +28,20 @@ export const PaneVisibleContext = createContext(true)
export const usePaneVisible = (): boolean => useContext(PaneVisibleContext)
/** Fallback group key for a surface rendered outside the layout tree (secondary
* windows, plain routes) one bucket, since there are no sibling zones there
* to tell apart. */
export const NO_PANE_GROUP = 'window'
/** The layout-tree GROUP (zone) a pane is rendered in the identity of "this
* set of tabs". Panes stacked as tabs share one group; each split zone is its
* own. State that should be per-zone rather than per-window or per-tab keys off
* this (see the composer pop-out). Follows a pane dragged between zones,
* because the provider is the zone that renders it. */
export const PaneGroupContext = createContext(NO_PANE_GROUP)
export const usePaneGroup = (): string => useContext(PaneGroupContext)
/** `querySelectorAll` minus anything inside an inactive tab. */
export const queryAllVisible = <T extends HTMLElement>(selector: string, root: ParentNode = document): T[] =>
[...root.querySelectorAll<T>(selector)].filter(el => !el.closest(HIDDEN_PANE))

View file

@ -24,7 +24,7 @@ import { cn } from '@/lib/utils'
import { $layoutEditMode } from '../../edit-mode'
import { useWindowControlsOverlap } from '../../geometry'
import { hiddenPaneProps, PaneVisibleContext } from '../../pane-visibility'
import { hiddenPaneProps, PaneGroupContext, PaneVisibleContext } from '../../pane-visibility'
import type { DropPosition, GroupNode, RootEdge } from '../model'
import { adjacentGroup } from '../model'
import {
@ -567,10 +567,14 @@ export function TreeGroup({
>
{pane?.render ? (
// Visibility flows to the pane so a kept-alive chat surface
// can gate its hot (per-token) subscriptions while hidden.
<PaneVisibleContext.Provider value={isActive}>
<ContribBoundary id={pane.id}>{pane.render()}</ContribBoundary>
</PaneVisibleContext.Provider>
// can gate its hot (per-token) subscriptions while hidden;
// the group id identifies the ZONE it lives in, for state
// that is per-zone rather than per-tab (composer pop-out).
<PaneGroupContext.Provider value={node.id}>
<PaneVisibleContext.Provider value={isActive}>
<ContribBoundary id={pane.id}>{pane.render()}</ContribBoundary>
</PaneVisibleContext.Provider>
</PaneGroupContext.Provider>
) : (
isActive && (
<div className="p-3 font-mono text-[11px] text-(--ui-text-quaternary)">