diff --git a/.changeset/26737-studio-selection-unfolds-panel.md b/.changeset/26737-studio-selection-unfolds-panel.md new file mode 100644 index 00000000000..84d8a2a8934 --- /dev/null +++ b/.changeset/26737-studio-selection-unfolds-panel.md @@ -0,0 +1,17 @@ +--- +'@object-ui/app-shell': patch +--- + +fix(app-shell): in Studio, selecting an element while the right panel is hidden unfolds the panel on its properties + +Clause-②: no +Metadata-protocol: none + +With the right panel hidden from the top bar, selecting a flow node in Automations (or a block in Interfaces, or a field in Data) showed its configuration nowhere on a desktop-width screen: only a phone reopened the panel. A builder had to find the toggle again before they could edit the selected step. + +What changes: + +- A new selection now unfolds a hidden panel at every width, on the selected element's properties, the way `⌘.` unfolds it. The panel's shown state is remembered as when you show it from the toggle. +- Hiding the panel again while the same element stays selected keeps it hidden. Only another selection unfolds it. + +What to do: nothing. Nothing a flow, page or object declares changes. diff --git a/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.pillarsClaimSlot-12725.test.tsx b/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.pillarsClaimSlot-12725.test.tsx index a7d18781266..34ff0e4debb 100644 --- a/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.pillarsClaimSlot-12725.test.tsx +++ b/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.pillarsClaimSlot-12725.test.tsx @@ -17,7 +17,8 @@ * returns it to the flow's Configuration. A row too narrow for the classic * aside opens no drawer (objectui#11795's fold is the classic layout's). * - While 属性 shows, the copilot strip stays at the slot's foot. - * - A hidden slot stays hidden on a selection, as on Interfaces. + * - A hidden slot unfolds on a new selection (objectstack#26737); hidden again + * over the same selection, it stays hidden. * - CONTROL: the Access pillar claims nothing — the slot shows the copilot * alone; the Interfaces pillar's own pins live in `rightSlot-12725`. * @@ -383,19 +384,39 @@ describe('Automations: the Configuration is in the right slot (objectui#12725)', } }); - it('a hidden slot stays hidden on a selection, as on Interfaces', async () => { - window.sessionStorage.setItem(DOCK_STUDIO_EXPANDED_STORAGE_KEY, '0'); + // objectstack#26737 — at a desktop width (no `md` match), a node selected + // while the panel is hidden unfolds it on that node's configuration. + it('a hidden slot unfolds on a selection, on the selected node’s configuration', async () => { await openFlow(); + fireEvent.click(screen.getByTestId('studio-slot-toggle')); expect(slot()).not.toBeVisible(); + expect(window.sessionStorage.getItem(DOCK_STUDIO_EXPANDED_STORAGE_KEY)).toBe('0'); fireEvent.click(screen.getByTestId('pick-node')); - await act(async () => {}); - expect(slot()).not.toBeVisible(); + await waitFor(() => expect(slot()).toBeVisible()); + expect(slot()).toHaveAttribute('data-mode', 'props'); + expect(within(slotProps()).getByLabelText('Node start')).toBeVisible(); + expect(screen.getByTestId('studio-slot-toggle')).toHaveAttribute('aria-pressed', 'true'); + expect(window.sessionStorage.getItem(DOCK_STUDIO_EXPANDED_STORAGE_KEY)).toBe('1'); expect(screen.queryByTestId('auto-config-drawer')).toBeNull(); - // Shown again, it opens on the selection's properties. + }); + + it('hidden again over the same selection, it stays hidden until another node is selected', async () => { + await openFlow(); + fireEvent.click(screen.getByTestId('pick-node')); + await waitFor(() => expect(within(slotProps()).getByTestId('stub-flow-inspector')).toBeVisible()); + fireEvent.click(screen.getByTestId('studio-slot-toggle')); - expect(slot()).toHaveAttribute('data-mode', 'props'); - expect(within(slotProps()).getByTestId('stub-flow-inspector')).toBeVisible(); + await act(async () => {}); + expect(slot()).not.toBeVisible(); + // The same node again is no new selection. + fireEvent.click(screen.getByTestId('pick-node')); + await act(async () => {}); + expect(slot()).not.toBeVisible(); + + fireEvent.click(screen.getByTestId('pick-other-node')); + await waitFor(() => expect(slot()).toBeVisible()); + expect(within(slotProps()).getByLabelText('Node end')).toBeVisible(); }); }); diff --git a/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.rightSlot-12725.test.tsx b/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.rightSlot-12725.test.tsx index aee0b100806..932b3fb721f 100644 --- a/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.rightSlot-12725.test.tsx +++ b/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.rightSlot-12725.test.tsx @@ -267,6 +267,19 @@ describe('Studio right slot: properties OR the copilot (objectui#12725)', () => expect(window.sessionStorage.getItem(DOCK_STUDIO_EXPANDED_STORAGE_KEY)).toBe('1'); }); + // objectstack#26737 — a selection is never drawn nowhere: at a desktop width + // it unfolds a hidden slot on the selected block's properties. + it('selecting a block while the slot is hidden unfolds it on the properties', async () => { + await openDashboard(); + fireEvent.click(screen.getByTestId('studio-slot-toggle')); + expect(slot()).not.toBeVisible(); + + fireEvent.click(screen.getByTestId('pick-block')); + await waitFor(() => expect(slot()).toBeVisible()); + expect(slot()).toHaveAttribute('data-mode', 'props'); + expect(within(slotProps()).getByTestId('stub-inspector')).toBeVisible(); + }); + it('the copilot tab and strip mark a plan that awaits approval', async () => { copilot.planPending = true; await openDashboard(); diff --git a/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx b/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx index 8df83030071..e3ba37b0f9a 100644 --- a/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx +++ b/ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx @@ -1807,10 +1807,19 @@ export function StudioDesignSurface({ aiSlot }: StudioDesignSurfaceProps): React setSlotPropsClaims((n) => n + 1); return () => setSlotPropsClaims((n) => n - 1); }, []); - const showSlotProps = React.useCallback(() => { + // objectstack#26737 — a selection shows its properties at every width: a + // hidden slot unfolds, as `⌘.` unfolds it, so a builder who hid the panel is + // never left with a selection whose properties are drawn nowhere. One + // function for the surface's life (state, not a memo — AGENTS.md #10): the + // pillars ask on a new selection only, never because this was re-created. + const setSlotOpenRef = React.useRef(setSlotOpen); + React.useLayoutEffect(() => { + setSlotOpenRef.current = setSlotOpen; + }); + const [showSlotProps] = React.useState(() => () => { setSlotMode('props'); - if (isMobile) setSlotOpenMobile(true); - }, [isMobile]); + setSlotOpenRef.current(true); + }); const slotHandle = React.useMemo( () => slotHost